diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /utils/hlfaceposer | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'utils/hlfaceposer')
173 files changed, 76134 insertions, 0 deletions
diff --git a/utils/hlfaceposer/AnimationBrowser.cpp b/utils/hlfaceposer/AnimationBrowser.cpp new file mode 100644 index 0000000..b487250 --- /dev/null +++ b/utils/hlfaceposer/AnimationBrowser.cpp @@ -0,0 +1,1500 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include <windows.h> +#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, COLORREF 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, RGB( 63, 63, 63 ), textRect, "%s", seqdesc.pszLabel() ); + + StudioModel *mdl = models->GetActiveStudioModel(); + if ( !mdl ) + return; + + OffsetRect( &textRect, 0, textheight ); + + helper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 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, RGB( 50, 200, 255 ), textRect, "frames %i", + mdl->GetNumFrames( sequence ) ); + + OffsetRect( &textRect, 0, textheight - 4 ); + + helper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 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( 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( RGB( 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, RGB( 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, 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, RGB( 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->pszName(), 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->pszName(), 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->pszName(), 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(); +}
\ No newline at end of file diff --git a/utils/hlfaceposer/AnimationBrowser.h b/utils/hlfaceposer/AnimationBrowser.h new file mode 100644 index 0000000..8d3b53e --- /dev/null +++ b/utils/hlfaceposer/AnimationBrowser.h @@ -0,0 +1,187 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#if !defined( ANIMATIONBROWSER_H ) +#define ANIMATIONBROWSER_H +#ifdef _WIN32 +#pragma once +#endif + +#define IDC_AB_TRAYSCROLL 1001 +#define IDC_AB_THUMBNAIL_INCREASE 1002 +#define IDC_AB_THUMBNAIL_DECREASE 1003 +#define IDC_AB_CONTEXT_CREATEBITMAP 1004 +#define IDC_AB_CONTEXT_CREATEALLBITMAPS 1005 +#define IDC_AB_FILTERTAB 1006 + +#define IDC_AB_CREATE_CUSTOM 1007 + +#define IDC_AB_ADDTOGROUPSTART 1100 +#define IDC_AB_ADDTOGROUPEND 1199 + +#define IDC_AB_REMOVEFROMGROUPSTART 1200 +#define IDC_AB_REMOVEFROMGROUPEND 1299 + +#define IDC_AB_DELETEGROUPSTART 1300 +#define IDC_AB_DELETEGROUPEND 1399 + +#define IDC_AB_RENAMEGROUPSTART 1400 +#define IDC_AB_RENAMEGROUPEND 1499 + +#define COLOR_TRAYBACKGROUND RGB( 240, 240, 220 ) + +#include "faceposertoolwindow.h" +#include "StudioModel.h" + +class CAnimBrowserTab; + +class CCustomAnim +{ +public: + CCustomAnim( const FileNameHandle_t &h ) + : + m_bDirty( false ), + m_ShortName( UTL_INVAL_SYMBOL ) + { + m_Handle = h; + } + + void LoadFromFile(); + void SaveToFile(); + + bool HasAnimation( char const *search ); + + bool m_bDirty; + CUtlSymbol m_ShortName; + FileNameHandle_t m_Handle; + CUtlVector< CUtlSymbol > m_Animations; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class AnimationBrowser : public mxWindow, public IFacePoserToolWindow +{ +public: + enum + { + FILTER_NONE = 0, + FILTER_GESTURES, + FILTER_POSTURES, + FILTER_STRING, + FILTER_FIRST_CUSTOM + }; + + AnimationBrowser( mxWindow *parent, int id = 0 ); + virtual ~AnimationBrowser ( void ); + + virtual void Shutdown(); + + virtual void redraw (); + virtual bool PaintBackground( void ); + + virtual int handleEvent (mxEvent *event); + + virtual void Think( float dt ); + + void ThumbnailIncrease( void ); + void ThumbnailDecrease( void ); + void RestoreThumbnailSize( void ); + + void Select( int sequence ); + void Deselect( void ); + + void SetCellSize( int cellsize ); + + void ReloadBitmaps( void ); + virtual void OnModelChanged(); + + void OnAddCustomAnimationFilter(); + +private: // Methods + + void OnFilter(); + bool SequencePassesFilter( StudioModel *model, int sequence, mstudioseqdesc_t &seqdesc ); + + int GetSequenceCount(); + mstudioseqdesc_t *GetSeqDesc( int index ); + int TranslateSequenceNumber( int index ); + + int GetCellUnderPosition( int x, int y ); + + bool ComputeRect( int cell, int& rcx, int& rcy, int& rcw, int& rch ); + int ComputePixelsNeeded( void ); + + void RepositionSlider(); + void SetClickedCell( int cell ); + void ShowRightClickMenu( int mx, int my ); + + void DrawThumbNail( int sequence, CChoreoWidgetDrawHelper& helper, + int rcx, int rcy, int rcw, int rch ); + + void DrawSequenceFocusRect( CChoreoWidgetDrawHelper& helper, int x, int y, int w, int h, COLORREF clr ); + void DrawSequenceDescription( CChoreoWidgetDrawHelper& helper, int x, int y, int w, int h, int sequence, mstudioseqdesc_t &seqdesc ); + + void DrawFocusRect( void ); + + // Custom group tab stuff + void FindCustomFiles( char const *subdir, CUtlVector< FileNameHandle_t >& files ); + void AddCustomFile( const FileNameHandle_t& handle ); + void RenameCustomFile( int index ); + void DeleteCustomFile( int index ); + void PurgeCustom(); + void BuildCustomFromFiles( CUtlVector< FileNameHandle_t >& files ); + void UpdateCustomTabs(); + int FindCustomFile( char const *shortName ); + void AddAnimationToCustomFile( int index, char const *animationName ); + void RemoveAnimationFromCustomFile( int index, char const *animationName ); + void RemoveAllAnimationsFromCustomFile( int index ); + +private: // Data + + mxScrollbar *slScrollbar; + CAnimBrowserTab *m_pFilterTab; + mxLineEdit *m_pSearchEntry; + + int m_nTopOffset; + + int m_nLastNumAnimations; + + int m_nGranularity; + + int m_nCurCell; + int m_nClickedCell; + + // Formatting + int m_nButtonSquare; + + int m_nGap; + int m_nDescriptionHeight; + int m_nSnapshotWidth; + int m_nSnapshotHeight; + + bool m_bDragging; + RECT m_rcFocus; + RECT m_rcOrig; + int m_nDragCell; + int m_nXStart; + int m_nYStart; + + mxButton *m_pThumbnailIncreaseButton; + mxButton *m_pThumbnailDecreaseButton; + + CUtlVector< int > m_Filtered; + int m_nCurFilter; + char m_szSearchString[ 256 ]; + + float m_flDragTime; + + CUtlVector< CCustomAnim * > m_CustomAnimationTabs; +}; + +extern AnimationBrowser *g_pAnimationBrowserTool; + +#endif // ANIMATIONBROWSER_H
\ No newline at end of file diff --git a/utils/hlfaceposer/CloseCaptionTool.cpp b/utils/hlfaceposer/CloseCaptionTool.cpp new file mode 100644 index 0000000..670439c --- /dev/null +++ b/utils/hlfaceposer/CloseCaptionTool.cpp @@ -0,0 +1,995 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" +#include <stdio.h> +#include "hlfaceposer.h" +#include "CloseCaptionTool.h" +#include "choreowidgetdrawhelper.h" +#include <vgui/ILocalize.h> + +extern vgui::ILocalize *g_pLocalize; + +using namespace vgui; + +CloseCaptionTool *g_pCloseCaptionTool = 0; + +#define STREAM_FONT "Tahoma" +#define STREAM_POINTSIZE 12 +#define STREAM_LINEHEIGHT ( STREAM_POINTSIZE + 2 ) +#define STREAM_WEIGHT FW_NORMAL + +#define CAPTION_LINGER_TIME 1.5f +// FIXME: Yahn, what is this, it's coded up as a DELAY before when the closed caption is displayed. That seems odd. +#define CAPTION_PREDISPLAY_TIME 0.0f // 0.5f + + + + +// A work unit is a pre-processed chunk of CC text to display +// Any state changes (font/color/etc) cause a new work unit to be precomputed +// Moving onto a new line also causes a new Work Unit +// The width and height are stored so that layout can be quickly recomputed each frame +class CCloseCaptionWorkUnit +{ +public: + CCloseCaptionWorkUnit(); + ~CCloseCaptionWorkUnit(); + + void SetWidth( int w ); + int GetWidth() const; + + void SetHeight( int h ); + int GetHeight() const; + + void SetPos( int x, int y ); + void GetPos( int& x, int &y ) const; + + void SetBold( bool bold ); + bool GetBold() const; + + void SetItalic( bool ital ); + bool GetItalic() const; + + void SetStream( const wchar_t *stream ); + const wchar_t *GetStream() const; + + void SetColor( COLORREF clr ); + COLORREF GetColor() const; + + int GetFontNumber() const + { + return CloseCaptionTool::GetFontNumber( m_bBold, m_bItalic ); + } + + void Dump() + { + char buf[ 2048 ]; + g_pLocalize->ConvertUnicodeToANSI( GetStream(), buf, sizeof( buf ) ); + + Msg( "x = %i, y = %i, w = %i h = %i text %s\n", m_nX, m_nY, m_nWidth, m_nHeight, buf ); + } + +private: + + int m_nX; + int m_nY; + int m_nWidth; + int m_nHeight; + + bool m_bBold; + bool m_bItalic; + wchar_t *m_pszStream; + COLORREF m_Color; +}; + +CCloseCaptionWorkUnit::CCloseCaptionWorkUnit() : + m_nWidth(0), + m_nHeight(0), + m_bBold(false), + m_bItalic(false), + m_pszStream(0), + m_Color( RGB( 255, 255, 255 ) ) +{ +} + +CCloseCaptionWorkUnit::~CCloseCaptionWorkUnit() +{ + delete[] m_pszStream; + m_pszStream = NULL; +} + +void CCloseCaptionWorkUnit::SetWidth( int w ) +{ + m_nWidth = w; +} + +int CCloseCaptionWorkUnit::GetWidth() const +{ + return m_nWidth; +} + +void CCloseCaptionWorkUnit::SetHeight( int h ) +{ + m_nHeight = h; +} + +int CCloseCaptionWorkUnit::GetHeight() const +{ + return m_nHeight; +} + +void CCloseCaptionWorkUnit::SetPos( int x, int y ) +{ + m_nX = x; + m_nY = y; +} + +void CCloseCaptionWorkUnit::GetPos( int& x, int &y ) const +{ + x = m_nX; + y = m_nY; +} + +void CCloseCaptionWorkUnit::SetBold( bool bold ) +{ + m_bBold = bold; +} + +bool CCloseCaptionWorkUnit::GetBold() const +{ + return m_bBold; +} + +void CCloseCaptionWorkUnit::SetItalic( bool ital ) +{ + m_bItalic = ital; +} + +bool CCloseCaptionWorkUnit::GetItalic() const +{ + return m_bItalic; +} + +void CCloseCaptionWorkUnit::SetStream( const wchar_t *stream ) +{ + delete[] m_pszStream; + m_pszStream = NULL; + + int len = wcslen( stream ); + Assert( len < 4096 ); + m_pszStream = new wchar_t[ len + 1 ]; + wcsncpy( m_pszStream, stream, len ); + m_pszStream[ len ] = L'\0'; +} + +const wchar_t *CCloseCaptionWorkUnit::GetStream() const +{ + return m_pszStream ? m_pszStream : L""; +} + +void CCloseCaptionWorkUnit::SetColor( COLORREF clr ) +{ + m_Color = clr; +} + +COLORREF CCloseCaptionWorkUnit::GetColor() const +{ + return m_Color; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CCloseCaptionItem +{ +public: + CCloseCaptionItem( + wchar_t *stream, + float timetolive, + float predisplay, + bool valid + ) : + m_flTimeToLive( 0.0f ), + m_bValid( false ), + m_nTotalWidth( 0 ), + m_nTotalHeight( 0 ), + m_bSizeComputed( false ) + { + SetStream( stream ); + SetTimeToLive( timetolive ); + SetPreDisplayTime( CAPTION_PREDISPLAY_TIME + predisplay ); + m_bValid = valid; + m_bSizeComputed = false; + } + + CCloseCaptionItem( const CCloseCaptionItem& src ) + { + SetStream( src.m_szStream ); + m_flTimeToLive = src.m_flTimeToLive; + m_bValid = src.m_bValid; + } + + ~CCloseCaptionItem( void ) + { + while ( m_Work.Count() > 0 ) + { + CCloseCaptionWorkUnit *unit = m_Work[ 0 ]; + m_Work.Remove( 0 ); + delete unit; + } + } + + void SetStream( const wchar_t *stream) + { + wcsncpy( m_szStream, stream, sizeof( m_szStream ) / sizeof( wchar_t ) ); + } + + const wchar_t *GetStream() const + { + return m_szStream; + } + + void SetTimeToLive( float ttl ) + { + m_flTimeToLive = ttl; + } + + float GetTimeToLive( void ) const + { + return m_flTimeToLive; + } + + bool IsValid() const + { + return m_bValid; + } + + void SetHeight( int h ) + { + m_nTotalHeight = h; + } + int GetHeight() const + { + return m_nTotalHeight; + } + void SetWidth( int w ) + { + m_nTotalWidth = w; + } + int GetWidth() const + { + return m_nTotalWidth; + } + + void AddWork( CCloseCaptionWorkUnit *unit ) + { + m_Work.AddToTail( unit ); + } + + int GetNumWorkUnits() const + { + return m_Work.Count(); + } + + CCloseCaptionWorkUnit *GetWorkUnit( int index ) + { + Assert( index >= 0 && index < m_Work.Count() ); + + return m_Work[ index ]; + } + + void SetSizeComputed( bool computed ) + { + m_bSizeComputed = computed; + } + + bool GetSizeComputed() const + { + return m_bSizeComputed; + } + + void SetPreDisplayTime( float t ) + { + m_flPreDisplayTime = t; + } + + float GetPreDisplayTime() const + { + return m_flPreDisplayTime; + } +private: + wchar_t m_szStream[ 256 ]; + + float m_flPreDisplayTime; + float m_flTimeToLive; + bool m_bValid; + int m_nTotalWidth; + int m_nTotalHeight; + + bool m_bSizeComputed; + + CUtlVector< CCloseCaptionWorkUnit * > m_Work; +}; + +ICloseCaptionManager *closecaptionmanager = NULL; + +CloseCaptionTool::CloseCaptionTool( mxWindow *parent ) +: IFacePoserToolWindow( "CloseCaptionTool", "Close Caption" ), mxWindow( parent, 0, 0, 0, 0 ) +{ + m_nLastItemCount = -1; + closecaptionmanager = this; + + m_hFonts[ CCFONT_NORMAL ] = CreateFont( + -STREAM_POINTSIZE, + 0, + 0, + 0, + STREAM_WEIGHT, + FALSE, + FALSE, + FALSE, + DEFAULT_CHARSET, + OUT_TT_PRECIS, + CLIP_DEFAULT_PRECIS, + ANTIALIASED_QUALITY, + DEFAULT_PITCH, + STREAM_FONT ); + + m_hFonts[ CCFONT_ITALIC ] = CreateFont( + -STREAM_POINTSIZE, + 0, + 0, + 0, + STREAM_WEIGHT, + TRUE, + FALSE, + FALSE, + DEFAULT_CHARSET, + OUT_TT_PRECIS, + CLIP_DEFAULT_PRECIS, + ANTIALIASED_QUALITY, + DEFAULT_PITCH, + STREAM_FONT ); + + m_hFonts[ CCFONT_BOLD ] = CreateFont( + -STREAM_POINTSIZE, + 0, + 0, + 0, + 700, + FALSE, + FALSE, + FALSE, + DEFAULT_CHARSET, + OUT_TT_PRECIS, + CLIP_DEFAULT_PRECIS, + ANTIALIASED_QUALITY, + DEFAULT_PITCH, + STREAM_FONT ); + + m_hFonts[ CCFONT_ITALICBOLD ] = CreateFont( + -STREAM_POINTSIZE, + 0, + 0, + 0, + 700, + TRUE, + FALSE, + FALSE, + DEFAULT_CHARSET, + OUT_TT_PRECIS, + CLIP_DEFAULT_PRECIS, + ANTIALIASED_QUALITY, + DEFAULT_PITCH, + STREAM_FONT ); +} + +CloseCaptionTool::~CloseCaptionTool( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dt - +//----------------------------------------------------------------------------- +void CloseCaptionTool::Think( float dt ) +{ + int c = m_Items.Count(); + int i; + + // Pass one decay all timers + for ( i = 0 ; i < c ; ++i ) + { + CCloseCaptionItem *item = m_Items[ i ]; + + float predisplay = item->GetPreDisplayTime(); + if ( predisplay > 0.0f ) + { + predisplay -= dt; + predisplay = max( 0.0f, predisplay ); + item->SetPreDisplayTime( predisplay ); + } + else + { + // remove time from actual playback + float ttl = item->GetTimeToLive(); + ttl -= dt; + ttl = max( 0.0f, ttl ); + item->SetTimeToLive( ttl ); + } + } + + // Pass two, remove from head until we get to first item with time remaining + bool foundfirstnondeletion = false; + for ( i = 0 ; i < c ; ++i ) + { + CCloseCaptionItem *item = m_Items[ i ]; + + // Skip items not yet showing... + float predisplay = item->GetPreDisplayTime(); + if ( predisplay > 0.0f ) + { + continue; + } + + float ttl = item->GetTimeToLive(); + if ( ttl > 0.0f ) + { + foundfirstnondeletion = true; + continue; + } + + // Skip the remainder of the items after we find the first/oldest active item + if ( foundfirstnondeletion ) + { + continue; + } + + delete item; + m_Items.Remove( i ); + --i; + --c; + } + + if ( m_Items.Count() != m_nLastItemCount ) + { + redraw(); + } + m_nLastItemCount = m_Items.Count(); +} + +struct VisibleStreamItem +{ + int height; + CCloseCaptionItem *item; +}; + +void CloseCaptionTool::redraw() +{ + if ( !ToolCanDraw() ) + return; + + CChoreoWidgetDrawHelper drawHelper( this ); + HandleToolRedraw( drawHelper ); + + RECT rcOutput; + drawHelper.GetClientRect( rcOutput ); + + RECT rcText = rcOutput; + drawHelper.DrawFilledRect( RGB( 0, 0, 0 ), rcText ); + drawHelper.DrawOutlinedRect( RGB( 200, 245, 150 ), PS_SOLID, 2, rcText ); + InflateRect( &rcText, -4, 0 ); + + int avail_width = rcText.right - rcText.left; + + int totalheight = 0; + int i; + CUtlVector< VisibleStreamItem > visibleitems; + int c = m_Items.Count(); + for ( i = 0; i < c; i++ ) + { + CCloseCaptionItem *item = m_Items[ i ]; + + // Not ready for display yet. + if ( item->GetPreDisplayTime() > 0.0f ) + { + continue; + } + + if ( !item->GetSizeComputed() ) + { + ComputeStreamWork( drawHelper, avail_width, item ); + } + + int itemheight = item->GetHeight(); + + totalheight += itemheight; + + VisibleStreamItem si; + si.height = itemheight; + si.item = item; + + visibleitems.AddToTail( si ); + } + + rcText.bottom -= 2; + rcText.top = rcText.bottom - totalheight; + + // Now draw them + c = visibleitems.Count(); + for ( i = 0; i < c; i++ ) + { + VisibleStreamItem *si = &visibleitems[ i ]; + + int height = si->height; + CCloseCaptionItem *item = si->item; + + rcText.bottom = rcText.top + height; + + DrawStream( drawHelper, rcText, item ); + + OffsetRect( &rcText, 0, height ); + + if ( rcText.top >= rcOutput.bottom ) + break; + } +} + +int CloseCaptionTool::handleEvent( mxEvent *event ) +{ + //MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + int iret = 0; + + if ( HandleToolEvent( event ) ) + { + return iret; + } + + return iret; +} + +bool CloseCaptionTool::PaintBackground() +{ + redraw(); + return false; +} + +void CloseCaptionTool::Reset( void ) +{ + while ( m_Items.Count() > 0 ) + { + CCloseCaptionItem *i = m_Items[ 0 ]; + delete i; + m_Items.Remove( 0 ); + } +} + +void CloseCaptionTool::Process( char const *tokenname, float duration, int languageid ) +{ + bool valid = true; + wchar_t stream[ 256 ]; + if ( !LookupUnicodeText( languageid, tokenname, stream, sizeof( stream ) / sizeof( wchar_t ) ) ) + { + valid = false; + g_pLocalize->ConvertANSIToUnicode( va( "--> Missing Caption[%s]", tokenname ), stream, sizeof( stream ) ); + } + + if ( !wcsncmp( stream, L"!!!", wcslen( L"!!!" ) ) ) + { + // It's in the text file, but hasn't been translated... + valid = false; + } + + // Nothing to do... + if ( wcslen( stream ) == 0 ) + { + return; + } + + float delay = 0.0f; + + wchar_t phrase[ 1024 ]; + wchar_t *out = phrase; + + for ( const wchar_t *curpos = stream; curpos && *curpos != L'\0'; ++curpos ) + { + wchar_t cmd[ 256 ]; + wchar_t args[ 256 ]; + + if ( SplitCommand( &curpos, cmd, args ) ) + { + if ( !wcscmp( cmd, L"delay" ) ) + { + + // End current phrase + *out = L'\0'; + + if ( wcslen( phrase ) > 0 ) + { + CCloseCaptionItem *item = new CCloseCaptionItem( phrase, duration + CAPTION_LINGER_TIME, delay, valid ); + m_Items.AddToTail( item ); + } + + // Start new phrase + out = phrase; + + // Delay must be positive + delay = max( 0.0f, (float)wcstod( args, NULL ) ); + + continue; + } + } + + *out++ = *curpos; + } + + // End final phrase, if any + *out = L'\0'; + if ( wcslen( phrase ) > 0 ) + { + CCloseCaptionItem *item = new CCloseCaptionItem( phrase, duration + CAPTION_LINGER_TIME, delay, valid ); + m_Items.AddToTail( item ); + } +} + +bool CloseCaptionTool::LookupUnicodeText( int languageId, char const *token, wchar_t *outbuf, size_t count ) +{ + wchar_t *outstr = g_pLocalize->Find( token ); + if ( !outstr ) + { + wcsncpy( outbuf, L"<can't find entry>", count ); + return false; + } + + wcsncpy( outbuf, outstr, count ); + + return true; +} + +bool CloseCaptionTool::LookupStrippedUnicodeText( int languageId, char const *token, wchar_t *outbuf, size_t count ) +{ + wchar_t *outstr = g_pLocalize->Find( token ); + if ( !outstr ) + { + wcsncpy( outbuf, L"<can't find entry>", count ); + return false; + } + + const wchar_t *curpos = outstr; + wchar_t *out = outbuf; + size_t outlen = 0; + + for ( ; + curpos && *curpos != L'\0' && outlen < count; + ++curpos ) + { + wchar_t cmd[ 256 ]; + wchar_t args[ 256 ]; + + if ( SplitCommand( &curpos, cmd, args ) ) + { + continue; + } + + *out++ = *curpos; + ++outlen; + } + + *out = L'\0'; + + return true; +} + +bool CloseCaptionTool::SplitCommand( wchar_t const **ppIn, wchar_t *cmd, wchar_t *args ) const +{ + const wchar_t *in = *ppIn; + const wchar_t *oldin = in; + + if ( in[0] != L'<' ) + { + *ppIn += ( oldin - in ); + return false; + } + + args[ 0 ] = 0; + cmd[ 0 ]= 0; + wchar_t *out = cmd; + in++; + while ( *in != L'\0' && *in != L':' && *in != L'>' && !isspace( *in ) ) + { + *out++ = *in++; + } + *out = L'\0'; + + if ( *in != L':' ) + { + *ppIn += ( in - oldin ); + return true; + } + + in++; + out = args; + while ( *in != L'\0' && *in != L'>' ) + { + *out++ = *in++; + } + *out = L'\0'; + + //if ( *in == L'>' ) + // in++; + + *ppIn += ( in - oldin ); + return true; +} + +struct WorkUnitParams +{ + WorkUnitParams() + { + Q_memset( stream, 0, sizeof( stream ) ); + out = stream; + x = 0; + y = 0; + width = 0; + bold = italic = false; + clr = RGB( 255, 255, 255 ); + newline = false; + } + + ~WorkUnitParams() + { + } + + void Finalize() + { + *out = L'\0'; + } + + void Next() + { + // Restart output + Q_memset( stream, 0, sizeof( stream ) ); + out = stream; + + x += width; + + width = 0; + // Leave bold, italic and color alone!!! + + if ( newline ) + { + newline = false; + x = 0; + y += STREAM_LINEHEIGHT; + } + } + + int GetFontNumber() + { + return CloseCaptionTool::GetFontNumber( bold, italic ); + } + + wchar_t stream[ 1024 ]; + wchar_t *out; + + int x; + int y; + int width; + bool bold; + bool italic; + COLORREF clr; + bool newline; +}; + +void CloseCaptionTool::AddWorkUnit( CCloseCaptionItem *item, + WorkUnitParams& params ) +{ + params.Finalize(); + + if ( wcslen( params.stream ) > 0 ) + { + CCloseCaptionWorkUnit *wu = new CCloseCaptionWorkUnit(); + + wu->SetStream( params.stream ); + wu->SetColor( params.clr ); + wu->SetBold( params.bold ); + wu->SetItalic( params.italic ); + wu->SetWidth( params.width ); + wu->SetHeight( STREAM_LINEHEIGHT ); + wu->SetPos( params.x, params.y ); + + + int curheight = item->GetHeight(); + int curwidth = item->GetWidth(); + + curheight = max( curheight, params.y + wu->GetHeight() ); + curwidth = max( curwidth, params.x + params.width ); + + item->SetHeight( curheight ); + item->SetWidth( curwidth ); + + // Add it + item->AddWork( wu ); + + params.Next(); + } +} + +void CloseCaptionTool::ComputeStreamWork( CChoreoWidgetDrawHelper &helper, int available_width, CCloseCaptionItem *item ) +{ + // Start with a clean param block + WorkUnitParams params; + + const wchar_t *curpos = item->GetStream(); + + CUtlVector< COLORREF > colorStack; + + for ( ; curpos && *curpos != L'\0'; ++curpos ) + { + wchar_t cmd[ 256 ]; + wchar_t args[ 256 ]; + + if ( SplitCommand( &curpos, cmd, args ) ) + { + if ( !wcscmp( cmd, L"cr" ) ) + { + params.newline = true; + AddWorkUnit( item, params); + } + else if ( !wcscmp( cmd, L"clr" ) ) + { + AddWorkUnit( item, params ); + + if ( args[0] == 0 && colorStack.Count()>= 2) + { + colorStack.Remove( colorStack.Count() - 1 ); + params.clr = colorStack[ colorStack.Count() - 1 ]; + } + else + { + int r = 0, g = 0, b = 0; + COLORREF newcolor; + if ( 3 == swscanf( args, L"%i,%i,%i", &r, &g, &b ) ) + { + newcolor = RGB( r, g, b ); + colorStack.AddToTail( newcolor ); + params.clr = colorStack[ colorStack.Count() - 1 ]; + } + } + } + else if ( !wcscmp( cmd, L"playerclr" ) ) + { + AddWorkUnit( item, params ); + + if ( args[0] == 0 && colorStack.Count()>= 2) + { + colorStack.Remove( colorStack.Count() - 1 ); + params.clr = colorStack[ colorStack.Count() - 1 ]; + } + else + { + // player and npc color selector + // e.g.,. 255,255,255:200,200,200 + int pr = 0, pg = 0, pb = 0, nr = 0, ng = 0, nb = 0; + COLORREF newcolor; + if ( 6 == swscanf( args, L"%i,%i,%i:%i,%i,%i", &pr, &pg, &pb, &nr, &ng, &nb ) ) + { + // FIXME: nothing in .vcds is ever from the player... + newcolor = /*item->IsFromPlayer()*/ false ? RGB( pr, pg, pb ) : RGB( nr, ng, nb ); + colorStack.AddToTail( newcolor ); + params.clr = colorStack[ colorStack.Count() - 1 ]; + } + } + } + else if ( !wcscmp( cmd, L"I" ) ) + { + AddWorkUnit( item, params ); + params.italic = !params.italic; + } + else if ( !wcscmp( cmd, L"B" ) ) + { + AddWorkUnit( item, params ); + params.bold = !params.bold; + } + + continue; + } + + HFONT useF = m_hFonts[ params.GetFontNumber() ]; + + int w = helper.CalcTextWidthW( useF, L"%c", *curpos ); + + if ( ( params.x + params.width ) + w > available_width ) + { + params.newline = true; + AddWorkUnit( item, params ); + } + *params.out++ = *curpos; + params.width += w; + } + + // Add the final unit. + params.newline = true; + AddWorkUnit( item, params ); + + item->SetSizeComputed( true ); + + // DumpWork( item ); +} + +void CloseCaptionTool:: DumpWork( CCloseCaptionItem *item ) +{ + int c = item->GetNumWorkUnits(); + for ( int i = 0 ; i < c; ++i ) + { + CCloseCaptionWorkUnit *wu = item->GetWorkUnit( i ); + wu->Dump(); + } +} + +void CloseCaptionTool::DrawStream( CChoreoWidgetDrawHelper &helper, RECT &rcText, CCloseCaptionItem *item ) +{ + int c = item->GetNumWorkUnits(); + + RECT rcOut; + rcOut.left = rcText.left; + + for ( int i = 0 ; i < c; ++i ) + { + int x = 0; + int y = 0; + + CCloseCaptionWorkUnit *wu = item->GetWorkUnit( i ); + + HFONT useF = m_hFonts[ wu->GetFontNumber() ]; + + wu->GetPos( x, y ); + + rcOut.left = rcText.left + x; + rcOut.right = rcOut.left + wu->GetWidth(); + rcOut.top = rcText.top + y; + rcOut.bottom = rcOut.top + wu->GetHeight(); + + COLORREF useColor = wu->GetColor(); + + if ( !item->IsValid() ) + { + useColor = RGB( 255, 255, 255 ); + rcOut.right += 2; + helper.DrawFilledRect( RGB( 100, 100, 40 ), rcOut ); + } + + helper.DrawColoredTextW( useF, useColor, + rcOut, L"%s", wu->GetStream() ); + + } +} + +int CloseCaptionTool::GetFontNumber( bool bold, bool italic ) +{ + if ( bold || italic ) + { + if( bold && italic ) + { + return CloseCaptionTool::CCFONT_ITALICBOLD; + } + + if ( bold ) + { + return CloseCaptionTool::CCFONT_BOLD; + } + + if ( italic ) + { + return CloseCaptionTool::CCFONT_ITALIC; + } + } + + return CloseCaptionTool::CCFONT_NORMAL; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/CloseCaptionTool.h b/utils/hlfaceposer/CloseCaptionTool.h new file mode 100644 index 0000000..d16f23d --- /dev/null +++ b/utils/hlfaceposer/CloseCaptionTool.h @@ -0,0 +1,74 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef CLOSECAPTIONTOOL_H +#define CLOSECAPTIONTOOL_H +#ifdef _WIN32 +#pragma once +#endif + +#include <mxtk/mx.h> +#include "utlvector.h" +#include "faceposertoolwindow.h" +#include "iclosecaptionmanager.h" + +class CCloseCaptionItem; +struct WorkUnitParams; + +class CloseCaptionTool : public mxWindow, public IFacePoserToolWindow, public ICloseCaptionManager +{ +public: + // Construction + CloseCaptionTool( mxWindow *parent ); + ~CloseCaptionTool( void ); + + // ICloseCaptionManager + virtual void Reset( void ); + virtual void Process( char const *tokenname, float duration, int languageid ); + virtual bool LookupUnicodeText( int languageId, char const *token, wchar_t *outbuf, size_t count ); + virtual bool LookupStrippedUnicodeText( int languageId, char const *token, wchar_t *outbuf, size_t count ); + + // End ICloseCaptionManager + + virtual void Think( float dt ); + + virtual int handleEvent( mxEvent *event ); + virtual void redraw( void ); + virtual bool PaintBackground(); + + enum + { + CCFONT_NORMAL = 0, + CCFONT_ITALIC, + CCFONT_BOLD, + CCFONT_ITALICBOLD + }; + + static int GetFontNumber( bool bold, bool italic ); + +private: + void ComputeStreamWork( CChoreoWidgetDrawHelper &helper, int available_width, CCloseCaptionItem *item ); + void DrawStream( CChoreoWidgetDrawHelper &helper, RECT &rcText, CCloseCaptionItem *item ); + bool SplitCommand( const wchar_t **in, wchar_t *cmd, wchar_t *args ) const; + + void ParseCloseCaptionStream( const wchar_t *in, int available_width ); + + void DumpWork( CCloseCaptionItem *item ); + + void AddWorkUnit( + CCloseCaptionItem *item, + WorkUnitParams& params ); + + CUtlVector< CCloseCaptionItem * > m_Items; + + HFONT m_hFonts[ 4 ]; // normal, italic, bold, bold + italic + + int m_nLastItemCount; +}; + +extern CloseCaptionTool *g_pCloseCaptionTool; + +#endif // CLOSECAPTIONTOOL_H diff --git a/utils/hlfaceposer/EdgeProperties.cpp b/utils/hlfaceposer/EdgeProperties.cpp new file mode 100644 index 0000000..fe28498 --- /dev/null +++ b/utils/hlfaceposer/EdgeProperties.cpp @@ -0,0 +1,271 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include <mxtk/mx.h> +#include <stdio.h> +#include "resource.h" +#include "EdgeProperties.h" +#include "mdlviewer.h" +#include "hlfaceposer.h" +#include "choreoevent.h" +#include "choreoscene.h" +#include "expressions.h" +#include "choreoactor.h" +#include "ifaceposersound.h" +#include "expclass.h" +#include "scriplib.h" + +static CEdgePropertiesParams g_Params; + +void CEdgePropertiesParams::SetFromFlexTrack( CFlexAnimationTrack *track ) +{ + for ( int i = 0 ; i < 2; ++i ) + { + m_bActive[ i ] = track->IsEdgeActive( i == 0 ? true : false ); + + int curveType = 0; + track->GetEdgeInfo( i == 0 ? true : false, curveType, m_flValue[ i ] ); + + if ( i == 0 ) + { + m_InterpolatorType[ i ] = GET_RIGHT_CURVE( curveType ); + } + else + { + m_InterpolatorType[ i ] = GET_LEFT_CURVE( curveType ); + } + } +} + +void CEdgePropertiesParams::ApplyToTrack( CFlexAnimationTrack *track ) +{ + for ( int i = 0 ; i < 2; ++i ) + { + track->SetEdgeActive( i == 0 ? true : false, m_bActive[ i ] ); + + int curveType = 0; + if ( i == 0 ) + { + curveType = MAKE_CURVE_TYPE( 0, m_InterpolatorType[ i ] ); + } + else + { + curveType = MAKE_CURVE_TYPE( m_InterpolatorType[ i ], 0 ); + } + + track->SetEdgeInfo( i == 0 ? true : false, curveType, m_flValue[ i ] ); + } +} + +void CEdgePropertiesParams::SetFromCurve( CCurveData *ramp ) +{ + for ( int i = 0 ; i < 2; ++i ) + { + m_bActive[ i ] = ramp->IsEdgeActive( i == 0 ? true : false ); + + int curveType = 0; + ramp->GetEdgeInfo( i == 0 ? true : false, curveType, m_flValue[ i ] ); + + if ( i == 0 ) + { + m_InterpolatorType[ i ] = GET_RIGHT_CURVE( curveType ); + } + else + { + m_InterpolatorType[ i ] = GET_LEFT_CURVE( curveType ); + } + } +} + +void CEdgePropertiesParams::ApplyToCurve( CCurveData *ramp ) +{ + for ( int i = 0 ; i < 2; ++i ) + { + ramp->SetEdgeActive( i == 0 ? true : false, m_bActive[ i ] ); + + int curveType = 0; + if ( i == 0 ) + { + curveType = MAKE_CURVE_TYPE( 0, m_InterpolatorType[ i ] ); + } + else + { + curveType = MAKE_CURVE_TYPE( m_InterpolatorType[ i ], 0 ); + } + + ramp->SetEdgeInfo( i == 0 ? true : false, curveType, m_flValue[ i ] ); + } +} + + +static void PopulateCurveType( HWND control, CEdgePropertiesParams *params, bool isLeftEdge ) +{ + SendMessage( control, CB_RESETCONTENT, 0, 0 ); + + for ( int i = 0; i < NUM_INTERPOLATE_TYPES; ++i ) + { + SendMessage( control, CB_ADDSTRING, 0, (LPARAM)Interpolator_NameForInterpolator( i, true ) ); + } + + SendMessage( control, CB_SETCURSEL , params->m_InterpolatorType[ isLeftEdge ? 0 : 1 ], 0 ); + +} + +static void Reset( HWND hwndDlg, bool left ) +{ + SendMessage( GetDlgItem( hwndDlg, left ? IDC_LEFT_ACTIVE : IDC_RIGHT_ACTIVE ), BM_SETCHECK, + ( WPARAM )BST_UNCHECKED, + ( LPARAM )0 ); + SendMessage( GetDlgItem( hwndDlg, left ? IDC_LEFT_CURVETYPE : IDC_RIGHT_CURVETYPE ), CB_SETCURSEL, 0, 0 ); + SetDlgItemText( hwndDlg, left ? IDC_LEFT_ZEROVALUE : IDC_RIGHT_ZEROVALUE, "0.0" ); + SendMessage( GetDlgItem( hwndDlg, IDC_HOLD_OUT ), BM_SETCHECK, + ( WPARAM )BST_UNCHECKED, + ( LPARAM )0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK EdgePropertiesDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + switch(uMsg) + { + case WM_INITDIALOG: + { + g_Params.PositionSelf( hwndDlg ); + + PopulateCurveType( GetDlgItem( hwndDlg, IDC_LEFT_CURVETYPE ), &g_Params, true ); + PopulateCurveType( GetDlgItem( hwndDlg, IDC_RIGHT_CURVETYPE ), &g_Params, false ); + + SetDlgItemText( hwndDlg, IDC_LEFT_ZEROVALUE, va( "%f", g_Params.m_flValue[ 0 ] ) ); + SetDlgItemText( hwndDlg, IDC_RIGHT_ZEROVALUE, va( "%f", g_Params.m_flValue[ 1 ] ) ); + + SendMessage( GetDlgItem( hwndDlg, IDC_LEFT_ACTIVE ), BM_SETCHECK, + ( WPARAM ) g_Params.m_bActive[ 0 ] ? BST_CHECKED : BST_UNCHECKED, + ( LPARAM )0 ); + + SendMessage( GetDlgItem( hwndDlg, IDC_RIGHT_ACTIVE ), BM_SETCHECK, + ( WPARAM ) g_Params.m_bActive[ 1 ] ? BST_CHECKED : BST_UNCHECKED, + ( LPARAM )0 ); + + SetWindowText( hwndDlg, g_Params.m_szDialogTitle ); + + SetFocus( GetDlgItem( hwndDlg, IDC_LEFT_ZEROVALUE ) ); + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_LEFT_RESET: + { + Reset( hwndDlg, true ); + } + break; + case IDC_RIGHT_RESET: + { + Reset( hwndDlg, false ); + } + break; + case IDC_LEFT_CURVETYPE: + { + if ( HIWORD( wParam ) == CBN_SELCHANGE ) + { + SendMessage( GetDlgItem( hwndDlg, IDC_LEFT_ACTIVE ), BM_SETCHECK, + ( WPARAM ) BST_CHECKED, + ( LPARAM )0 ); + } + } + break; + case IDC_RIGHT_CURVETYPE: + { + if ( HIWORD( wParam ) == CBN_SELCHANGE ) + { + SendMessage( GetDlgItem( hwndDlg, IDC_RIGHT_ACTIVE ), BM_SETCHECK, + ( WPARAM ) BST_CHECKED, + ( LPARAM )0 ); + } + } + break; + case IDC_LEFT_ZEROVALUE: + { + if ( HIWORD( wParam ) == EN_CHANGE ) + { + SendMessage( GetDlgItem( hwndDlg, IDC_LEFT_ACTIVE ), BM_SETCHECK, + ( WPARAM ) BST_CHECKED, + ( LPARAM )0 ); + } + } + break; + case IDC_RIGHT_ZEROVALUE: + { + if ( HIWORD( wParam ) == EN_CHANGE ) + { + SendMessage( GetDlgItem( hwndDlg, IDC_RIGHT_ACTIVE ), BM_SETCHECK, + ( WPARAM ) BST_CHECKED, + ( LPARAM )0 ); + } + } + break; + case IDOK: + { + char sz[ 64 ]; + GetDlgItemText( hwndDlg, IDC_LEFT_ZEROVALUE, sz, sizeof( sz ) ); + g_Params.m_flValue[ 0 ] = clamp( Q_atof( sz ), 0.0f, 1.0f ); + GetDlgItemText( hwndDlg, IDC_RIGHT_ZEROVALUE, sz, sizeof( sz ) ); + g_Params.m_flValue[ 1 ] = clamp( Q_atof( sz ), 0.0f, 1.0f ); + + g_Params.m_bActive[ 0 ] = SendMessage( GetDlgItem( hwndDlg, IDC_LEFT_ACTIVE ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + g_Params.m_bActive[ 1 ] = SendMessage( GetDlgItem( hwndDlg, IDC_RIGHT_ACTIVE ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + + int interpolatorType; + interpolatorType = SendMessage( GetDlgItem( hwndDlg, IDC_LEFT_CURVETYPE ), CB_GETCURSEL, 0, 0 ); + if ( interpolatorType != CB_ERR ) + { + g_Params.m_InterpolatorType[ 0 ] = interpolatorType; + } + interpolatorType = SendMessage( GetDlgItem( hwndDlg, IDC_RIGHT_CURVETYPE ), CB_GETCURSEL, 0, 0 ); + if ( interpolatorType != CB_ERR ) + { + g_Params.m_InterpolatorType[ 1 ] = interpolatorType; + } + + EndDialog( hwndDlg, 1 ); + } + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int EdgeProperties( CEdgePropertiesParams *params ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_EDGEPROPERTIES ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)EdgePropertiesDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/EdgeProperties.h b/utils/hlfaceposer/EdgeProperties.h new file mode 100644 index 0000000..d97b477 --- /dev/null +++ b/utils/hlfaceposer/EdgeProperties.h @@ -0,0 +1,42 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef EDGEPROPERTIES_H +#define EDGEPROPERTIES_H +#ifdef _WIN32 +#pragma once +#endif + +class CCurveData; +class ChoreoScene; +class CChoreoEvent; +class CFlexAnimationTrack; + +#include "basedialogparams.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +struct CEdgePropertiesParams : public CBaseDialogParams +{ + // GlobalEvent descriptive name + char m_szName[ 256 ]; + + void SetFromFlexTrack( CFlexAnimationTrack *track ); + void ApplyToTrack( CFlexAnimationTrack *track ); + + void SetFromCurve( CCurveData *ramp ); + void ApplyToCurve( CCurveData *ramp ); + + bool m_bActive[ 2 ]; + int m_InterpolatorType[ 2 ]; + float m_flValue[ 2 ]; +}; + +int EdgeProperties( CEdgePropertiesParams *params ); + +#endif // EDGEPROPERTIES_H diff --git a/utils/hlfaceposer/EditPhrase.cpp b/utils/hlfaceposer/EditPhrase.cpp new file mode 100644 index 0000000..e6b36cc --- /dev/null +++ b/utils/hlfaceposer/EditPhrase.cpp @@ -0,0 +1,82 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#define UNICODE + +#include "resource.h" +#include "EditPhrase.h" +#include "mxtk/mx.h" +#include "mdlviewer.h" + +static CEditPhraseParams g_Params; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK EditPhraseDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + switch(uMsg) + { + case WM_INITDIALOG: + // Insert code here to put the string (to find and replace with) + // into the edit controls. + // ... + { + g_Params.PositionSelf( hwndDlg ); + + + SetDlgItemTextW( hwndDlg, IDC_INPUTSTRING, g_Params.m_szInputText ); + SetDlgItemTextA( hwndDlg, IDC_STATIC_PROMPT, g_Params.m_szPrompt ); + + SetWindowTextA( hwndDlg, g_Params.m_szDialogTitle ); + + SetFocus( GetDlgItem( hwndDlg, IDC_INPUTSTRING ) ); + SendMessage( GetDlgItem( hwndDlg, IDC_INPUTSTRING ), EM_SETSEL, 0, MAKELONG(0, 0xffff) ); + + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + g_Params.m_szInputText[ 0 ] = 0; + GetDlgItemTextW( hwndDlg, IDC_INPUTSTRING, g_Params.m_szInputText, ARRAYSIZE( g_Params.m_szInputText ) ); + EndDialog( hwndDlg, 1 ); + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int EditPhrase( CEditPhraseParams *params ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_EDITPHRASE ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)EditPhraseDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/EditPhrase.h b/utils/hlfaceposer/EditPhrase.h new file mode 100644 index 0000000..6cb0ccc --- /dev/null +++ b/utils/hlfaceposer/EditPhrase.h @@ -0,0 +1,39 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef EDITPHRASE_H +#define EDITPHRASE_H +#ifdef _WIN32 +#pragma once +#endif + +#include <stdio.h> + +//========= Copyright � 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "basedialogparams.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +struct CEditPhraseParams : public CBaseDialogParams +{ + char m_szPrompt[ 256 ]; + + // i/o input text + wchar_t m_szInputText[ 1024 ]; +}; + +// Display/create dialog +int EditPhrase( CEditPhraseParams *params ); + +#endif // EDITPHRASE_H diff --git a/utils/hlfaceposer/GestureTool.cpp b/utils/hlfaceposer/GestureTool.cpp new file mode 100644 index 0000000..bc81da0 --- /dev/null +++ b/utils/hlfaceposer/GestureTool.cpp @@ -0,0 +1,1986 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include <stdio.h> +#include "hlfaceposer.h" +#include "GestureTool.h" +#include "mdlviewer.h" +#include "choreowidgetdrawhelper.h" +#include "TimelineItem.h" +#include "expressions.h" +#include "expclass.h" +#include "choreoevent.h" +#include "StudioModel.h" +#include "choreoscene.h" +#include "choreoactor.h" +#include "choreochannel.h" +#include "ChoreoView.h" +#include "InputProperties.h" +#include "ControlPanel.h" +#include "FlexPanel.h" +#include "mxExpressionTray.h" +#include "ExpressionProperties.h" +#include "tier1/strtools.h" +#include "faceposer_models.h" +#include "UtlBuffer.h" +#include "filesystem.h" +#include "iscenetokenprocessor.h" +#include "choreoviewcolors.h" +#include "MatSysWin.h" + +GestureTool *g_pGestureTool = 0; + +#define TRAY_HEIGHT 20 +#define TRAY_ITEM_INSET 10 + +#define TAG_TOP ( TRAY_HEIGHT + 32 ) +#define TAG_BOTTOM ( TAG_TOP + 20 ) + +#define MAX_TIME_ZOOM 1000 +// 10% per step +#define TIME_ZOOM_STEP 2 + +float SnapTime( float input, float granularity ); + +GestureTool::GestureTool( mxWindow *parent ) +: IFacePoserToolWindow( "GestureTool", "Gesture" ), mxWindow( parent, 0, 0, 0, 0 ) +{ + m_bSuppressLayout = false; + + SetAutoProcess( true ); + + m_nFocusEventGlobalID = -1; + + m_flScrub = 0.0f; + m_flScrubTarget = 0.0f; + m_nDragType = DRAGTYPE_NONE; + + m_nClickedX = 0; + m_nClickedY = 0; + + m_hPrevCursor = 0; + + m_nStartX = 0; + m_nStartY = 0; + + m_pLastEvent = NULL; + + m_nMousePos[ 0 ] = m_nMousePos[ 1 ] = 0; + + m_nMinX = 0; + m_nMaxX = 0; + m_bUseBounds = false; + + m_bLayoutIsValid = false; + m_flPixelsPerSecond = 500.0f; + + m_flLastDuration = 0.0f; + m_nScrollbarHeight = 12; + m_flLeftOffset = 0.0f; + m_nLastHPixelsNeeded = -1; + m_pHorzScrollBar = new mxScrollbar( this, 0, 0, 18, 100, IDC_GESTUREHSCROLL, mxScrollbar::Horizontal ); + m_pHorzScrollBar->setVisible( false ); + + m_bInSetEvent = false; + m_flScrubberTimeOffset = 0.0f; +} + +GestureTool::~GestureTool( void ) +{ +} + +void GestureTool::SetEvent( CChoreoEvent *event ) +{ + if ( m_bInSetEvent ) + return; + + m_bInSetEvent = true; + + if ( event == m_pLastEvent ) + { + if ( event ) + { + if ( event->GetDuration() != m_flLastDuration ) + { + m_flLastDuration = event->GetDuration(); + m_nLastHPixelsNeeded = -1; + m_flLeftOffset = 0.0f; + InvalidateLayout(); + } + + m_nFocusEventGlobalID = event->GetGlobalID(); + } + + m_bInSetEvent = false; + return; + } + + m_pLastEvent = event; + + m_nFocusEventGlobalID = -1; + if ( event ) + { + m_nFocusEventGlobalID = event->GetGlobalID(); + } + + if ( event ) + { + m_flLastDuration = event->GetDuration(); + } + else + { + m_flLastDuration = 0.0f; + } + m_flLeftOffset = 0.0f; + m_nLastHPixelsNeeded = -1; + InvalidateLayout(); + + m_bInSetEvent = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CChoreoEvent *GestureTool::GetSafeEvent( void ) +{ + if ( m_nFocusEventGlobalID == -1 ) + return NULL; + + if ( !g_pChoreoView ) + return NULL; + + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( !scene ) + return NULL; + + // Find event by name + for ( int i = 0; i < scene->GetNumEvents() ; i++ ) + { + CChoreoEvent *e = scene->GetEvent( i ); + if ( !e || e->GetType() != CChoreoEvent::GESTURE ) + continue; + + if ( e->GetGlobalID() == m_nFocusEventGlobalID ) + { + return e; + } + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rcHandle - +//----------------------------------------------------------------------------- +void GestureTool::GetScrubHandleRect( RECT& rcHandle, float scrub, bool clipped ) +{ + float pixel = 0.0f; + if ( w2() > 0 ) + { + pixel = GetPixelForTimeValue( scrub ); + + if ( clipped ) + { + pixel = clamp( pixel, SCRUBBER_HANDLE_WIDTH / 2, w2() - SCRUBBER_HANDLE_WIDTH / 2 ); + } + } + + rcHandle.left = pixel- SCRUBBER_HANDLE_WIDTH / 2; + rcHandle.right = pixel + SCRUBBER_HANDLE_WIDTH / 2; + rcHandle.top = 2 + GetCaptionHeight(); + rcHandle.bottom = rcHandle.top + SCRUBBER_HANDLE_HEIGHT; +} + +void GestureTool::GetScrubHandleReferenceRect( RECT& rcHandle, float scrub, bool clipped /*= false*/ ) +{ + float pixel = 0.0f; + if ( w2() > 0 ) + { + pixel = GetPixelForTimeValue( scrub ); + + if ( clipped ) + { + pixel = clamp( pixel, SCRUBBER_HANDLE_WIDTH/2, w2() - SCRUBBER_HANDLE_WIDTH/2 ); + } + } + + rcHandle.left = pixel-SCRUBBER_HANDLE_WIDTH/2; + rcHandle.right = pixel + SCRUBBER_HANDLE_WIDTH/2; + rcHandle.top = 2 + GetCaptionHeight() + 195; + rcHandle.bottom = rcHandle.top + SCRUBBER_HANDLE_HEIGHT; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rcHandle - +//----------------------------------------------------------------------------- +void GestureTool::DrawScrubHandle( CChoreoWidgetDrawHelper& drawHelper, RECT& rcHandle, float scrub, bool reference ) +{ + HBRUSH br = CreateSolidBrush( reference ? RGB( 150, 0, 0 ) : RGB( 0, 150, 100 ) ); + + COLORREF areaBorder = RGB( 230, 230, 220 ); + + drawHelper.DrawColoredLine( areaBorder, + PS_SOLID, 1, 0, rcHandle.top, w2(), rcHandle.top ); + drawHelper.DrawColoredLine( areaBorder, + PS_SOLID, 1, 0, rcHandle.bottom, w2(), rcHandle.bottom ); + + drawHelper.DrawFilledRect( br, rcHandle ); + + // + char sz[ 32 ]; + sprintf( sz, "%.3f", scrub ); + + CChoreoEvent *ev = GetSafeEvent(); + if ( ev ) + { + float st, ed; + st = ev->GetStartTime(); + ed = ev->GetEndTime(); + + float dt = ed - st; + if ( dt > 0.0f ) + { + sprintf( sz, "%.3f", st + scrub ); + } + } + + int len = drawHelper.CalcTextWidth( "Arial", 9, 500, sz ); + + RECT rcText = rcHandle; + + int textw = rcText.right - rcText.left; + + rcText.left += ( textw - len ) / 2; + + drawHelper.DrawColoredText( "Arial", 9, 500, RGB( 255, 255, 255 ), rcText, sz ); + + DeleteObject( br ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool GestureTool::IsMouseOverScrubHandle( mxEvent *event ) +{ + RECT rcHandle; + GetScrubHandleRect( rcHandle, m_flScrub, true ); + InflateRect( &rcHandle, 2, 2 ); + + POINT pt; + pt.x = (short)event->x; + pt.y = (short)event->y; + if ( PtInRect( &rcHandle, pt ) ) + { + return true; + } + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool GestureTool::IsProcessing( void ) +{ + if ( !GetSafeEvent() ) + return false; + + if ( m_flScrub != m_flScrubTarget ) + return true; + + return false; +} + +bool GestureTool::IsScrubbing( void ) const +{ + bool scrubbing = ( m_nDragType == DRAGTYPE_SCRUBBER ) ? true : false; + return scrubbing; +} + +void GestureTool::SetScrubTime( float t ) +{ + m_flScrub = t; + CChoreoEvent *e = GetSafeEvent(); + if ( e && e->GetDuration() ) + { + float realtime = e->GetStartTime() + m_flScrub; + + g_pChoreoView->SetScrubTime( realtime ); + g_pChoreoView->DrawScrubHandle(); + } +} + +void GestureTool::SetScrubTargetTime( float t ) +{ + m_flScrubTarget = t; + CChoreoEvent *e = GetSafeEvent(); + if ( e && e->GetDuration() ) + { + float realtime = e->GetStartTime() + m_flScrubTarget; + + g_pChoreoView->SetScrubTargetTime( realtime ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dt - +//----------------------------------------------------------------------------- +void GestureTool::Think( float dt ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + bool scrubbing = IsScrubbing(); + ScrubThink( dt, scrubbing ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dt - +//----------------------------------------------------------------------------- +void GestureTool::ScrubThink( float dt, bool scrubbing ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + if ( m_flScrubTarget == m_flScrub && !scrubbing ) + return; + + float d = m_flScrubTarget - m_flScrub; + int sign = d > 0.0f ? 1 : -1; + + float maxmove = dt; + + if ( sign > 0 ) + { + if ( d < maxmove ) + { + SetScrubTime( m_flScrubTarget ); + } + else + { + SetScrubTime( m_flScrub + maxmove ); + } + } + else + { + if ( -d < maxmove ) + { + SetScrubTime( m_flScrubTarget ); + } + else + { + SetScrubTime( m_flScrub - maxmove ); + } + } + + if ( scrubbing ) + { + g_pMatSysWindow->Frame(); + } +} + +void GestureTool::DrawScrubHandles() +{ + RECT rcTray; + + RECT rcHandle; + GetScrubHandleRect( rcHandle, m_flScrub, true ); + + rcTray = rcHandle; + rcTray.left = 0; + rcTray.right = w2(); + + CChoreoWidgetDrawHelper drawHelper( this, rcTray ); + DrawScrubHandle( drawHelper, rcHandle, m_flScrub, false ); + + CChoreoEvent *ev = GetSafeEvent(); + if ( ev && ev->GetDuration() > 0.0f ) + { + float scrub = ev->GetOriginalPercentageFromPlaybackPercentage( m_flScrub / ev->GetDuration() ) * ev->GetDuration(); + GetScrubHandleReferenceRect( rcHandle, scrub, true ); + + rcTray = rcHandle; + rcTray.left = 0; + rcTray.right = w2(); + + CChoreoWidgetDrawHelper drawHelper( this, rcTray ); + DrawScrubHandle( drawHelper, rcHandle, scrub, true ); + } +} + +void GestureTool::redraw() +{ + if ( !ToolCanDraw() ) + return; + + CChoreoWidgetDrawHelper drawHelper( this ); + HandleToolRedraw( drawHelper ); + + RECT rc; + drawHelper.GetClientRect( rc ); + + CChoreoEvent *ev = GetSafeEvent(); + if ( ev ) + { + RECT rcText; + drawHelper.GetClientRect( rcText ); + rcText.top += GetCaptionHeight()+1; + rcText.bottom = rcText.top + 13; + rcText.left += 5; + rcText.right -= 5; + + OffsetRect( &rcText, 0, 12 ); + + int current, total; + + g_pChoreoView->GetUndoLevels( current, total ); + if ( total > 0 ) + { + RECT rcUndo = rcText; + OffsetRect( &rcUndo, 0, 2 ); + + drawHelper.DrawColoredText( "Small Fonts", 8, FW_NORMAL, RGB( 0, 100, 0 ), rcUndo, + "Undo: %i/%i", current, total ); + } + + rcText.left += 60; + + // Found it, write out description + // + float seqduration; + ev->GetGestureSequenceDuration( seqduration ); + + RECT rcTextLine = rcText; + + drawHelper.DrawColoredText( "Arial", 11, 900, RGB( 200, 0, 0 ), rcTextLine, + "Event: %s", + ev->GetName() ); + + OffsetRect( &rcTextLine, 0, 12 ); + + drawHelper.DrawColoredText( "Arial", 11, 900, RGB( 200, 0, 0 ), rcTextLine, + "Sequence: '%s' %.3f s.", + ev->GetParameters(), + seqduration ); + + RECT rcTimeLine; + drawHelper.GetClientRect( rcTimeLine ); + rcTimeLine.left = 0; + rcTimeLine.right = w2(); + rcTimeLine.top += ( GetCaptionHeight() + 70 ); + + float lefttime = GetTimeValueForMouse( 0 ); + float righttime = GetTimeValueForMouse( w2() ); + + DrawTimeLine( drawHelper, rcTimeLine, lefttime, righttime ); + + OffsetRect( &rcText, 0, 30 ); + + rcText.left = 5; + + RECT timeRect = rcText; + + timeRect.right = timeRect.left + 100; + + char sz[ 32 ]; + + Q_snprintf( sz, sizeof( sz ), "%.2f", lefttime + ev->GetStartTime() ); + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 0, 0, 0 ), timeRect, sz ); + + timeRect = rcText; + + Q_snprintf( sz, sizeof( sz ), "%.2f", righttime + ev->GetStartTime() ); + + int textW = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, sz ); + + timeRect.right = w2() - 10; + timeRect.left = timeRect.right - textW; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 0, 0, 0 ), timeRect, sz ); + } + + RECT rcHandle; + GetScrubHandleRect( rcHandle, m_flScrub, true ); + DrawScrubHandle( drawHelper, rcHandle, m_flScrub, false ); + + DrawEventEnd( drawHelper ); + + if ( ev && ev->GetDuration() > 0.0f ) + { + float scrub = ev->GetOriginalPercentageFromPlaybackPercentage( m_flScrub / ev->GetDuration() ) * ev->GetDuration(); + GetScrubHandleReferenceRect( rcHandle, scrub, true ); + DrawScrubHandle( drawHelper, rcHandle, scrub, true ); + } + + RECT rcTags = rc; + rcTags.top = TAG_TOP + GetCaptionHeight(); + rcTags.bottom = TAG_BOTTOM + GetCaptionHeight(); + + DrawRelativeTags( drawHelper, rcTags ); + + DrawAbsoluteTags( drawHelper ); + + RECT rcPos; + GetMouseOverPosRect( rcPos ); + DrawMouseOverPos( drawHelper, rcPos ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void GestureTool::ShowContextMenu( mxEvent *event, bool include_track_menus ) +{ + // Construct main menu + mxPopupMenu *pop = new mxPopupMenu(); + + int current, total; + g_pChoreoView->GetUndoLevels( current, total ); + if ( total > 0 ) + { + if ( current > 0 ) + { + pop->add( va( "Undo %s", g_pChoreoView->GetUndoDescription() ), IDC_UNDO_GT ); + } + + if ( current <= total - 1 ) + { + pop->add( va( "Redo %s", g_pChoreoView->GetRedoDescription() ), IDC_REDO_GT ); + } + pop->addSeparator(); + } + + CEventAbsoluteTag *tag = IsMouseOverTag( (short)event->x, (short)event->y ); + if ( tag ) + { + pop->add( va( "Delete '%s'...", tag->GetName() ), IDC_GT_DELETE_TAG ); + } + else + { + pop->add( "Insert Tag...", IDC_GT_INSERT_TAG ); + } + pop->add( "Revert Tag Timings", IDC_GT_REVERT ); + pop->add( va( "Change scale..." ), IDC_GT_CHANGESCALE ); + + pop->popup( this, (short)event->x, (short)event->y ); +} + +void GestureTool::GetWorkspaceLeftRight( int& left, int& right ) +{ + left = 0; + right = w2(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void GestureTool::DrawFocusRect( void ) +{ + HDC dc = GetDC( NULL ); + + for ( int i = 0; i < m_FocusRects.Size(); i++ ) + { + RECT rc = m_FocusRects[ i ].m_rcFocus; + + ::DrawFocusRect( dc, &rc ); + } + + ReleaseDC( NULL, dc ); +} + +void GestureTool::SetClickedPos( int x, int y ) +{ + m_nClickedX = x; + m_nClickedY = y; +} + +float GestureTool::GetTimeForClickedPos( void ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return 0.0f; + + float t = GetTimeValueForMouse( m_nClickedX ); + return t; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dragtype - +// startx - +// cursor - +//----------------------------------------------------------------------------- +void GestureTool::StartDragging( int dragtype, int startx, int starty, HCURSOR cursor ) +{ + m_nDragType = dragtype; + m_nStartX = startx; + m_nLastX = startx; + m_nStartY = starty; + m_nLastY = starty; + + if ( m_hPrevCursor ) + { + SetCursor( m_hPrevCursor ); + m_hPrevCursor = NULL; + } + m_hPrevCursor = SetCursor( cursor ); + + m_FocusRects.Purge(); + + RECT rcStart; + rcStart.left = startx; + rcStart.right = startx; + + bool addrect = true; + switch ( dragtype ) + { + default: + case DRAGTYPE_SCRUBBER: + { + RECT rcScrub; + GetScrubHandleRect( rcScrub, m_flScrub, true ); + + rcStart = rcScrub; + rcStart.left = ( rcScrub.left + rcScrub.right ) / 2; + rcStart.right = rcStart.left; + rcStart.top = rcScrub.bottom; + + rcStart.bottom = h2(); + } + break; + case DRAGTYPE_ABSOLUTE_TIMING_TAG: + { + rcStart.top = 0; + rcStart.bottom = h2(); + } + break; + } + + + if ( addrect ) + { + AddFocusRect( rcStart ); + } + + DrawFocusRect(); +} + +void GestureTool::OnMouseMove( mxEvent *event ) +{ + int mx = (short)event->x; + int my = (short)event->y; + + event->x = (short)mx; + + if ( m_nDragType != DRAGTYPE_NONE ) + { + DrawFocusRect(); + + for ( int i = 0; i < m_FocusRects.Size(); i++ ) + { + CFocusRect *f = &m_FocusRects[ i ]; + f->m_rcFocus = f->m_rcOrig; + + switch ( m_nDragType ) + { + default: + case DRAGTYPE_SCRUBBER: + { + ApplyBounds( mx, my ); + if ( w2() > 0 ) + { + float t = GetTimeValueForMouse( mx ); + t += m_flScrubberTimeOffset; + ForceScrubPosition( t ); + } + } + break; + case DRAGTYPE_ABSOLUTE_TIMING_TAG: + { + ApplyBounds( mx, my ); + } + break; + } + + OffsetRect( &f->m_rcFocus, ( mx - m_nStartX ), 0 ); + } + + DrawFocusRect(); + } + else + { + if ( m_hPrevCursor ) + { + SetCursor( m_hPrevCursor ); + m_hPrevCursor = NULL; + } + + if ( IsMouseOverScrubHandle( event ) ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + } + else if ( IsMouseOverTag( mx, my ) ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + } + } + + m_nLastX = (short)event->x; + m_nLastY = (short)event->y; +} + +int GestureTool::handleEvent( mxEvent *event ) +{ + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + int iret = 0; + + if ( HandleToolEvent( event ) ) + { + return iret; + } + + switch ( event->event ) + { + case mxEvent::Size: + { + int w, h; + w = event->width; + h = event->height; + + m_nLastHPixelsNeeded = 0; + InvalidateLayout(); + iret = 1; + } + break; + case mxEvent::MouseWheeled: + { + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( scene ) + { + int tz = g_pChoreoView->GetTimeZoom( GetToolName() ); + bool shiftdown = ( event->modifiers & mxEvent::KeyShift ) ? true : false; + int stepMultipiler = shiftdown ? 5 : 1; + + // Zoom time in / out + if ( event->height > 0 ) + { + tz = min( tz + TIME_ZOOM_STEP * stepMultipiler, MAX_TIME_ZOOM ); + } + else + { + tz = max( tz - TIME_ZOOM_STEP * stepMultipiler, TIME_ZOOM_STEP ); + } + + g_pChoreoView->SetPreservedTimeZoom( this, tz ); + } + RepositionHSlider(); + redraw(); + iret = 1; + } + break; + case mxEvent::MouseDown: + { + iret = 1; + + int mx = (short)event->x; + int my = (short)event->y; + + SetClickedPos( mx, my ); + + SetMouseOverPos( mx, my ); + DrawMouseOverPos(); + + if ( event->buttons & mxEvent::MouseRightButton ) + { + ShowContextMenu( event, false ); + return iret; + } + + if ( m_nDragType == DRAGTYPE_NONE ) + { + if ( IsMouseOverScrubHandle( event ) ) + { + if ( w2() > 0 ) + { + float t = GetTimeValueForMouse( (short)event->x ); + m_flScrubberTimeOffset = m_flScrub - t; + float maxoffset = 0.5f * (float)SCRUBBER_HANDLE_WIDTH / GetPixelsPerSecond(); + m_flScrubberTimeOffset = clamp( m_flScrubberTimeOffset, -maxoffset, maxoffset ); + t += m_flScrubberTimeOffset; + ForceScrubPosition( t ); + } + + StartDragging( DRAGTYPE_SCRUBBER, m_nClickedX, m_nClickedY, LoadCursor( NULL, IDC_SIZEWE ) ); + } + else if ( IsMouseOverTag( mx, my ) ) + { + StartDragging( DRAGTYPE_ABSOLUTE_TIMING_TAG, m_nClickedX, m_nClickedY, LoadCursor( NULL, IDC_SIZEWE ) ); + } + else + { + if ( w2() > 0 ) + { + float t = GetTimeValueForMouse( (short)event->x ); + + SetScrubTargetTime( t ); + } + } + + CalcBounds( m_nDragType ); + } + } + break; + case mxEvent::MouseDrag: + case mxEvent::MouseMove: + { + int mx = (short)event->x; + int my = (short)event->y; + + SetMouseOverPos( mx, my ); + DrawMouseOverPos(); + + OnMouseMove( event ); + + iret = 1; + } + break; + case mxEvent::MouseUp: + { + if ( event->buttons & mxEvent::MouseRightButton ) + { + return 1; + } + + int mx = (short)event->x; + int my = (short)event->y; + + if ( m_nDragType != DRAGTYPE_NONE ) + { + DrawFocusRect(); + } + + if ( m_hPrevCursor ) + { + SetCursor( m_hPrevCursor ); + m_hPrevCursor = 0; + } + + switch ( m_nDragType ) + { + case DRAGTYPE_NONE: + break; + case DRAGTYPE_SCRUBBER: + { + ApplyBounds( mx, my ); + + if ( w2() > 0 ) + { + float t = GetTimeValueForMouse( (short)event->x ); + t += m_flScrubberTimeOffset; + ForceScrubPosition( t ); + m_flScrubberTimeOffset = 0.0f; + } + } + break; + case DRAGTYPE_ABSOLUTE_TIMING_TAG: + { + ApplyBounds( mx, my ); + + CEventAbsoluteTag *tag = IsMouseOverTag( m_nClickedX, m_nClickedY ); + if ( tag && w2() && GetSafeEvent() ) + { + float t = GetTimeValueForMouse( mx ); + float lastfrac = t / GetSafeEvent()->GetDuration(); + lastfrac = clamp( lastfrac, 0.0f, 1.0f ); + + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "move absolute tag" ); + tag->SetPercentage( lastfrac ); + g_pChoreoView->PushRedo( "move absolute tag" ); + + g_pChoreoView->InvalidateLayout(); + + redraw(); + } + + } + break; + } + + m_nDragType = DRAGTYPE_NONE; + + SetMouseOverPos( mx, my ); + DrawMouseOverPos(); + + iret = 1; + } + break; + case mxEvent::KeyDown: + { + iret = g_pChoreoView->HandleZoomKey( this, event->key ); + } + break; + case mxEvent::Action: + { + iret = 1; + switch ( event->action ) + { + default: + iret = 0; + break; + case IDC_UNDO_GT: + OnUndo(); + break; + case IDC_REDO_GT: + OnRedo(); + break; + case IDC_GT_DELETE_TAG: + OnDeleteTag(); + break; + case IDC_GT_INSERT_TAG: + OnInsertTag(); + break; + case IDC_GT_REVERT: + OnRevert(); + break; + case IDC_GESTUREHSCROLL: + { + int offset = 0; + bool processed = true; + + switch ( event->modifiers ) + { + case SB_THUMBTRACK: + offset = event->height; + break; + case SB_PAGEUP: + offset = m_pHorzScrollBar->getValue(); + offset -= 20; + offset = max( offset, m_pHorzScrollBar->getMinValue() ); + break; + case SB_PAGEDOWN: + offset = m_pHorzScrollBar->getValue(); + offset += 20; + offset = min( offset, m_pHorzScrollBar->getMaxValue() ); + break; + case SB_LINEUP: + offset = m_pHorzScrollBar->getValue(); + offset -= 10; + offset = max( offset, m_pHorzScrollBar->getMinValue() ); + break; + case SB_LINEDOWN: + offset = m_pHorzScrollBar->getValue(); + offset += 10; + offset = min( offset, m_pHorzScrollBar->getMaxValue() ); + break; + default: + processed = false; + break; + } + + if ( processed ) + { + MoveTimeSliderToPos( offset ); + } + } + break; + case IDC_GT_CHANGESCALE: + { + OnChangeScale(); + } + break; + } + } + break; + } + return iret; +} + +void GestureTool::ApplyBounds( int& mx, int& my ) +{ + if ( !m_bUseBounds ) + return; + + mx = clamp( mx, m_nMinX, m_nMaxX ); +} + +int GestureTool::GetTagTypeForTag( CEventAbsoluteTag const *tag ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return -1; + + for ( int t = 0; t < CChoreoEvent::NUM_ABS_TAG_TYPES; t++ ) + { + CChoreoEvent::AbsTagType tagtype = (CChoreoEvent::AbsTagType)t; + + for ( int i = 0; i < e->GetNumAbsoluteTags( tagtype ); i++ ) + { + CEventAbsoluteTag *ptag = e->GetAbsoluteTag( tagtype, i ); + Assert( ptag ); + if ( ptag == tag ) + return t; + } + } + + return -1; +} + +void GestureTool::CalcBounds( int movetype ) +{ + switch ( movetype ) + { + default: + case DRAGTYPE_NONE: + { + m_bUseBounds = false; + m_nMinX = 0; + m_nMaxX = 0; + } + break; + case DRAGTYPE_SCRUBBER: + { + m_bUseBounds = true; + m_nMinX = 0; + m_nMaxX = w2(); + } + break; + case DRAGTYPE_ABSOLUTE_TIMING_TAG: + { + m_bUseBounds = true; + m_nMinX = 0; + m_nMaxX = w2(); + + CChoreoEvent *e = GetSafeEvent(); + CEventAbsoluteTag *tag = IsMouseOverTag( m_nClickedX, m_nClickedY ); + if ( tag && e && e->GetDuration() ) + { + m_nMinX = GetPixelForTimeValue( 0 ); + m_nMaxX = max( w2(), GetPixelForTimeValue( e->GetDuration() ) ); + + int t = GetTagTypeForTag( tag ); + if ( t != -1 ) + { + CChoreoEvent::AbsTagType tagtype = (CChoreoEvent::AbsTagType)t; + + CEventAbsoluteTag *prevTag = NULL, *nextTag = NULL; + int c = e->GetNumAbsoluteTags( tagtype ); + int i; + for ( i = 0; i < c; i++ ) + { + CEventAbsoluteTag *t = e->GetAbsoluteTag( tagtype, i ); + Assert( t ); + + if ( t == tag ) + { + prevTag = i > 0 ? e->GetAbsoluteTag( tagtype, i-1 ) : NULL; + nextTag = i < c - 1 ? e->GetAbsoluteTag( tagtype, i+1 ) : NULL; + break; + } + } + + if ( i < c ) + { + if ( prevTag ) + { + m_nMinX = GetPixelForTimeValue( prevTag->GetPercentage() * e->GetDuration() ) + 1; + } + if ( nextTag ) + { + m_nMaxX = GetPixelForTimeValue( nextTag->GetPercentage() * e->GetDuration() ) - 1; + } + } + else + { + Assert( 0 ); + } + } + } + } + break; + } +} + +bool GestureTool::PaintBackground() +{ + redraw(); + return false; +} + +void GestureTool::OnUndo( void ) +{ + g_pChoreoView->Undo(); +} + +void GestureTool::OnRedo( void ) +{ + g_pChoreoView->Redo(); +} + +void GestureTool::ForceScrubPositionFromSceneTime( float scenetime ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e || !e->GetDuration() ) + return; + + float t = scenetime - e->GetStartTime(); + m_flScrub = t; + m_flScrubTarget = t; + DrawScrubHandles(); +} + +void GestureTool::ForceScrubPosition( float t ) +{ + m_flScrub = t; + m_flScrubTarget = t; + + CChoreoEvent *e = GetSafeEvent(); + if ( e && e->GetDuration() ) + { + float realtime = e->GetStartTime() + t; + + g_pChoreoView->SetScrubTime( realtime ); + g_pChoreoView->SetScrubTargetTime( realtime ); + + g_pChoreoView->DrawScrubHandle(); + } + + DrawScrubHandles(); +} + +void GestureTool::SetMouseOverPos( int x, int y ) +{ + m_nMousePos[ 0 ] = x; + m_nMousePos[ 1 ] = y; +} + +void GestureTool::GetMouseOverPos( int &x, int& y ) +{ + x = m_nMousePos[ 0 ]; + y = m_nMousePos[ 1 ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rcPos - +//----------------------------------------------------------------------------- +void GestureTool::GetMouseOverPosRect( RECT& rcPos ) +{ + rcPos.top = GetCaptionHeight() + 12; + rcPos.left = w2() - 200; + rcPos.right = w2() - 5; + rcPos.bottom = rcPos.top + 13; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rcPos - +//----------------------------------------------------------------------------- +void GestureTool::DrawMouseOverPos( CChoreoWidgetDrawHelper& drawHelper, RECT& rcPos ) +{ + // Compute time for pixel x + float t = GetTimeValueForMouse( m_nMousePos[ 0 ] ); + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return; + + t += e->GetStartTime(); + float snapped = FacePoser_SnapTime( t ); + + // Found it, write out description + // + char sz[ 128 ]; + if ( t != snapped ) + { + Q_snprintf( sz, sizeof( sz ), "%s", FacePoser_DescribeSnappedTime( t ) ); + } + else + { + Q_snprintf( sz, sizeof( sz ), "%.3f", t ); + } + + int len = drawHelper.CalcTextWidth( "Arial", 11, 900, sz ); + + RECT rcText = rcPos; + rcText.left = max( rcPos.left, rcPos.right - len ); + + drawHelper.DrawColoredText( "Arial", 11, 900, RGB( 255, 50, 70 ), rcText, sz ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void GestureTool::DrawMouseOverPos() +{ + RECT rcPos; + GetMouseOverPosRect( rcPos ); + + CChoreoWidgetDrawHelper drawHelper( this, rcPos ); + DrawMouseOverPos( drawHelper, rcPos ); +} + +void GestureTool::AddFocusRect( RECT& rc ) +{ + RECT rcFocus = rc; + + POINT offset; + offset.x = 0; + offset.y = 0; + ClientToScreen( (HWND)getHandle(), &offset ); + OffsetRect( &rcFocus, offset.x, offset.y ); + + // Convert to screen space? + CFocusRect fr; + fr.m_rcFocus = rcFocus; + fr.m_rcOrig = rcFocus; + + m_FocusRects.AddToTail( fr ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &rcClient - +// tagtype - +// rcTray - +//----------------------------------------------------------------------------- +void GestureTool::GetTagTrayRect( RECT &rcClient, int tagtype, RECT& rcTray ) +{ + rcTray = rcClient; + + rcTray.top += ( GetCaptionHeight() + 110 ); + + rcTray.bottom = rcTray.top + 6; + + if ( tagtype == CChoreoEvent::ORIGINAL ) + { + OffsetRect( &rcTray, 0, 45 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rcClient - +// *event - +// tagtype - +// *tag - +// rcTag - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool GestureTool::GetAbsTagRect( RECT& rcClient, CChoreoEvent *event, + int tagtype, CEventAbsoluteTag *tag, RECT& rcTag ) +{ + rcTag = rcClient; + + GetTagTrayRect( rcClient, tagtype, rcTag ); + + bool clipped = false; + float t = tag->GetPercentage() * event->GetDuration(); + int tagx = GetPixelForTimeValue( t, &clipped ); + + rcTag.left = tagx - 3; + rcTag.right = tagx + 3; + + if ( clipped ) + return false; + + return true; +} + +void GestureTool::DrawAbsoluteTags( CChoreoWidgetDrawHelper& drawHelper ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + RECT rcClient; + drawHelper.GetClientRect( rcClient ); + + bool showDots = true; + if ( event->GetNumAbsoluteTags( (CChoreoEvent::AbsTagType)0 ) != + event->GetNumAbsoluteTags( (CChoreoEvent::AbsTagType)1 ) ) + { + showDots = false; + } + + int t; + for ( t = 0; t < CChoreoEvent::NUM_ABS_TAG_TYPES; t++ ) + { + CChoreoEvent::AbsTagType tagtype = ( CChoreoEvent::AbsTagType )t; + + RECT rcTray; + GetTagTrayRect( rcClient, tagtype, rcTray ); + + drawHelper.DrawColoredLine( RGB( 220, 220, 220 ), PS_SOLID, 1, rcTray.left, rcTray.top, rcTray.right, rcTray.top ); + drawHelper.DrawColoredLine( RGB( 220, 220, 220 ), PS_SOLID, 1, rcTray.left, rcTray.bottom, rcTray.right, rcTray.bottom ); + + RECT rcText; + rcText = rcTray; + + InflateRect( &rcText, 0, 4 ); + OffsetRect( &rcText, 0, t == 0 ? -10 : 10 ); + + rcText.left = 2; + + drawHelper.DrawColoredText( "Arial", 9, 500, RGB( 150, 150, 150 ), rcText, "%s", + t == 0 ? "Playback Time" : "Original Time" ); + + for ( int i = 0; i < event->GetNumAbsoluteTags( tagtype ); i++ ) + { + CEventAbsoluteTag *tag = event->GetAbsoluteTag( tagtype, i ); + if ( !tag ) + continue; + + RECT rcMark; + + bool visible = GetAbsTagRect( rcClient, event, tagtype, tag, rcMark ); + + if ( showDots && t == 1 ) + { + CChoreoEvent::AbsTagType tagtypeOther = (CChoreoEvent::AbsTagType)0; + + RECT rcMark2; + CEventAbsoluteTag *otherTag = event->GetAbsoluteTag( tagtypeOther, i ); + if ( otherTag ) + { + GetAbsTagRect( rcClient, event, tagtypeOther, otherTag, rcMark2 ); + { + int midx1 = ( rcMark.left + rcMark.right ) / 2; + int midx2 = ( rcMark2.left + rcMark2.right ) / 2; + + int y1 = rcMark.top; + int y2 = rcMark2.bottom; + + drawHelper.DrawColoredLine( + RGB( 200, 200, 200 ), PS_SOLID, 1, + midx1, y1, midx2, y2 ); + } + } + } + + if ( !visible ) + continue; + + drawHelper.DrawTriangleMarker( rcMark, RGB( 200, 0, 30 ), tagtype != CChoreoEvent::PLAYBACK ); + + RECT rcText; + rcText = rcMark; + + if ( tagtype == CChoreoEvent::PLAYBACK ) + { + rcText.top -= 15; + } + else + { + rcText.top += 10; + } + + char text[ 256 ]; + sprintf( text, "%s", tag->GetName() ); + + int len = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, text ); + rcText.left = ( rcMark.left + rcMark.right ) / 2 - len / 2; + rcText.right = rcText.left + len + 2; + + rcText.bottom = rcText.top + 10; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 200, 100, 100 ), rcText, text ); + + if ( tagtype == CChoreoEvent::PLAYBACK ) + { + rcText.top -= 10; + } + else + { + rcText.top += 10; + } + + // sprintf( text, "%.3f", tag->GetPercentage() * event->GetDuration() + event->GetStartTime() ); + sprintf( text, "%.3f", tag->GetPercentage() ); + + len = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, text ); + rcText.left = ( rcMark.left + rcMark.right ) / 2 - len / 2; + rcText.right = rcText.left + len + 2; + + rcText.bottom = rcText.top + 10; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 200, 100, 100 ), rcText, text ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rc - +// left - +// right - +//----------------------------------------------------------------------------- +void GestureTool::DrawTimeLine( CChoreoWidgetDrawHelper& drawHelper, RECT& rc, float left, float right ) +{ + RECT rcLabel; + float granularity = 0.5f; + + drawHelper.DrawColoredLine( RGB( 150, 150, 200 ), PS_SOLID, 1, rc.left, rc.top + 2, rc.right, rc.top + 2 ); + + float f = SnapTime( left, granularity ); + while ( f < right ) + { + float frac = ( f - left ) / ( right - left ); + if ( frac >= 0.0f && frac <= 1.0f ) + { + rcLabel.left = GetPixelForTimeValue( f ); + rcLabel.top = rc.top + 5; + rcLabel.bottom = rcLabel.top + 10; + + if ( f != left ) + { + drawHelper.DrawColoredLine( RGB( 220, 220, 240 ), PS_DOT, 1, + rcLabel.left, rc.top, rcLabel.left, h2() ); + } + + char sz[ 32 ]; + sprintf( sz, "%.2f", f ); + + int textWidth = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, sz ); + + rcLabel.right = rcLabel.left + textWidth; + + OffsetRect( &rcLabel, -textWidth / 2, 0 ); + + RECT rcOut = rcLabel; + if ( rcOut.left <= 0 ) + { + OffsetRect( &rcOut, -rcOut.left + 2, 0 ); + } + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 0, 50, 150 ), rcOut, sz ); + + } + f += granularity; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +// Output : CFlexTimingTag +//----------------------------------------------------------------------------- +CEventAbsoluteTag *GestureTool::IsMouseOverTag( int mx, int my ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return NULL; + + RECT rcClient; + GetClientRect( (HWND)getHandle(), &rcClient ); + + POINT pt; + pt.x = mx; + pt.y = my; + + for ( int t = 0; t < CChoreoEvent::NUM_ABS_TAG_TYPES; t++ ) + { + CChoreoEvent::AbsTagType tagtype = ( CChoreoEvent::AbsTagType )t; + + for ( int i = 0; i < event->GetNumAbsoluteTags( tagtype ); i++ ) + { + CEventAbsoluteTag *tag = event->GetAbsoluteTag( tagtype, i ); + if ( !tag ) + continue; + + if ( tag->GetLocked() ) + continue; + + RECT rcTag; + + if ( !GetAbsTagRect( rcClient, event, tagtype, tag, rcTag ) ) + continue; + + if ( !PtInRect( &rcTag, pt ) ) + continue; + + return tag; + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +// Output : int +//----------------------------------------------------------------------------- +int GestureTool::GetTagTypeForMouse( int mx, int my ) +{ + RECT rcClient; + rcClient.left = 0; + rcClient.right = w2(); + rcClient.top = 0; + rcClient.bottom = h2(); + + POINT pt; + pt.x = mx; + pt.y = my; + + for ( int t = 0; t < CChoreoEvent::NUM_ABS_TAG_TYPES; t++ ) + { + RECT rcTray; + GetTagTrayRect( rcClient, t, rcTray ); + + if ( PtInRect( &rcTray, pt ) ) + { + return t; + } + } + return -1; +} + +void GestureTool::OnInsertTag( void ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + if ( event->GetType() != CChoreoEvent::GESTURE ) + { + Con_ErrorPrintf( "Absolute Tag: Can only tag GESTURE events\n" ); + return; + } + + CInputParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Absolute Tag Name" ); + strcpy( params.m_szPrompt, "Name:" ); + + strcpy( params.m_szInputText, "" ); + + if ( !InputProperties( ¶ms ) ) + return; + + if ( strlen( params.m_szInputText ) <= 0 ) + { + Con_ErrorPrintf( "Timing Tag Name: No name entered!\n" ); + return; + } + + // Convert click to frac + float t = GetTimeValueForMouse( m_nClickedX ) / event->GetDuration(); + float tshifted = event->GetOriginalPercentageFromPlaybackPercentage( t ); + + g_pChoreoView->SetDirty( true ); + + g_pChoreoView->PushUndo( "Add Gesture Tag" ); + + event->AddAbsoluteTag( CChoreoEvent::ORIGINAL, params.m_szInputText, tshifted ); + event->AddAbsoluteTag( CChoreoEvent::PLAYBACK, params.m_szInputText, t ); + + g_pChoreoView->PushRedo( "Add Gesture Tag" ); + + // Redraw this window + redraw(); +} + +void GestureTool::OnRevert() +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + if ( !event->GetNumAbsoluteTags( CChoreoEvent::PLAYBACK ) ) + return; + + if ( event->GetNumAbsoluteTags( CChoreoEvent::PLAYBACK ) != + event->GetNumAbsoluteTags( CChoreoEvent::ORIGINAL ) ) + { + Assert( 0 ); + return; + } + + g_pChoreoView->SetDirty( true ); + + g_pChoreoView->PushUndo( "Revert Gesture Tags" ); + + int c = event->GetNumAbsoluteTags( CChoreoEvent::PLAYBACK ); + for ( int i = 0; i < c; i++ ) + { + CEventAbsoluteTag *original = event->GetAbsoluteTag( CChoreoEvent::ORIGINAL, i ); + CEventAbsoluteTag *playback = event->GetAbsoluteTag( CChoreoEvent::PLAYBACK, i ); + + playback->SetPercentage( original->GetPercentage() ); + } + + + g_pChoreoView->PushRedo( "Revert Gesture Tags" ); + + // Redraw this window + redraw(); +} + +void GestureTool::OnDeleteTag( void ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + CEventAbsoluteTag *tag = IsMouseOverTag( m_nClickedX, m_nClickedY ); + if ( !tag ) + return; + + g_pChoreoView->SetDirty( true ); + + g_pChoreoView->PushUndo( "Remove Gesture Tag" ); + + char sz[ 512 ]; + Q_strncpy( sz, tag->GetName(), sizeof( sz ) ); + + for ( int t = 0; t < CChoreoEvent::NUM_ABS_TAG_TYPES; t++ ) + { + event->RemoveAbsoluteTag( (CChoreoEvent::AbsTagType)t, sz ); + } + + g_pChoreoView->PushRedo( "Remove Gesture Tags" ); + + // Redraw this window + redraw(); +} + +void GestureTool::DrawRelativeTags( CChoreoWidgetDrawHelper& drawHelper, RECT& rc ) +{ + CChoreoEvent *gesture = GetSafeEvent(); + if ( !gesture ) + return; + + CChoreoScene *scene = gesture->GetScene(); + if ( !scene ) + return; + + float starttime = GetTimeValueForMouse( 0 ); + float endtime = GetTimeValueForMouse( w2() ); + + if ( endtime - starttime <= 0.0f ) + return; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 0, 100, 200 ), rc, "Timing Tags:" ); + + // Loop through all events in scene + + int c = scene->GetNumEvents(); + int i; + for ( i = 0; i < c; i++ ) + { + CChoreoEvent *e = scene->GetEvent( i ); + if ( !e ) + continue; + + if ( e->GetNumRelativeTags() <= 0 ) + continue; + + // See if time overlaps + if ( !e->HasEndTime() ) + continue; + + if ( ( e->GetEndTime() - e->GetStartTime() ) < starttime ) + continue; + + if ( ( e->GetStartTime() - e->GetStartTime() ) > endtime ) + continue; + + DrawRelativeTagsForEvent( drawHelper, rc, gesture, e, starttime, endtime ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rc - +//----------------------------------------------------------------------------- +void GestureTool::DrawRelativeTagsForEvent( CChoreoWidgetDrawHelper& drawHelper, RECT& rc, CChoreoEvent *gesture, CChoreoEvent *event, float starttime, float endtime ) +{ + if ( !event ) + return; + + //drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, PEColor( COLOR_PHONEME_TIMING_TAG ), rc, "Timing Tags:" ); + + for ( int i = 0; i < event->GetNumRelativeTags(); i++ ) + { + CEventRelativeTag *tag = event->GetRelativeTag( i ); + if ( !tag ) + continue; + + // + float tagtime = ( event->GetStartTime() + tag->GetPercentage() * event->GetDuration() ) - gesture->GetStartTime(); + if ( tagtime < starttime || tagtime > endtime ) + continue; + + bool clipped = false; + int left = GetPixelForTimeValue( tagtime, &clipped ); + if ( clipped ) + continue; + + //float frac = ( tagtime - starttime ) / ( endtime - starttime ); + + //int left = rc.left + (int)( frac * ( float )( rc.right - rc.left ) + 0.5f ); + + RECT rcMark; + rcMark = rc; + rcMark.top = rc.bottom - 8; + rcMark.bottom = rc.bottom; + rcMark.left = left - 4; + rcMark.right = left + 4; + + drawHelper.DrawTriangleMarker( rcMark, RGB( 0, 100, 200 ) ); + + RECT rcText; + rcText = rc; + rcText.bottom = rc.bottom - 10; + rcText.top = rcText.bottom - 10; + + int len = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, tag->GetName() ); + rcText.left = left - len / 2; + rcText.right = rcText.left + len + 2; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 0, 100, 200 ), rcText, tag->GetName() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int GestureTool::ComputeHPixelsNeeded( void ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return 0; + + int pixels = 0; + float maxtime = event->GetDuration(); + pixels = (int)( ( maxtime ) * GetPixelsPerSecond() ) + 10; + + return pixels; + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void GestureTool::RepositionHSlider( void ) +{ + int pixelsneeded = ComputeHPixelsNeeded(); + + if ( pixelsneeded <= w2() ) + { + m_pHorzScrollBar->setVisible( false ); + } + else + { + m_pHorzScrollBar->setVisible( true ); + } + m_pHorzScrollBar->setBounds( 0, h2() - m_nScrollbarHeight, w2() - m_nScrollbarHeight, m_nScrollbarHeight ); + + m_flLeftOffset = max( 0.f, m_flLeftOffset ); + m_flLeftOffset = min( (float)pixelsneeded, m_flLeftOffset ); + + m_pHorzScrollBar->setRange( 0, pixelsneeded ); + m_pHorzScrollBar->setValue( (int)m_flLeftOffset ); + m_pHorzScrollBar->setPagesize( w2() ); + + m_nLastHPixelsNeeded = pixelsneeded; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float GestureTool::GetPixelsPerSecond( void ) +{ + return m_flPixelsPerSecond * (float)g_pChoreoView->GetTimeZoom( GetToolName() )/100.0f; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : x - +//----------------------------------------------------------------------------- +void GestureTool::MoveTimeSliderToPos( int x ) +{ + m_flLeftOffset = (float)x; + m_pHorzScrollBar->setValue( (int)m_flLeftOffset ); + InvalidateRect( (HWND)m_pHorzScrollBar->getHandle(), NULL, TRUE ); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void GestureTool::InvalidateLayout( void ) +{ + if ( m_bSuppressLayout ) + return; + + if ( ComputeHPixelsNeeded() != m_nLastHPixelsNeeded ) + { + RepositionHSlider(); + } + + m_bLayoutIsValid = false; + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : st - +// ed - +//----------------------------------------------------------------------------- +void GestureTool::GetStartAndEndTime( float& st, float& ed ) +{ + st = m_flLeftOffset / GetPixelsPerSecond(); + ed = st + (float)w2() / GetPixelsPerSecond(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : float +//----------------------------------------------------------------------------- +float GestureTool::GetEventEndTime() +{ + CChoreoEvent *ev = GetSafeEvent(); + if ( !ev ) + return 1.0f; + + return ev->GetDuration(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : time - +// *clipped - +// Output : int +//----------------------------------------------------------------------------- +int GestureTool::GetPixelForTimeValue( float time, bool *clipped /*=NULL*/ ) +{ + if ( clipped ) + { + *clipped = false; + } + + float st, ed; + GetStartAndEndTime( st, ed ); + + float frac = ( time - st ) / ( ed - st ); + if ( frac < 0.0 || frac > 1.0 ) + { + if ( clipped ) + { + *clipped = true; + } + } + + int pixel = ( int )( frac * w2() ); + return pixel; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// clip - +// Output : float +//----------------------------------------------------------------------------- +float GestureTool::GetTimeValueForMouse( int mx, bool clip /*=false*/) +{ + float st, ed; + GetStartAndEndTime( st, ed ); + + if ( clip ) + { + if ( mx < 0 ) + { + return st; + } + if ( mx > w2() ) + { + return ed; + } + } + + float frac = (float)( mx ) / (float)( w2() ); + return st + frac * ( ed - st ); +} + +void GestureTool::OnChangeScale( void ) +{ + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( !scene ) + { + return; + } + + // Zoom time in / out + CInputParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Change Zoom" ); + strcpy( params.m_szPrompt, "New scale (e.g., 2.5x):" ); + + Q_snprintf( params.m_szInputText, sizeof( params.m_szInputText ), "%.2f", (float)g_pChoreoView->GetTimeZoom( GetToolName() ) / 100.0f ); + + if ( !InputProperties( ¶ms ) ) + return; + + g_pChoreoView->SetTimeZoom( GetToolName(), clamp( (int)( 100.0f * atof( params.m_szInputText ) ), 1, MAX_TIME_ZOOM ), false ); + + m_nLastHPixelsNeeded = -1; + m_flLeftOffset= 0.0f; + InvalidateLayout(); + Con_Printf( "Zoom factor %i %%\n", g_pChoreoView->GetTimeZoom( GetToolName() ) ); +} + +void GestureTool::DrawEventEnd( CChoreoWidgetDrawHelper& drawHelper ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return; + + float duration = e->GetDuration(); + if ( !duration ) + return; + + int leftx = GetPixelForTimeValue( duration ); + if ( leftx >= w2() ) + return; + + RECT rcClient; + drawHelper.GetClientRect( rcClient ); + + drawHelper.DrawColoredLine( + COLOR_CHOREO_ENDTIME, PS_SOLID, 1, + leftx, GetCaptionHeight() + 73, leftx, rcClient.bottom ); + +} + +void GestureTool::OnModelChanged() +{ + redraw(); +}
\ No newline at end of file diff --git a/utils/hlfaceposer/GestureTool.h b/utils/hlfaceposer/GestureTool.h new file mode 100644 index 0000000..1f332bd --- /dev/null +++ b/utils/hlfaceposer/GestureTool.h @@ -0,0 +1,174 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef GESTURETOOL_H +#define GESTURETOOL_H +#ifdef _WIN32 +#pragma once +#endif + +#include <mxtk/mx.h> +#include "studio.h" +#include "utlvector.h" +#include "faceposertoolwindow.h" + +class CChoreoEvent; +class CChoreoWidgetDrawHelper; +class CChoreoView; +class CEventAbsoluteTag; + +#define IDC_REDO_GT 1000 +#define IDC_UNDO_GT 1001 +#define IDC_GT_DELETE_TAG 1002 +#define IDC_GT_INSERT_TAG 1003 +#define IDC_GT_REVERT 1004 + +#define IDC_GT_CHANGESCALE 1005 +#define IDC_GESTUREHSCROLL 1006 + +class GestureTool : public mxWindow, public IFacePoserToolWindow +{ +public: + // Construction + GestureTool( mxWindow *parent ); + ~GestureTool( void ); + + virtual void Think( float dt ); + void ScrubThink( float dt, bool scrubbing ); + virtual bool IsScrubbing( void ) const; + virtual bool IsProcessing( void ); + + virtual int handleEvent( mxEvent *event ); + virtual void redraw( void ); + virtual bool PaintBackground(); + + void SetEvent( CChoreoEvent *event ); + + void GetScrubHandleRect( RECT& rcHandle, float scrub, bool clipped = false ); + void GetScrubHandleReferenceRect( RECT& rcHandle, float scrub, bool clipped = false ); + + void DrawScrubHandle( CChoreoWidgetDrawHelper& drawHelper, RECT& rcHandle, float scrub, bool reference ); + void DrawTimeLine( CChoreoWidgetDrawHelper& drawHelper, RECT& rc, float left, float right ); + void DrawEventEnd( CChoreoWidgetDrawHelper& drawHelper ); + + void DrawAbsoluteTags( CChoreoWidgetDrawHelper& drawHelper ); + bool GetAbsTagRect( RECT& rcClient, CChoreoEvent *event, int tagtype, CEventAbsoluteTag *tag, RECT& rcTag ); + void GetTagTrayRect( RECT &rcClient, int tagtype, RECT& rcTray ); + + void SetMouseOverPos( int x, int y ); + void GetMouseOverPos( int &x, int& y ); + void GetMouseOverPosRect( RECT& rcPos ); + void DrawMouseOverPos( CChoreoWidgetDrawHelper& drawHelper, RECT& rcPos ); + void DrawMouseOverPos(); + + void DrawScrubHandles(); + + CChoreoEvent *GetSafeEvent( void ); + + bool IsMouseOverScrubHandle( mxEvent *event ); + void ForceScrubPosition( float newtime ); + void ForceScrubPositionFromSceneTime( float scenetime ); + + void SetScrubTime( float t ); + void SetScrubTargetTime( float t ); + virtual void OnModelChanged(); + +private: + + void StartDragging( int dragtype, int startx, int starty, HCURSOR cursor ); + void AddFocusRect( RECT& rc ); + void OnMouseMove( mxEvent *event ); + void DrawFocusRect( void ); + void ShowContextMenu( mxEvent *event, bool include_track_menus ); + void GetWorkspaceLeftRight( int& left, int& right ); + void SetClickedPos( int x, int y ); + float GetTimeForClickedPos( void ); + + void ApplyBounds( int& mx, int& my ); + void CalcBounds( int movetype ); + void OnUndo( void ); + void OnRedo( void ); + + CEventAbsoluteTag *IsMouseOverTag( int mx, int my ); + int GetTagTypeForMouse( int mx, int my ); + + int GetTagTypeForTag( CEventAbsoluteTag const *tag ); + void OnInsertTag( void ); + void OnDeleteTag( void ); + void OnRevert( void ); + + void DrawRelativeTags( CChoreoWidgetDrawHelper& drawHelper, RECT& rc ); + void DrawRelativeTagsForEvent( CChoreoWidgetDrawHelper& drawHelper, RECT& rc, CChoreoEvent *gesture, CChoreoEvent *event, float starttime, float endtime ); + + // Readjust slider + void MoveTimeSliderToPos( int x ); + void OnChangeScale(); + int ComputeHPixelsNeeded( void ); + float GetPixelsPerSecond( void ); + void InvalidateLayout( void ); + void RepositionHSlider( void ); + void GetStartAndEndTime( float& st, float& ed ); + float GetEventEndTime(); + float GetTimeValueForMouse( int mx, bool clip = false ); + int GetPixelForTimeValue( float time, bool *clipped = NULL ); + + float m_flScrub; + float m_flScrubTarget; + + enum + { + DRAGTYPE_NONE = 0, + DRAGTYPE_SCRUBBER, + DRAGTYPE_ABSOLUTE_TIMING_TAG, + }; + + int m_nFocusEventGlobalID; + + int m_nMousePos[ 2 ]; + + bool m_bUseBounds; + int m_nMinX; + int m_nMaxX; + + HCURSOR m_hPrevCursor; + int m_nDragType; + + int m_nStartX; + int m_nStartY; + int m_nLastX; + int m_nLastY; + + int m_nClickedX; + int m_nClickedY; + + struct CFocusRect + { + RECT m_rcOrig; + RECT m_rcFocus; + }; + CUtlVector < CFocusRect > m_FocusRects; + CChoreoEvent *m_pLastEvent; + + bool m_bSuppressLayout; + // Height/width of scroll bars + int m_nScrollbarHeight; + float m_flLeftOffset; + mxScrollbar *m_pHorzScrollBar; + int m_nLastHPixelsNeeded; + // How many pixels per second we are showing in the UI + float m_flPixelsPerSecond; + // Do we need to move controls? + bool m_bLayoutIsValid; + float m_flLastDuration; + bool m_bInSetEvent; + float m_flScrubberTimeOffset; + + friend class CChoreoView; +}; + +extern GestureTool *g_pGestureTool; +#endif // GESTURETOOL_H diff --git a/utils/hlfaceposer/ICloseCaptionManager.h b/utils/hlfaceposer/ICloseCaptionManager.h new file mode 100644 index 0000000..a82a019 --- /dev/null +++ b/utils/hlfaceposer/ICloseCaptionManager.h @@ -0,0 +1,33 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ICLOSECAPTIONMANAGER_H +#define ICLOSECAPTIONMANAGER_H +#ifdef _WIN32 +#pragma once +#endif + +typedef struct tagRECT RECT; +class CSentence; +class StudioModel; +class CChoreoWidgetDrawHelper; + +class ICloseCaptionManager +{ +public: + virtual void Reset( void ) = 0; + + virtual void Process( char const *tokenname, float duration, int languageid ) = 0; + + virtual bool LookupUnicodeText( int languageId, char const *token, wchar_t *outbuf, size_t count ) = 0; + // Same as above, except strips out <> command tokens + virtual bool LookupStrippedUnicodeText( int languageId, char const *token, wchar_t *outbuf, size_t count ) = 0; +}; + +extern ICloseCaptionManager *closecaptionmanager; + +#endif // ICLOSECAPTIONMANAGER_H diff --git a/utils/hlfaceposer/ProgressDialog.cpp b/utils/hlfaceposer/ProgressDialog.cpp new file mode 100644 index 0000000..92264d5 --- /dev/null +++ b/utils/hlfaceposer/ProgressDialog.cpp @@ -0,0 +1,243 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "resource.h" +#include "ProgressDialog.h" +#include "mxtk/mx.h" +#include "mdlviewer.h" +#include "tier1/utlstring.h" +#include "tier1/strtools.h" +#include "tier1/fmtstr.h" + +#include <CommCtrl.h> + +class CProgressDialog : public IProgressDialog +{ +public: + CProgressDialog(); + + void Start( char const *pchTitle, char const *pchText, bool bShowCancel ); + void Update( float flZeroToOneFraction ); + void UpdateText( char const *pchFmt, ... ); + bool IsCancelled(); + void Finish(); + + static BOOL CALLBACK ProgressDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ); + +private: + + BOOL ProgressDialogProcImpl( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ); + + CUtlString m_sTitle; + CUtlString m_sStatus; + float m_flFraction; + + bool m_bShowCancel; + bool m_bWantsCancel; + + HWND m_hwndDlg; + + double m_flStartTime; +}; + +static CProgressDialog g_ProgressDialog; +IProgressDialog *g_pProgressDialog = &g_ProgressDialog; + +CProgressDialog::CProgressDialog() : + m_flFraction( 0.0f ), m_hwndDlg( 0 ), m_bShowCancel( false ), m_bWantsCancel( false ), m_flStartTime( 0.0f ) +{ +} + +bool CProgressDialog::IsCancelled() +{ + return m_bShowCancel && m_bWantsCancel; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +BOOL CALLBACK CProgressDialog::ProgressDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + return g_ProgressDialog.ProgressDialogProcImpl( hwndDlg, uMsg, wParam, lParam ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +void CProgressDialog::Start( char const *pchTitle, char const *pchText, bool bShowCancel ) +{ + if ( m_hwndDlg ) + { + Finish(); + } + Assert( NULL == m_hwndDlg ); + + m_sTitle = pchTitle; + m_sStatus = pchText; + m_flFraction = 0.0f; + m_bShowCancel = bShowCancel; + m_bWantsCancel = false; + m_flStartTime = Plat_FloatTime(); + + m_hwndDlg = CreateDialog( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_PROGRESS ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)ProgressDialogProc ); +} + +void CProgressDialog::Update( float flZeroToOneFraction ) +{ + m_flFraction = clamp( flZeroToOneFraction, 0.0f, 1.0f ); + + // Update the progress bar + HWND pb = GetDlgItem( m_hwndDlg, IDC_FP_PROGRESS ); + int pos = clamp( (int)( 1000.0f * flZeroToOneFraction ), 0, 1000 ); + + SendMessage( pb, (UINT) PBM_SETPOS, pos, 0 ); + + HWND btn = GetDlgItem( m_hwndDlg, IDCANCEL ); + LRESULT lr = SendMessage( btn, BM_GETSTATE, 0, 0 ); + if ( lr & BST_PUSHED ) + { + m_bWantsCancel = true; + } + + if ( GetAsyncKeyState( VK_ESCAPE ) ) + { + m_bWantsCancel = true; + } + + mx::check(); +} + +void CProgressDialog::UpdateText( char const *pchFmt, ... ) +{ + char buf[ 2048 ]; + va_list argptr; + va_start( argptr, pchFmt ); + Q_vsnprintf( buf, sizeof( buf ), pchFmt, argptr ); + va_end( argptr ); + m_sStatus = buf; + + SetDlgItemText( m_hwndDlg, IDC_FP_PROGRESS_TEXT, CFmtStr( "%s", m_sStatus.String() ) ); + SetDlgItemText( m_hwndDlg, IDC_FP_PROGRESS_PERCENT, CFmtStr( "%.2f %%", m_flFraction * 100.0f ) ); + + double elapsed = Plat_FloatTime() - m_flStartTime; + double flPercentagePerSecond = 0.0f; + if ( m_flFraction > 0.0f ) + { + flPercentagePerSecond = elapsed / m_flFraction; + } + + double flSecondsRemaining = flPercentagePerSecond * ( 1.0f - m_flFraction ); + + int seconds = (int)flSecondsRemaining; + + CFmtStr string; + + int hours = 0; + int minutes = seconds / 60; + + if ( minutes > 0 ) + { + seconds -= (minutes * 60); + hours = minutes / 60; + + if ( hours > 0 ) + { + minutes -= (hours * 60); + } + } + + if ( hours > 0 ) + { + string.sprintf( "Time Remaining: %2i:%02i:%02i", hours, minutes, seconds ); + } + else + { + string.sprintf( "Time Remaining: %02i:%02i", minutes, seconds ); + } + + SetDlgItemText( m_hwndDlg, IDC_FP_PROGRESS_ETA, string.Access() ); +} + +void CProgressDialog::Finish() +{ + if ( !m_hwndDlg ) + return; + DestroyWindow( m_hwndDlg ); + m_hwndDlg = NULL; +} + +BOOL CProgressDialog::ProgressDialogProcImpl( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + switch(uMsg) + { + case WM_INITDIALOG: + // Insert code here to put the string (to find and replace with) + // into the edit controls. + // ... + { + RECT rcDlg; + GetWindowRect( hwndDlg, &rcDlg ); + + // Get relative to primary monitor instead of actual window parent + RECT rcParent; + rcParent.left = 0; + rcParent.right = rcParent.left + GetSystemMetrics( SM_CXFULLSCREEN ); + rcParent.top = 0; + rcParent.bottom = rcParent.top + GetSystemMetrics( SM_CYFULLSCREEN ); + + int dialogw, dialogh; + int parentw, parenth; + + parentw = rcParent.right - rcParent.left; + parenth = rcParent.bottom - rcParent.top; + dialogw = rcDlg.right - rcDlg.left; + dialogh = rcDlg.bottom - rcDlg.top; + + int dlgleft, dlgtop; + dlgleft = ( parentw - dialogw ) / 2; + dlgtop = ( parenth - dialogh ) / 2; + + MoveWindow( hwndDlg, + dlgleft, + dlgtop, + dialogw, + dialogh, + TRUE + ); + + SetDlgItemText( hwndDlg, IDC_FP_PROGRESS_TITLE, m_sTitle.String() ); + SetDlgItemText( hwndDlg, IDC_FP_PROGRESS_TEXT, m_sStatus.String() ); + + HWND pb = GetDlgItem( hwndDlg, IDC_FP_PROGRESS ); + SendMessage( pb, (UINT) PBM_SETRANGE, 0, MAKELPARAM( 0, 1000 ) ); + + ShowWindow( GetDlgItem( hwndDlg, IDCANCEL ), m_bShowCancel ? SW_SHOW : SW_HIDE ); + + Update( 0.0f ); + } + return FALSE; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDCANCEL: + m_bWantsCancel = true; + break; + } + return TRUE; + } + return FALSE; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/ProgressDialog.h b/utils/hlfaceposer/ProgressDialog.h new file mode 100644 index 0000000..b4a3192 --- /dev/null +++ b/utils/hlfaceposer/ProgressDialog.h @@ -0,0 +1,26 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef PROGRESSDIALOG_H +#define PROGRESSDIALOG_H +#ifdef _WIN32 +#pragma once +#endif + +class IProgressDialog +{ +public: + + virtual void Start( char const *pchTitle, char const *pchText, bool bShowCancel ) = 0; + virtual void Update( float flZeroToOneFraction ) = 0; + virtual void UpdateText( char const *pchFmt, ... ) = 0; + virtual bool IsCancelled() = 0; + virtual void Finish() = 0; +}; + +extern IProgressDialog *g_pProgressDialog; + +#endif // PROGRESSDIALOG_H diff --git a/utils/hlfaceposer/RampTool.cpp b/utils/hlfaceposer/RampTool.cpp new file mode 100644 index 0000000..519fc61 --- /dev/null +++ b/utils/hlfaceposer/RampTool.cpp @@ -0,0 +1,2379 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include <stdio.h> +#include "hlfaceposer.h" +#include "RampTool.h" +#include "mdlviewer.h" +#include "choreowidgetdrawhelper.h" +#include "TimelineItem.h" +#include "expressions.h" +#include "expclass.h" +#include "choreoevent.h" +#include "StudioModel.h" +#include "choreoscene.h" +#include "choreoactor.h" +#include "choreochannel.h" +#include "ChoreoView.h" +#include "InputProperties.h" +#include "ControlPanel.h" +#include "FlexPanel.h" +#include "mxExpressionTray.h" +#include "ExpressionProperties.h" +#include "tier1/strtools.h" +#include "faceposer_models.h" +#include "UtlBuffer.h" +#include "filesystem.h" +#include "iscenetokenprocessor.h" +#include "choreoviewcolors.h" +#include "MatSysWin.h" +#include "curveeditorhelpers.h" +#include "EdgeProperties.h" + +RampTool *g_pRampTool = 0; + +#define TRAY_HEIGHT 20 +#define TRAY_ITEM_INSET 10 + +#define TAG_TOP ( TRAY_HEIGHT + 12 ) +#define TAG_BOTTOM ( TAG_TOP + 20 ) + +#define MAX_TIME_ZOOM 1000 +// 10% per step +#define TIME_ZOOM_STEP 2 + +RampTool::RampTool( mxWindow *parent ) +: IFacePoserToolWindow( "RampTool", "Ramp" ), mxWindow( parent, 0, 0, 0, 0 ) +{ + m_pHelper = new CCurveEditorHelper< RampTool >( this ); + + m_bSuppressLayout = false; + + SetAutoProcess( true ); + + m_nFocusEventGlobalID = -1; + + m_flScrub = 0.0f; + m_flScrubTarget = 0.0f; + m_nDragType = DRAGTYPE_NONE; + + m_nClickedX = 0; + m_nClickedY = 0; + + m_hPrevCursor = 0; + + m_nStartX = 0; + m_nStartY = 0; + + m_pLastEvent = NULL; + + m_nMousePos[ 0 ] = m_nMousePos[ 1 ] = 0; + + m_nMinX = 0; + m_nMaxX = 0; + m_bUseBounds = false; + + m_bLayoutIsValid = false; + m_flPixelsPerSecond = 500.0f; + + m_flLastDuration = 0.0f; + m_nScrollbarHeight = 12; + m_flLeftOffset = 0.0f; + m_nLastHPixelsNeeded = -1; + m_pHorzScrollBar = new mxScrollbar( this, 0, 0, 18, 100, IDC_RAMPHSCROLL, mxScrollbar::Horizontal ); + m_pHorzScrollBar->setVisible( false ); + + m_bInSetEvent = false; + m_flScrubberTimeOffset = 0.0f; + + m_nUndoSetup = 0; +} + +RampTool::~RampTool( void ) +{ + delete m_pHelper; +} + +void RampTool::SetEvent( CChoreoEvent *event ) +{ + if ( m_bInSetEvent ) + return; + + m_bInSetEvent = true; + + if ( event == m_pLastEvent ) + { + if ( event ) + { + if ( event->GetDuration() != m_flLastDuration ) + { + m_flLastDuration = event->GetDuration(); + m_nLastHPixelsNeeded = -1; + m_flLeftOffset = 0.0f; + InvalidateLayout(); + } + + m_nFocusEventGlobalID = event->GetGlobalID(); + } + + m_bInSetEvent = false; + return; + } + + m_pLastEvent = event; + + m_nFocusEventGlobalID = -1; + if ( event ) + { + m_nFocusEventGlobalID = event->GetGlobalID(); + } + + if ( event ) + { + m_flLastDuration = event->GetDuration(); + } + else + { + m_flLastDuration = 0.0f; + } + m_flLeftOffset = 0.0f; + m_nLastHPixelsNeeded = -1; + InvalidateLayout(); + + m_bInSetEvent = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CChoreoEvent *RampTool::GetSafeEvent( void ) +{ + if ( m_nFocusEventGlobalID == -1 ) + return NULL; + + if ( !g_pChoreoView ) + return NULL; + + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( !scene ) + return NULL; + + // Find event by name + for ( int i = 0; i < scene->GetNumEvents() ; i++ ) + { + CChoreoEvent *e = scene->GetEvent( i ); + if ( !e || !e->HasEndTime() ) + continue; + + if ( e->GetGlobalID() == m_nFocusEventGlobalID ) + { + return e; + } + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rcHandle - +//----------------------------------------------------------------------------- +void RampTool::GetScrubHandleRect( RECT& rcHandle, float scrub, bool clipped ) +{ + float pixel = 0.0f; + if ( w2() > 0 ) + { + pixel = GetPixelForTimeValue( scrub ); + + if ( clipped ) + { + pixel = clamp( pixel, SCRUBBER_HANDLE_WIDTH / 2, w2() - SCRUBBER_HANDLE_WIDTH / 2 ); + } + } + + rcHandle.left = pixel- SCRUBBER_HANDLE_WIDTH / 2; + rcHandle.right = pixel + SCRUBBER_HANDLE_WIDTH / 2; + rcHandle.top = 2 + GetCaptionHeight(); + rcHandle.bottom = rcHandle.top + SCRUBBER_HANDLE_HEIGHT; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rcHandle - +//----------------------------------------------------------------------------- +void RampTool::DrawScrubHandle( CChoreoWidgetDrawHelper& drawHelper, RECT& rcHandle, float scrub, bool reference ) +{ + HBRUSH br = CreateSolidBrush( reference ? RGB( 150, 0, 0 ) : RGB( 0, 150, 100 ) ); + + COLORREF areaBorder = RGB( 230, 230, 220 ); + + drawHelper.DrawColoredLine( areaBorder, + PS_SOLID, 1, 0, rcHandle.top, w2(), rcHandle.top ); + drawHelper.DrawColoredLine( areaBorder, + PS_SOLID, 1, 0, rcHandle.bottom, w2(), rcHandle.bottom ); + + drawHelper.DrawFilledRect( br, rcHandle ); + + // + char sz[ 32 ]; + sprintf( sz, "%.3f", scrub ); + + CChoreoEvent *ev = GetSafeEvent(); + if ( ev ) + { + float st, ed; + st = ev->GetStartTime(); + ed = ev->GetEndTime(); + + float dt = ed - st; + if ( dt > 0.0f ) + { + sprintf( sz, "%.3f", st + scrub ); + } + } + + int len = drawHelper.CalcTextWidth( "Arial", 9, 500, sz ); + + RECT rcText = rcHandle; + + int textw = rcText.right - rcText.left; + + rcText.left += ( textw - len ) / 2; + + drawHelper.DrawColoredText( "Arial", 9, 500, RGB( 255, 255, 255 ), rcText, sz ); + + DeleteObject( br ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool RampTool::IsMouseOverScrubHandle( mxEvent *event ) +{ + RECT rcHandle; + GetScrubHandleRect( rcHandle, m_flScrub, true ); + InflateRect( &rcHandle, 2, 2 ); + + POINT pt; + pt.x = (short)event->x; + pt.y = (short)event->y; + if ( PtInRect( &rcHandle, pt ) ) + { + return true; + } + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool RampTool::IsProcessing( void ) +{ + if ( !GetSafeEvent() ) + return false; + + if ( m_flScrub != m_flScrubTarget ) + return true; + + return false; +} + +bool RampTool::IsScrubbing( void ) const +{ + bool scrubbing = ( m_nDragType == DRAGTYPE_SCRUBBER ) ? true : false; + return scrubbing; +} + +void RampTool::SetScrubTime( float t ) +{ + m_flScrub = t; + CChoreoEvent *e = GetSafeEvent(); + if ( e && e->GetDuration() ) + { + float realtime = e->GetStartTime() + m_flScrub; + + g_pChoreoView->SetScrubTime( realtime ); + g_pChoreoView->DrawScrubHandle(); + } +} + +void RampTool::SetScrubTargetTime( float t ) +{ + m_flScrubTarget = t; + CChoreoEvent *e = GetSafeEvent(); + if ( e && e->GetDuration() ) + { + float realtime = e->GetStartTime() + m_flScrubTarget; + + g_pChoreoView->SetScrubTargetTime( realtime ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dt - +//----------------------------------------------------------------------------- +void RampTool::Think( float dt ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + bool scrubbing = IsScrubbing(); + ScrubThink( dt, scrubbing ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dt - +//----------------------------------------------------------------------------- +void RampTool::ScrubThink( float dt, bool scrubbing ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + if ( m_flScrubTarget == m_flScrub && !scrubbing ) + return; + + float d = m_flScrubTarget - m_flScrub; + int sign = d > 0.0f ? 1 : -1; + + float maxmove = dt; + + if ( sign > 0 ) + { + if ( d < maxmove ) + { + SetScrubTime( m_flScrubTarget ); + } + else + { + SetScrubTime( m_flScrub + maxmove ); + } + } + else + { + if ( -d < maxmove ) + { + SetScrubTime( m_flScrubTarget ); + } + else + { + SetScrubTime( m_flScrub - maxmove ); + } + } + + if ( scrubbing ) + { + g_pMatSysWindow->Frame(); + } +} + +void RampTool::DrawScrubHandles() +{ + RECT rcTray; + + RECT rcHandle; + GetScrubHandleRect( rcHandle, m_flScrub, true ); + + rcTray = rcHandle; + rcTray.left = 0; + rcTray.right = w2(); + + CChoreoWidgetDrawHelper drawHelper( this, rcTray ); + DrawScrubHandle( drawHelper, rcHandle, m_flScrub, false ); +} + +void RampTool::redraw() +{ + if ( !ToolCanDraw() ) + return; + + CChoreoWidgetDrawHelper drawHelper( this ); + HandleToolRedraw( drawHelper ); + + RECT rc; + drawHelper.GetClientRect( rc ); + + CChoreoEvent *ev = GetSafeEvent(); + if ( ev ) + { + RECT rcText; + drawHelper.GetClientRect( rcText ); + rcText.top += GetCaptionHeight()+1; + rcText.bottom = rcText.top + 13; + rcText.left += 5; + rcText.right -= 5; + + OffsetRect( &rcText, 0, 12 ); + + int current, total; + + g_pChoreoView->GetUndoLevels( current, total ); + if ( total > 0 ) + { + RECT rcUndo = rcText; + OffsetRect( &rcUndo, 0, 2 ); + + drawHelper.DrawColoredText( "Small Fonts", 8, FW_NORMAL, RGB( 0, 100, 0 ), rcUndo, + "Undo: %i/%i", current, total ); + } + + rcText.left += 60; + + // Found it, write out description + // + RECT rcTextLine = rcText; + + drawHelper.DrawColoredText( "Arial", 11, 900, RGB( 200, 0, 0 ), rcTextLine, + "Event: %s", + ev->GetName() ); + + RECT rcTimeLine; + drawHelper.GetClientRect( rcTimeLine ); + rcTimeLine.left = 0; + rcTimeLine.right = w2(); + rcTimeLine.top += ( GetCaptionHeight() + 50 ); + + float lefttime = GetTimeValueForMouse( 0 ); + float righttime = GetTimeValueForMouse( w2() ); + + DrawTimeLine( drawHelper, rcTimeLine, lefttime, righttime ); + + OffsetRect( &rcText, 0, 28 ); + + rcText.left = 5; + + RECT timeRect = rcText; + + timeRect.right = timeRect.left + 100; + + char sz[ 32 ]; + + Q_snprintf( sz, sizeof( sz ), "%.2f", lefttime + ev->GetStartTime() ); + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 0, 0, 0 ), timeRect, sz ); + + timeRect = rcText; + + Q_snprintf( sz, sizeof( sz ), "%.2f", righttime + ev->GetStartTime() ); + + int textW = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, sz ); + + timeRect.right = w2() - 10; + timeRect.left = timeRect.right - textW; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 0, 0, 0 ), timeRect, sz ); + } + + RECT rcHandle; + GetScrubHandleRect( rcHandle, m_flScrub, true ); + DrawScrubHandle( drawHelper, rcHandle, m_flScrub, false ); + + RECT rcSamples; + GetSampleTrayRect( rcSamples ); + DrawSamples( drawHelper, rcSamples ); + + DrawEventEnd( drawHelper ); + + RECT rcTags = rc; + rcTags.top = TAG_TOP + GetCaptionHeight(); + rcTags.bottom = TAG_BOTTOM + GetCaptionHeight(); + + DrawTimingTags( drawHelper, rcTags ); + + RECT rcPos; + GetMouseOverPosRect( rcPos ); + DrawMouseOverPos( drawHelper, rcPos ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void RampTool::ShowContextMenu( mxEvent *event, bool include_track_menus ) +{ + // Construct main menu + mxPopupMenu *pop = new mxPopupMenu(); + + int current, total; + g_pChoreoView->GetUndoLevels( current, total ); + if ( total > 0 ) + { + if ( current > 0 ) + { + pop->add( va( "Undo %s", g_pChoreoView->GetUndoDescription() ), IDC_UNDO_RT ); + } + + if ( current <= total - 1 ) + { + pop->add( va( "Redo %s", g_pChoreoView->GetRedoDescription() ), IDC_REDO_RT ); + } + pop->addSeparator(); + } + + CChoreoEvent *e = GetSafeEvent(); + if ( e ) + { + if ( CountSelected() > 0 ) + { + pop->add( va( "Delete" ), IDC_RT_DELETE ); + pop->add( "Deselect all", IDC_RT_DESELECT ); + } + pop->add( "Select all", IDC_RT_SELECTALL ); + } + + pop->add( va( "Change scale..." ), IDC_RT_CHANGESCALE ); + pop->addSeparator(); + pop->add( "Edge Properties...", IDC_RT_EDGEPROPERTIES ); + + pop->popup( this, (short)event->x, (short)event->y ); +} + +void RampTool::GetWorkspaceLeftRight( int& left, int& right ) +{ + left = 0; + right = w2(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void RampTool::DrawFocusRect( void ) +{ + HDC dc = GetDC( NULL ); + + for ( int i = 0; i < m_FocusRects.Size(); i++ ) + { + RECT rc = m_FocusRects[ i ].m_rcFocus; + + ::DrawFocusRect( dc, &rc ); + } + + ReleaseDC( NULL, dc ); +} + +void RampTool::SetClickedPos( int x, int y ) +{ + m_nClickedX = x; + m_nClickedY = y; +} + +float RampTool::GetTimeForClickedPos( void ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return 0.0f; + + float t = GetTimeValueForMouse( m_nClickedX ); + return t; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dragtype - +// startx - +// cursor - +//----------------------------------------------------------------------------- +void RampTool::StartDragging( int dragtype, int startx, int starty, HCURSOR cursor ) +{ + m_nDragType = dragtype; + m_nStartX = startx; + m_nLastX = startx; + m_nStartY = starty; + m_nLastY = starty; + + if ( m_hPrevCursor ) + { + SetCursor( m_hPrevCursor ); + m_hPrevCursor = NULL; + } + m_hPrevCursor = SetCursor( cursor ); + + m_FocusRects.Purge(); + + RECT rcStart; + rcStart.left = startx; + rcStart.right = startx; + + bool addrect = true; + switch ( dragtype ) + { + case DRAGTYPE_SCRUBBER: + { + RECT rcScrub; + GetScrubHandleRect( rcScrub, m_flScrub, true ); + + rcStart = rcScrub; + rcStart.left = ( rcScrub.left + rcScrub.right ) / 2; + rcStart.right = rcStart.left; + rcStart.top = rcScrub.bottom; + + rcStart.bottom = h2(); + } + break; + default: + { + rcStart.top = starty; + rcStart.bottom = starty; + } + break; + } + + + if ( addrect ) + { + AddFocusRect( rcStart ); + } + + DrawFocusRect(); +} + +void RampTool::OnMouseMove( mxEvent *event ) +{ + int mx = (short)event->x; + int my = (short)event->y; + + event->x = (short)mx; + + if ( m_nDragType != DRAGTYPE_NONE ) + { + DrawFocusRect(); + + for ( int i = 0; i < m_FocusRects.Size(); i++ ) + { + CFocusRect *f = &m_FocusRects[ i ]; + f->m_rcFocus = f->m_rcOrig; + + switch ( m_nDragType ) + { + default: + { + OffsetRect( &f->m_rcFocus, ( mx - m_nStartX ), ( my - m_nStartY ) ); + } + break; + case DRAGTYPE_SCRUBBER: + { + ApplyBounds( mx, my ); + if ( w2() > 0 ) + { + float t = GetTimeValueForMouse( mx ); + t += m_flScrubberTimeOffset; + ForceScrubPosition( t ); + } + + OffsetRect( &f->m_rcFocus, ( mx - m_nStartX ), 0 ); + } + break; + case DRAGTYPE_MOVEPOINTS_TIME: + case DRAGTYPE_MOVEPOINTS_VALUE: + { + int dx = mx - m_nLastX; + int dy = my - m_nLastY; + + if ( !( event->modifiers & mxEvent::KeyCtrl ) ) + { + // Zero out motion on other axis + if ( m_nDragType == DRAGTYPE_MOVEPOINTS_VALUE ) + { + dx = 0; + mx = m_nLastX; + } + else + { + dy = 0; + my = m_nLastY; + } + } + else + { + SetCursor( LoadCursor( NULL, IDC_SIZEALL ) ); + } + + RECT rcSamples; + GetSampleTrayRect( rcSamples ); + + int height = rcSamples.bottom - rcSamples.top; + Assert( height > 0 ); + + float dfdx = (float)dx / GetPixelsPerSecond(); + float dfdy = (float)dy / (float)height; + + MoveSelectedSamples( dfdx, dfdy ); + + // Update the scrubber + if ( w2() > 0 ) + { + float t = GetTimeValueForMouse( mx ); + ForceScrubPosition( t ); + g_pMatSysWindow->Frame(); + } + + OffsetRect( &f->m_rcFocus, dx, dy ); + } + break; + case DRAGTYPE_SELECTION: + { + RECT rcFocus; + + rcFocus.left = m_nStartX < m_nLastX ? m_nStartX : m_nLastX; + rcFocus.right = m_nStartX < m_nLastX ? m_nLastX : m_nStartX; + + rcFocus.top = m_nStartY < m_nLastY ? m_nStartY : m_nLastY; + rcFocus.bottom = m_nStartY < m_nLastY ? m_nLastY : m_nStartY; + + POINT offset; + offset.x = 0; + offset.y = 0; + ClientToScreen( (HWND)getHandle(), &offset ); + OffsetRect( &rcFocus, offset.x, offset.y ); + + f->m_rcFocus = rcFocus; + } + break; + } + } + + DrawFocusRect(); + } + else + { + if ( m_hPrevCursor ) + { + SetCursor( m_hPrevCursor ); + m_hPrevCursor = NULL; + } + + if ( IsMouseOverScrubHandle( event ) ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + } + /* + else if ( IsMouseOverTag( mx, my ) ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + } + */ + + // See if anything is selected + if ( CountSelected() <= 0 ) + { + // Nothing selected + // Draw auto highlight + DrawAutoHighlight( event ); + } + } + + m_nLastX = (short)event->x; + m_nLastY = (short)event->y; +} + +int RampTool::handleEvent( mxEvent *event ) +{ + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + int iret = 0; + + if ( HandleToolEvent( event ) ) + { + return iret; + } + + // Give helper a shot at the event + if ( m_pHelper->HelperHandleEvent( event ) ) + { + return 1; + } + + switch ( event->event ) + { + case mxEvent::Size: + { + int w, h; + w = event->width; + h = event->height; + + m_nLastHPixelsNeeded = 0; + InvalidateLayout(); + iret = 1; + } + break; + case mxEvent::MouseWheeled: + { + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( scene ) + { + int tz = g_pChoreoView->GetTimeZoom( GetToolName() ); + bool shiftdown = ( event->modifiers & mxEvent::KeyShift ) ? true : false; + int stepMultipiler = shiftdown ? 5 : 1; + + // Zoom time in / out + if ( event->height > 0 ) + { + tz = min( tz + TIME_ZOOM_STEP * stepMultipiler, MAX_TIME_ZOOM ); + } + else + { + tz = max( tz - TIME_ZOOM_STEP * stepMultipiler, TIME_ZOOM_STEP ); + } + + g_pChoreoView->SetPreservedTimeZoom( this, tz ); + } + + redraw(); + iret = 1; + } + break; + case mxEvent::MouseDown: + { + bool ctrldown = ( event->modifiers & mxEvent::KeyCtrl ) ? true : false; + bool shiftdown = ( event->modifiers & mxEvent::KeyShift ) ? true : false; + + bool rightbutton = ( event->buttons & mxEvent::MouseRightButton ) ? true : false; + + iret = 1; + + int mx = (short)event->x; + int my = (short)event->y; + + SetClickedPos( mx, my ); + + SetMouseOverPos( mx, my ); + DrawMouseOverPos(); + + POINT pt; + pt.x = mx; + pt.y = my; + + RECT rcSamples; + GetSampleTrayRect( rcSamples ); + + bool insamplearea = PtInRect( &rcSamples, pt ) ? true : false; + + if ( m_nDragType == DRAGTYPE_NONE ) + { + bool ctrlDown = ( event->modifiers & mxEvent::KeyCtrl ) ? true : false; + + CExpressionSample *sample = GetSampleUnderMouse( event->x, event->y, ctrlDown ? FP_RT_ADDSAMPLE_TOLERANCE : FP_RT_SELECTION_TOLERANCE ); + + if ( IsMouseOverScrubHandle( event ) ) + { + if ( w2() > 0 ) + { + float t = GetTimeValueForMouse( (short)event->x ); + m_flScrubberTimeOffset = m_flScrub - t; + float maxoffset = 0.5f * (float)SCRUBBER_HANDLE_WIDTH / GetPixelsPerSecond(); + m_flScrubberTimeOffset = clamp( m_flScrubberTimeOffset, -maxoffset, maxoffset ); + t += m_flScrubberTimeOffset; + ForceScrubPosition( t ); + } + + StartDragging( DRAGTYPE_SCRUBBER, m_nClickedX, m_nClickedY, LoadCursor( NULL, IDC_SIZEWE ) ); + } + else if ( insamplearea ) + { + if ( sample ) + { + if ( shiftdown ) + { + sample->selected = !sample->selected; + redraw(); + } + else if ( sample->selected ) + { + PreDataChanged( "move ramp points" ); + + StartDragging( + rightbutton ? DRAGTYPE_MOVEPOINTS_TIME : DRAGTYPE_MOVEPOINTS_VALUE, + m_nClickedX, m_nClickedY, + LoadCursor( NULL, rightbutton ? IDC_SIZEWE : IDC_SIZENS ) ); + } + else + { + if ( !shiftdown ) + { + DeselectAll(); + } + + StartDragging( DRAGTYPE_SELECTION, m_nClickedX, m_nClickedY, LoadCursor( NULL, IDC_ARROW ) ); + } + } + else if ( ctrldown ) + { + CChoreoEvent *e = GetSafeEvent(); + if ( e ) + { + // Add a sample point + float t = GetTimeValueForMouse( mx ); + + t = FacePoser_SnapTime( t ); + float value = 1.0f - (float)( (short)event->y - rcSamples.top ) / (float)( rcSamples.bottom - rcSamples.top ); + value = clamp( value, 0.0f, 1.0f ); + + PreDataChanged( "Add ramp point" ); + + e->AddRamp( t, value, false ); + + e->ResortRamp(); + + PostDataChanged( "Add ramp point" ); + } + } + else + { + if ( event->buttons & mxEvent::MouseRightButton ) + { + ShowContextMenu( event, false ); + iret = 1; + return iret; + } + else + { + if ( !shiftdown ) + { + DeselectAll(); + } + + StartDragging( DRAGTYPE_SELECTION, m_nClickedX, m_nClickedY, LoadCursor( NULL, IDC_ARROW ) ); + } + } + } + else + { + if ( event->buttons & mxEvent::MouseRightButton ) + { + ShowContextMenu( event, false ); + iret = 1; + return iret; + } + else + { + if ( w2() > 0 ) + { + float t = GetTimeValueForMouse( (short)event->x ); + + SetScrubTargetTime( t ); + } + } + } + + CalcBounds( m_nDragType ); + } + } + break; + case mxEvent::MouseDrag: + case mxEvent::MouseMove: + { + int mx = (short)event->x; + int my = (short)event->y; + + SetMouseOverPos( mx, my ); + DrawMouseOverPos(); + + OnMouseMove( event ); + + iret = 1; + } + break; + case mxEvent::MouseUp: + { + OnMouseMove( event ); + + int mx = (short)event->x; + int my = (short)event->y; + + if ( m_nDragType != DRAGTYPE_NONE ) + { + DrawFocusRect(); + } + + if ( m_hPrevCursor ) + { + SetCursor( m_hPrevCursor ); + m_hPrevCursor = 0; + } + + switch ( m_nDragType ) + { + case DRAGTYPE_NONE: + break; + case DRAGTYPE_SCRUBBER: + { + ApplyBounds( mx, my ); + + if ( w2() > 0 ) + { + float t = GetTimeValueForMouse( (short)event->x ); + t += m_flScrubberTimeOffset; + ForceScrubPosition( t ); + m_flScrubberTimeOffset = 0.0f; + } + } + break; + case DRAGTYPE_MOVEPOINTS_VALUE: + case DRAGTYPE_MOVEPOINTS_TIME: + { + PostDataChanged( "move ramp points" ); + } + break; + case DRAGTYPE_SELECTION: + { + SelectPoints(); + } + break; + } + + m_nDragType = DRAGTYPE_NONE; + + SetMouseOverPos( mx, my ); + DrawMouseOverPos(); + + redraw(); + + iret = 1; + } + break; + case mxEvent::Action: + { + iret = 1; + switch ( event->action ) + { + default: + iret = 0; + break; + case IDC_UNDO_RT: + { + OnUndo(); + } + break; + case IDC_REDO_RT: + { + OnRedo(); + } + break; + case IDC_RT_DELETE: + { + Delete(); + } + break; + case IDC_RT_DESELECT: + { + DeselectAll(); + } + break; + case IDC_RT_SELECTALL: + { + SelectAll(); + } + break; + case IDC_RAMPHSCROLL: + { + int offset = 0; + bool processed = true; + + switch ( event->modifiers ) + { + case SB_THUMBTRACK: + offset = event->height; + break; + case SB_PAGEUP: + offset = m_pHorzScrollBar->getValue(); + offset -= 20; + offset = max( offset, m_pHorzScrollBar->getMinValue() ); + break; + case SB_PAGEDOWN: + offset = m_pHorzScrollBar->getValue(); + offset += 20; + offset = min( offset, m_pHorzScrollBar->getMaxValue() ); + break; + case SB_LINEUP: + offset = m_pHorzScrollBar->getValue(); + offset -= 10; + offset = max( offset, m_pHorzScrollBar->getMinValue() ); + break; + case SB_LINEDOWN: + offset = m_pHorzScrollBar->getValue(); + offset += 10; + offset = min( offset, m_pHorzScrollBar->getMaxValue() ); + break; + default: + processed = false; + break; + } + + if ( processed ) + { + MoveTimeSliderToPos( offset ); + } + } + break; + case IDC_RT_CHANGESCALE: + { + OnChangeScale(); + } + break; + case IDC_RT_EDGEPROPERTIES: + { + OnEdgeProperties(); + } + break; + } + } + break; + case mxEvent::KeyDown: + { + iret = 1; + switch ( event->key ) + { + default: + iret = g_pChoreoView->HandleZoomKey( this, event->key ); + break; + case VK_ESCAPE: + DeselectAll(); + break; + case VK_DELETE: + Delete(); + break; + } + } + } + return iret; +} + +void RampTool::ApplyBounds( int& mx, int& my ) +{ + if ( !m_bUseBounds ) + return; + + mx = clamp( mx, m_nMinX, m_nMaxX ); +} + +void RampTool::CalcBounds( int movetype ) +{ + switch ( movetype ) + { + default: + case DRAGTYPE_NONE: + { + m_bUseBounds = false; + m_nMinX = 0; + m_nMaxX = 0; + } + break; + case DRAGTYPE_SCRUBBER: + { + m_bUseBounds = true; + m_nMinX = 0; + m_nMaxX = w2(); + } + break; + } +} + +bool RampTool::PaintBackground() +{ + redraw(); + return false; +} + +void RampTool::OnUndo( void ) +{ + g_pChoreoView->Undo(); +} + +void RampTool::OnRedo( void ) +{ + g_pChoreoView->Redo(); +} + +void RampTool::ForceScrubPositionFromSceneTime( float scenetime ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e || !e->GetDuration() ) + return; + + float t = scenetime - e->GetStartTime(); + m_flScrub = t; + m_flScrubTarget = t; + DrawScrubHandles(); +} + +void RampTool::ForceScrubPosition( float t ) +{ + m_flScrub = t; + m_flScrubTarget = t; + + CChoreoEvent *e = GetSafeEvent(); + if ( e && e->GetDuration() ) + { + float realtime = e->GetStartTime() + t; + + g_pChoreoView->SetScrubTime( realtime ); + g_pChoreoView->SetScrubTargetTime( realtime ); + + g_pChoreoView->DrawScrubHandle(); + } + + DrawScrubHandles(); +} + +void RampTool::SetMouseOverPos( int x, int y ) +{ + m_nMousePos[ 0 ] = x; + m_nMousePos[ 1 ] = y; +} + +void RampTool::GetMouseOverPos( int &x, int& y ) +{ + x = m_nMousePos[ 0 ]; + y = m_nMousePos[ 1 ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rcPos - +//----------------------------------------------------------------------------- +void RampTool::GetMouseOverPosRect( RECT& rcPos ) +{ + rcPos.top = GetCaptionHeight() + 12; + rcPos.left = w2() - 200; + rcPos.right = w2() - 5; + rcPos.bottom = rcPos.top + 13; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rcPos - +//----------------------------------------------------------------------------- +void RampTool::DrawMouseOverPos( CChoreoWidgetDrawHelper& drawHelper, RECT& rcPos ) +{ + // Compute time for pixel x + float t = GetTimeValueForMouse( m_nMousePos[ 0 ] ); + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return; + + t += e->GetStartTime(); + float snapped = FacePoser_SnapTime( t ); + + // Found it, write out description + // + char sz[ 128 ]; + if ( t != snapped ) + { + Q_snprintf( sz, sizeof( sz ), "%s", FacePoser_DescribeSnappedTime( t ) ); + } + else + { + Q_snprintf( sz, sizeof( sz ), "%.3f", t ); + } + + int len = drawHelper.CalcTextWidth( "Arial", 11, 900, sz ); + + RECT rcText = rcPos; + rcText.left = max( rcPos.left, rcPos.right - len ); + + drawHelper.DrawColoredText( "Arial", 11, 900, RGB( 255, 50, 70 ), rcText, sz ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void RampTool::DrawMouseOverPos() +{ + RECT rcPos; + GetMouseOverPosRect( rcPos ); + + CChoreoWidgetDrawHelper drawHelper( this, rcPos ); + DrawMouseOverPos( drawHelper, rcPos ); +} + +void RampTool::AddFocusRect( RECT& rc ) +{ + RECT rcFocus = rc; + + POINT offset; + offset.x = 0; + offset.y = 0; + ClientToScreen( (HWND)getHandle(), &offset ); + OffsetRect( &rcFocus, offset.x, offset.y ); + + // Convert to screen space? + CFocusRect fr; + fr.m_rcFocus = rcFocus; + fr.m_rcOrig = rcFocus; + + m_FocusRects.AddToTail( fr ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rc - +// left - +// right - +//----------------------------------------------------------------------------- +void RampTool::DrawTimeLine( CChoreoWidgetDrawHelper& drawHelper, RECT& rc, float left, float right ) +{ + RECT rcLabel; + float granularity = 0.5f; + + drawHelper.DrawColoredLine( RGB( 150, 150, 200 ), PS_SOLID, 1, rc.left, rc.top + 2, rc.right, rc.top + 2 ); + + float f = SnapTime( left, granularity ); + while ( f < right ) + { + float frac = ( f - left ) / ( right - left ); + if ( frac >= 0.0f && frac <= 1.0f ) + { + rcLabel.left = GetPixelForTimeValue( f ); + rcLabel.top = rc.top + 5; + rcLabel.bottom = rcLabel.top + 10; + + if ( f != left ) + { + drawHelper.DrawColoredLine( RGB( 220, 220, 240 ), PS_DOT, 1, + rcLabel.left, rc.top, rcLabel.left, h2() ); + } + + char sz[ 32 ]; + sprintf( sz, "%.2f", f ); + + int textWidth = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, sz ); + + rcLabel.right = rcLabel.left + textWidth; + + OffsetRect( &rcLabel, -textWidth / 2, 0 ); + + RECT rcOut = rcLabel; + if ( rcOut.left <= 0 ) + { + OffsetRect( &rcOut, -rcOut.left + 2, 0 ); + } + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 0, 50, 150 ), rcOut, sz ); + + } + f += granularity; + } +} + +void RampTool::DrawTimingTags( CChoreoWidgetDrawHelper& drawHelper, RECT& rc ) +{ + CChoreoEvent *rampevent = GetSafeEvent(); + if ( !rampevent ) + return; + + CChoreoScene *scene = rampevent->GetScene(); + if ( !scene ) + return; + + float starttime = GetTimeValueForMouse( 0 ); + float endtime = GetTimeValueForMouse( w2() ); + + if ( endtime - starttime <= 0.0f ) + return; + + RECT rcText = rc; + rcText.bottom = rcText.top + 10; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 0, 100, 200 ), rcText, "Timing Tags:" ); + + // Loop through all events in scene + + int c = scene->GetNumEvents(); + int i; + for ( i = 0; i < c; i++ ) + { + CChoreoEvent *e = scene->GetEvent( i ); + if ( !e ) + continue; + + // See if time overlaps + if ( !e->HasEndTime() ) + continue; + + if ( ( e->GetEndTime() - e->GetStartTime() ) < starttime ) + continue; + + if ( ( e->GetStartTime() - e->GetStartTime() ) > endtime ) + continue; + + if ( e->GetNumRelativeTags() > 0 ) + { + DrawRelativeTagsForEvent( drawHelper, rc, rampevent, e, starttime, endtime ); + } + if ( e->GetNumAbsoluteTags( CChoreoEvent::PLAYBACK ) > 0 ) + { + DrawAbsoluteTagsForEvent( drawHelper, rc, rampevent, e, starttime, endtime ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// &rc - +//----------------------------------------------------------------------------- +void RampTool::DrawAbsoluteTagsForEvent( CChoreoWidgetDrawHelper& drawHelper, RECT &rc, CChoreoEvent *rampevent, CChoreoEvent *event, float starttime, float endtime ) +{ + if ( !event ) + return; + + for ( int i = 0; i < event->GetNumAbsoluteTags( CChoreoEvent::PLAYBACK ); i++ ) + { + CEventAbsoluteTag *tag = event->GetAbsoluteTag( CChoreoEvent::PLAYBACK, i ); + if ( !tag ) + continue; + + float tagtime = ( event->GetStartTime() + tag->GetPercentage() * event->GetDuration() ) - rampevent->GetStartTime(); + if ( tagtime < starttime || tagtime > endtime ) + continue; + + bool clipped = false; + int left = GetPixelForTimeValue( tagtime, &clipped ); + if ( clipped ) + continue; + + // Don't add gesture tags except for the current event + if ( event != rampevent && + event->GetType() == CChoreoEvent::GESTURE ) + { + continue; + } + + COLORREF clr = event == rampevent ? RGB( 0, 100, 250 ) : RGB( 100, 100, 100 ); + + RECT rcMark; + rcMark = rc; + rcMark.top = rc.bottom - 8; + rcMark.bottom = rc.bottom; + rcMark.left = left - 4; + rcMark.right = left + 4; + + drawHelper.DrawTriangleMarker( rcMark, clr ); + + RECT rcText; + rcText = rcMark; + rcText.top -= 12; + + int len = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, tag->GetName() ); + rcText.left = left - len / 2; + rcText.right = rcText.left + len + 2; + + rcText.bottom = rcText.top + 10; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, clr, rcText, tag->GetName() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rc - +//----------------------------------------------------------------------------- +void RampTool::DrawRelativeTagsForEvent( CChoreoWidgetDrawHelper& drawHelper, RECT& rc, CChoreoEvent *rampevent, CChoreoEvent *event, float starttime, float endtime ) +{ + if ( !event ) + return; + + //drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, PEColor( COLOR_PHONEME_TIMING_TAG ), rc, "Timing Tags:" ); + + for ( int i = 0; i < event->GetNumRelativeTags(); i++ ) + { + CEventRelativeTag *tag = event->GetRelativeTag( i ); + if ( !tag ) + continue; + + // + float tagtime = ( event->GetStartTime() + tag->GetPercentage() * event->GetDuration() ) - rampevent->GetStartTime(); + if ( tagtime < starttime || tagtime > endtime ) + continue; + + bool clipped = false; + int left = GetPixelForTimeValue( tagtime, &clipped ); + if ( clipped ) + continue; + + //float frac = ( tagtime - starttime ) / ( endtime - starttime ); + + //int left = rc.left + (int)( frac * ( float )( rc.right - rc.left ) + 0.5f ); + + COLORREF clr = event == rampevent ? RGB( 0, 100, 250 ) : RGB( 100, 100, 100 ); + + RECT rcMark; + rcMark = rc; + rcMark.top = rc.bottom - 8; + rcMark.bottom = rc.bottom; + rcMark.left = left - 4; + rcMark.right = left + 4; + + drawHelper.DrawTriangleMarker( rcMark, clr ); + + RECT rcText; + rcText = rc; + rcText.bottom = rc.bottom - 10; + rcText.top = rcText.bottom - 10; + + int len = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, tag->GetName() ); + rcText.left = left - len / 2; + rcText.right = rcText.left + len + 2; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, clr, rcText, tag->GetName() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int RampTool::ComputeHPixelsNeeded( void ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return 0; + + int pixels = 0; + float maxtime = event->GetDuration(); + pixels = (int)( ( maxtime ) * GetPixelsPerSecond() + 10 ); + + return pixels; + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void RampTool::RepositionHSlider( void ) +{ + int pixelsneeded = ComputeHPixelsNeeded(); + + if ( pixelsneeded <= w2() - 10 ) + { + m_pHorzScrollBar->setVisible( false ); + } + else + { + m_pHorzScrollBar->setVisible( true ); + } + m_pHorzScrollBar->setBounds( 0, h2() - m_nScrollbarHeight, w2() - m_nScrollbarHeight, m_nScrollbarHeight ); + + m_flLeftOffset = max( 0.f, m_flLeftOffset ); + m_flLeftOffset = min( (float)pixelsneeded, m_flLeftOffset ); + + m_pHorzScrollBar->setRange( 0, pixelsneeded ); + m_pHorzScrollBar->setValue( (int)m_flLeftOffset ); + m_pHorzScrollBar->setPagesize( w2() - 10 ); + + m_nLastHPixelsNeeded = pixelsneeded; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float RampTool::GetPixelsPerSecond( void ) +{ + return m_flPixelsPerSecond * (float)g_pChoreoView->GetTimeZoom( GetToolName() ) / 100.0f; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : x - +//----------------------------------------------------------------------------- +void RampTool::MoveTimeSliderToPos( int x ) +{ + m_flLeftOffset = (float)x; + m_pHorzScrollBar->setValue( (int)m_flLeftOffset ); + InvalidateRect( (HWND)m_pHorzScrollBar->getHandle(), NULL, TRUE ); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void RampTool::InvalidateLayout( void ) +{ + if ( m_bSuppressLayout ) + return; + + if ( ComputeHPixelsNeeded() != m_nLastHPixelsNeeded ) + { + RepositionHSlider(); + } + + m_bLayoutIsValid = false; + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : st - +// ed - +//----------------------------------------------------------------------------- +void RampTool::GetStartAndEndTime( float& st, float& ed ) +{ + st = m_flLeftOffset / GetPixelsPerSecond(); + ed = st + (float)w2() / GetPixelsPerSecond(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : float +//----------------------------------------------------------------------------- +float RampTool::GetEventEndTime() +{ + CChoreoEvent *ev = GetSafeEvent(); + if ( !ev ) + return 1.0f; + + return ev->GetDuration(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : time - +// *clipped - +// Output : int +//----------------------------------------------------------------------------- +int RampTool::GetPixelForTimeValue( float time, bool *clipped /*=NULL*/ ) +{ + if ( clipped ) + { + *clipped = false; + } + + float st, ed; + GetStartAndEndTime( st, ed ); + + float frac = ( time - st ) / ( ed - st ); + if ( frac < 0.0 || frac > 1.0 ) + { + if ( clipped ) + { + *clipped = true; + } + } + + int pixel = ( int )( frac * w2() ); + return pixel; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// clip - +// Output : float +//----------------------------------------------------------------------------- +float RampTool::GetTimeValueForMouse( int mx, bool clip /*=false*/) +{ + float st, ed; + GetStartAndEndTime( st, ed ); + + if ( clip ) + { + if ( mx < 0 ) + { + return st; + } + if ( mx > w2() ) + { + return ed; + } + } + + float frac = (float)( mx ) / (float)( w2() ); + return st + frac * ( ed - st ); +} + +void RampTool::OnChangeScale( void ) +{ + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( !scene ) + { + return; + } + + // Zoom time in / out + CInputParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Change Zoom" ); + strcpy( params.m_szPrompt, "New scale (e.g., 2.5x):" ); + + Q_snprintf( params.m_szInputText, sizeof( params.m_szInputText ), "%.2f", (float)g_pChoreoView->GetTimeZoom( GetToolName() ) / 100.0f ); + + if ( !InputProperties( ¶ms ) ) + return; + + g_pChoreoView->SetTimeZoom( GetToolName(), clamp( (int)( 100.0f * atof( params.m_szInputText ) ), 1, MAX_TIME_ZOOM ), false ); + + m_nLastHPixelsNeeded = -1; + m_flLeftOffset= 0.0f; + InvalidateLayout(); + Con_Printf( "Zoom factor %i %%\n", g_pChoreoView->GetTimeZoom( GetToolName() ) ); +} + +void RampTool::DrawEventEnd( CChoreoWidgetDrawHelper& drawHelper ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return; + + float duration = e->GetDuration(); + if ( !duration ) + return; + + int leftx = GetPixelForTimeValue( duration ); + if ( leftx >= w2() ) + return; + + RECT rcSample; + GetSampleTrayRect( rcSample ); + + drawHelper.DrawColoredLine( + COLOR_CHOREO_ENDTIME, PS_SOLID, 1, + leftx, rcSample.top, leftx, rcSample.bottom ); +} + +void RampTool::GetSampleTrayRect( RECT& rc ) +{ + rc.left = 0; + rc.right = w2(); + rc.top = GetCaptionHeight() + 65; + + rc.bottom = h2() - m_nScrollbarHeight-2; +} + +void RampTool::DrawSamplesSimple( CChoreoWidgetDrawHelper& drawHelper, CChoreoEvent *e, bool clearbackground, COLORREF sampleColor, RECT &rcSamples ) +{ + if ( clearbackground ) + { + drawHelper.DrawFilledRect( RGB( 230, 230, 215 ), rcSamples ); + } + + if ( !e ) + return; + + float starttime = e->GetStartTime(); + + COLORREF lineColor = sampleColor; + + int width = rcSamples.right - rcSamples.left; + if ( width <= 0.0f ) + return; + + int height = rcSamples.bottom - rcSamples.top; + int bottom = rcSamples.bottom; + + float timestepperpixel = e->GetDuration() / (float)width; + + float prev_value = e->GetIntensity( starttime ); + int prev_x = rcSamples.left; + float prev_t = 0.0f; + + for ( float x = rcSamples.left; x < rcSamples.right; x+=3 ) + { + float t = (float)( x - rcSamples.left ) * timestepperpixel; + + float value = e->GetIntensity( starttime + t ); + + // Draw segment + drawHelper.DrawColoredLine( lineColor, PS_SOLID, 1, + prev_x, bottom - prev_value * height, + x, bottom - value * height ); + + prev_x = x; + prev_t = t; + prev_value = value; + } +} + +void RampTool::DrawSamples( CChoreoWidgetDrawHelper& drawHelper, RECT &rcSamples ) +{ + drawHelper.DrawFilledRect( RGB( 230, 230, 215 ), rcSamples ); + + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return; + + int rampCount = e->GetRampCount(); + if ( !rampCount ) + return; + + float starttime; + float endtime; + + GetStartAndEndTime( starttime, endtime ); + + COLORREF lineColor = RGB( 0, 0, 255 ); + COLORREF dotColor = RGB( 0, 0, 255 ); + COLORREF dotColorSelected = RGB( 240, 80, 20 ); + COLORREF shadowColor = RGB( 150, 150, 250 ); + + int height = rcSamples.bottom - rcSamples.top; + int bottom = rcSamples.bottom; + int top = rcSamples.top; + + float timestepperpixel = 1.0f / GetPixelsPerSecond(); + + float stoptime = min( endtime, e->GetDuration() ); + + float prev_t = starttime; + float prev_value = e->GetIntensity( prev_t ); + + if ( 0 ) + { + COLORREF shadowColor = RGB( 150, 150, 250 ); + + // draw hermite version of time step + float i0, i1, i2; + float time10hz = starttime; + + i0 = e->GetIntensity( time10hz + e->GetStartTime() ); + i1 = i0; + time10hz = starttime + 0.1; + i2 = e->GetIntensity( time10hz + e->GetStartTime() );; + + for ( float t = starttime-timestepperpixel; t <= stoptime; t += timestepperpixel ) + { + while (t >= time10hz) + { + time10hz += 0.1; + i0 = i1; + i1 = i2; + i2 = e->GetIntensity( time10hz + e->GetStartTime() ); + + bool clipped; + int x = GetPixelForTimeValue( time10hz, &clipped ); + int y = bottom - i2 * height; + int dotsize = 4; + + drawHelper.DrawCircle( + shadowColor, + x, y, + dotsize, + false ); + } + + float value = Hermite_Spline( i0, i1, i2, (t - time10hz + 0.1) / 0.1 ); + + int prevx, x; + + bool clipped1, clipped2; + x = GetPixelForTimeValue( t, &clipped1 ); + prevx = GetPixelForTimeValue( prev_t, &clipped2 ); + + if ( !clipped1 && !clipped2 ) + { + // Draw segment + drawHelper.DrawColoredLine( shadowColor, PS_SOLID, 1, + prevx, clamp( bottom - prev_value * height, top, bottom ), + x, clamp( bottom - value * height, top, bottom ) ); + } + + prev_t = t; + prev_value = value; + } + } + + + + for ( float t = starttime-timestepperpixel; t <= stoptime; t += timestepperpixel ) + { + float value = e->GetIntensity( t + e->GetStartTime() ); + + int prevx, x; + + bool clipped1, clipped2; + x = GetPixelForTimeValue( t, &clipped1 ); + prevx = GetPixelForTimeValue( prev_t, &clipped2 ); + + if ( !clipped1 && !clipped2 ) + { + // Draw segment + drawHelper.DrawColoredLine( lineColor, PS_SOLID, 1, + prevx, bottom - prev_value * height, + x, bottom - value * height ); + } + + prev_t = t; + prev_value = value; + + } + + for ( int sample = 0; sample < rampCount; sample++ ) + { + CExpressionSample *start = e->GetRamp( sample ); + + /* + int pixel = (int)( ( start->time / event_time ) * width + 0.5f); + int x = m_rcBounds.left + pixel; + float roundedfrac = (float)pixel / (float)width; + */ + float value = start->value; + bool clipped = false; + int x = GetPixelForTimeValue( start->time, &clipped ); + if ( clipped ) + continue; + int y = bottom - value * height; + + int dotsize = 6; + int dotSizeSelected = 6; + + COLORREF clr = dotColor; + COLORREF clrSelected = dotColorSelected; + + drawHelper.DrawCircle( + start->selected ? clrSelected : clr, + x, y, + start->selected ? dotSizeSelected : dotsize, + true ); + + if ( !start->selected ) + continue; + + if ( start->GetCurveType() == CURVE_DEFAULT ) + continue; + + // Draw curve type indicator... + char sz[ 128 ]; + Q_snprintf( sz, sizeof( sz ), "%s", Interpolator_NameForCurveType( start->GetCurveType(), true ) ); + RECT rc; + int fontSize = 9; + rc.top = clamp( y + 5, rcSamples.top + 2, rcSamples.bottom - 2 - fontSize ); + rc.bottom = rc.top + fontSize + 1; + rc.left = x - 75; + rc.right = x + 175; + drawHelper.DrawColoredText( "Arial", fontSize, 500, shadowColor, rc, sz ); + } +} + +void RampTool::DrawAutoHighlight( mxEvent *event ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return; + + CExpressionSample *hover = GetSampleUnderMouse( event->x, event->y, 0.0f ); + RECT rcSamples; + GetSampleTrayRect( rcSamples ); + + CChoreoWidgetDrawHelper drawHelper( this, rcSamples, true ); + + RECT rcClient = rcSamples; + + COLORREF dotColor = RGB( 0, 0, 255 ); + COLORREF dotColorSelected = RGB( 240, 80, 20 ); + COLORREF clrHighlighted = RGB( 0, 200, 0 ); + + int height = rcClient.bottom - rcClient.top; + int bottom = rcClient.bottom; + + int dotsize = 6; + int dotSizeSelected = 6; + int dotSizeHighlighted = 6; + + COLORREF clr = dotColor; + COLORREF clrSelected = dotColorSelected; + COLORREF bgColor = RGB( 230, 230, 200 ); + + // Fixme, could look at 1st derivative and do more sampling at high rate of change? + // or near actual sample points! + int sampleCount = e->GetRampCount(); + for ( int sample = 0; sample < sampleCount; sample++ ) + { + CExpressionSample *start = e->GetRamp( sample ); + + float value = start->value; + bool clipped = false; + int x = GetPixelForTimeValue( start->time, &clipped ); + if ( clipped ) + continue; + int y = bottom - value * height; + + if ( hover == start ) + { + drawHelper.DrawCircle( + bgColor, + x, y, + dotSizeHighlighted, + true ); + + drawHelper.DrawCircle( + clrHighlighted, + x, y, + dotSizeHighlighted, + false ); + + + } + else + { + drawHelper.DrawCircle( + start->selected ? clrSelected : clr, + x, y, + start->selected ? dotSizeSelected : dotsize, + true ); + } + } +} + +int RampTool::NumSamples() +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return 0; + + return e->GetRampCount(); +} + +CExpressionSample *RampTool::GetSample( int idx ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return NULL; + + return e->GetRamp( idx ); +} + +CExpressionSample *RampTool::GetSampleUnderMouse( int mx, int my, float tolerance /*= FP_RT_SELECTION_TOLERANCE*/ ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return NULL; + + RECT rcSamples; + GetSampleTrayRect( rcSamples ); + + POINT pt; + pt.x = mx; + pt.y = my; + + if ( !PtInRect( &rcSamples, pt ) ) + return NULL; + + pt.y -= rcSamples.top; + + float closest_dist = 9999999.f; + CExpressionSample *bestsample = NULL; + + int height = rcSamples.bottom - rcSamples.top; + + for ( int i = 0; i < e->GetRampCount(); i++ ) + { + CExpressionSample *sample = e->GetRamp( i ); + Assert( sample ); + + bool clipped = false; + int px = GetPixelForTimeValue( sample->time, &clipped ); + int py = height * ( 1.0f - sample->value ); + + int dx = px - pt.x; + int dy = py - pt.y; + + float dist = sqrt( (float)(dx * dx + dy * dy) ); + + if ( dist < closest_dist ) + { + bestsample = sample; + closest_dist = dist; + } + + } + + // Not close to any of them!!! + if ( ( tolerance != 0.0f ) && + ( closest_dist > tolerance ) ) + return NULL; + + return bestsample; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void RampTool::SelectPoints( void ) +{ + RECT rcSelection; + + rcSelection.left = m_nStartX < m_nLastX ? m_nStartX : m_nLastX; + rcSelection.right = m_nStartX < m_nLastX ? m_nLastX : m_nStartX; + + rcSelection.top = m_nStartY < m_nLastY ? m_nStartY : m_nLastY; + rcSelection.bottom = m_nStartY < m_nLastY ? m_nLastY : m_nStartY; + + int selW = rcSelection.right - rcSelection.left; + int selH = rcSelection.bottom - rcSelection.top; + + float tolerance = FP_RT_SELECTION_RECTANGLE_TOLERANCE; + // If they are just clicking and releasing in one spot, capture any items w/in a larger tolerance + if ( selW <= 2 && selH <= 2 ) + { + tolerance = FP_RT_SELECTION_TOLERANCE; + + CExpressionSample *sample = GetSampleUnderMouse( rcSelection.left + selW * 0.5f, rcSelection.top + selH * 0.5f ); + if ( sample ) + { + sample->selected = true; + return; + } + } + else + { + InflateRect( &rcSelection, 3, 3 ); + } + + RECT rcSamples; + GetSampleTrayRect( rcSamples ); + + int height = rcSamples.bottom - rcSamples.top; + + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return; + + float duration = e->GetDuration(); + + float fleft = (float)GetTimeValueForMouse( rcSelection.left ); + float fright = (float)GetTimeValueForMouse( rcSelection.right ); + + //fleft *= duration; + //fright *= duration; + + float ftop = (float)( rcSelection.top - rcSamples.top ) / (float)height; + float fbottom = (float)( rcSelection.bottom - rcSamples.top ) / (float)height; + + fleft = clamp( fleft, 0.0f, duration ); + fright = clamp( fright, 0.0f, duration ); + ftop = clamp( ftop, 0.0f, 1.0f ); + fbottom = clamp( fbottom, 0.0f, 1.0f ); + + float timestepperpixel = 1.0f / GetPixelsPerSecond(); + float yfracstepperpixel = 1.0f / (float)height; + + float epsx = tolerance*timestepperpixel; + float epsy = tolerance*yfracstepperpixel; + + for ( int i = 0; i < e->GetRampCount(); i++ ) + { + CExpressionSample *sample = e->GetRamp( i ); + + if ( sample->time + epsx < fleft ) + continue; + + if ( sample->time - epsx > fright ) + continue; + + if ( (1.0f - sample->value ) + epsy < ftop ) + continue; + + if ( (1.0f - sample->value ) - epsy > fbottom ) + continue; + + sample->selected = true; + } + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int RampTool::CountSelected( void ) +{ + return m_pHelper->CountSelected( false ); +} + +void RampTool::MoveSelectedSamples( float dfdx, float dfdy ) +{ + int selecteditems = CountSelected(); + if ( !selecteditems ) + return; + + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return; + + int c = e->GetRampCount(); + + float duration = e->GetDuration(); + //dfdx *= duration; + + for ( int i = 0; i < c; i++ ) + { + CExpressionSample *sample = e->GetRamp( i ); + if ( !sample || !sample->selected ) + continue; + + sample->time += dfdx; + sample->time = clamp( sample->time, 0.0f, duration ); + + sample->value -= dfdy; + sample->value = clamp( sample->value, 0.0f, 1.0f ); + } + + e->ResortRamp(); + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void RampTool::DeselectAll( void ) +{ + int i; + + int selecteditems = CountSelected(); + if ( !selecteditems ) + return; + + CChoreoEvent *e = GetSafeEvent(); + Assert( e ); + if ( !e ) + return; + + for ( i = e->GetRampCount() - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = e->GetRamp( i ); + sample->selected = false; + } + + redraw(); +} + +void RampTool::SelectAll( void ) +{ + int i; + + CChoreoEvent *e = GetSafeEvent(); + Assert( e ); + if ( !e ) + return; + + for ( i = e->GetRampCount() - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = e->GetRamp( i ); + sample->selected = true; + } + + redraw(); +} + +void RampTool::Delete( void ) +{ + int i; + + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return; + + int selecteditems = CountSelected(); + if ( !selecteditems ) + return; + + PreDataChanged( "Delete ramp points" ); + + for ( i = e->GetRampCount() - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = e->GetRamp( i ); + if ( !sample->selected ) + continue; + + e->DeleteRamp( i ); + } + + PostDataChanged( "Delete ramp points" ); +} + +void RampTool::OnModelChanged() +{ + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *undodescription - +//----------------------------------------------------------------------------- +void RampTool::PreDataChanged( char const *undodescription ) +{ + if ( m_nUndoSetup == 0 ) + { + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( undodescription ); + } + ++m_nUndoSetup; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *redodescription - +//----------------------------------------------------------------------------- +void RampTool::PostDataChanged( char const *redodescription ) +{ + --m_nUndoSetup; + if ( m_nUndoSetup == 0 ) + { + g_pChoreoView->PushRedo( redodescription ); + redraw(); + } +} + + +void RampTool::SetMousePositionForEvent( mxEvent *event ) +{ + POINT pt; + GetCursorPos( &pt ); + ScreenToClient( (HWND)getHandle(), &pt ); + + event->x = pt.x; + event->y = pt.y; +} + +void RampTool::OnEdgeProperties() +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return; + + CEdgePropertiesParams params; + Q_memset( ¶ms, 0, sizeof( params ) ); + Q_strcpy( params.m_szDialogTitle, "Edge Properties" ); + + params.SetFromCurve( e->GetRamp() ); + + if ( !EdgeProperties( ¶ms ) ) + { + return; + } + + char *undotext = "Change Event Ramp Edge Properties"; + + PreDataChanged( undotext ); + + // Apply changes. + params.ApplyToCurve( e->GetRamp() ); + + PostDataChanged( undotext ); +} + +void RampTool::GetWorkList( bool reflect, CUtlVector< RampTool * >& list ) +{ + NOTE_UNUSED( reflect ); + list.AddToTail( this ); +} diff --git a/utils/hlfaceposer/RampTool.h b/utils/hlfaceposer/RampTool.h new file mode 100644 index 0000000..b4fc9de --- /dev/null +++ b/utils/hlfaceposer/RampTool.h @@ -0,0 +1,205 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef RAMPTOOL_H +#define RAMPTOOL_H +#ifdef _WIN32 +#pragma once +#endif + +#include <mxtk/mx.h> +#include "studio.h" +#include "utlvector.h" +#include "faceposertoolwindow.h" + +class CChoreoEvent; +class CChoreoWidgetDrawHelper; +class CChoreoView; +struct CExpressionSample; + +#define IDC_REDO_RT 1000 +#define IDC_UNDO_RT 1001 + +#define IDC_RT_DELETE 1002 +#define IDC_RT_DESELECT 1003 +#define IDC_RT_SELECTALL 1004 + +#define IDC_RT_CHANGESCALE 1005 +#define IDC_RAMPHSCROLL 1006 +#define IDC_RT_EDGEPROPERTIES 1007 + +#define FP_RT_SELECTION_TOLERANCE 30.0f +#define FP_RT_SELECTION_RECTANGLE_TOLERANCE 5.0f +#define FP_RT_ADDSAMPLE_TOLERANCE 5.0f + +template< class T > class CCurveEditorHelper; + +class RampTool : public mxWindow, public IFacePoserToolWindow +{ +public: + // Construction + RampTool( mxWindow *parent ); + ~RampTool( void ); + + virtual void Think( float dt ); + void ScrubThink( float dt, bool scrubbing ); + virtual bool IsScrubbing( void ) const; + virtual bool IsProcessing( void ); + + virtual int handleEvent( mxEvent *event ); + virtual void redraw( void ); + virtual bool PaintBackground(); + + void SetEvent( CChoreoEvent *event ); + + void GetScrubHandleRect( RECT& rcHandle, float scrub, bool clipped = false ); + + void DrawScrubHandle( CChoreoWidgetDrawHelper& drawHelper, RECT& rcHandle, float scrub, bool reference ); + void DrawTimeLine( CChoreoWidgetDrawHelper& drawHelper, RECT& rc, float left, float right ); + void DrawEventEnd( CChoreoWidgetDrawHelper& drawHelper ); + + void SetMouseOverPos( int x, int y ); + void GetMouseOverPos( int &x, int& y ); + void GetMouseOverPosRect( RECT& rcPos ); + void DrawMouseOverPos( CChoreoWidgetDrawHelper& drawHelper, RECT& rcPos ); + void DrawMouseOverPos(); + + void DrawScrubHandles(); + + CChoreoEvent *GetSafeEvent( void ); + + bool IsMouseOverScrubHandle( mxEvent *event ); + void ForceScrubPosition( float newtime ); + void ForceScrubPositionFromSceneTime( float scenetime ); + + void SetScrubTime( float t ); + void SetScrubTargetTime( float t ); + + void DrawSamplesSimple( CChoreoWidgetDrawHelper& drawHelper, CChoreoEvent *e, bool clearbackground, COLORREF sampleColor, RECT &rcSamples ); + virtual void OnModelChanged(); + + void SetMousePositionForEvent( mxEvent *event ); + + int NumSamples(); + CExpressionSample *GetSample( int idx ); + void PreDataChanged( char const *undodescription ); + void PostDataChanged( char const *redodescription ); + CExpressionSample *GetSampleUnderMouse( int mx, int my, float tolerance = FP_RT_SELECTION_TOLERANCE ); + void GetWorkList( bool reflect, CUtlVector< RampTool * >& list ); + +private: + + void GetSampleTrayRect( RECT& rc ); + void DrawSamples( CChoreoWidgetDrawHelper& drawHelper, RECT &rcSamples ); + + void SelectPoints( void ); + void DeselectAll(); + void SelectAll(); + void Delete( void ); + + int CountSelected( void ); + void MoveSelectedSamples( float dfdx, float dfdy ); + + void StartDragging( int dragtype, int startx, int starty, HCURSOR cursor ); + void AddFocusRect( RECT& rc ); + void OnMouseMove( mxEvent *event ); + void DrawFocusRect( void ); + void ShowContextMenu( mxEvent *event, bool include_track_menus ); + void GetWorkspaceLeftRight( int& left, int& right ); + void SetClickedPos( int x, int y ); + float GetTimeForClickedPos( void ); + + void DrawAutoHighlight( mxEvent *event ); + + void ApplyBounds( int& mx, int& my ); + void CalcBounds( int movetype ); + void OnUndo( void ); + void OnRedo( void ); + + //CEventAbsoluteTag *IsMouseOverTag( int mx, int my ); + void OnRevert( void ); + + void OnEdgeProperties(); + + void DrawTimingTags( CChoreoWidgetDrawHelper& drawHelper, RECT& rc ); + void DrawRelativeTagsForEvent( CChoreoWidgetDrawHelper& drawHelper, RECT& rc, CChoreoEvent *rampevent, CChoreoEvent *event, float starttime, float endtime ); + void DrawAbsoluteTagsForEvent( CChoreoWidgetDrawHelper& drawHelper, RECT &rc, CChoreoEvent *rampevent, CChoreoEvent *event, float starttime, float endtime ); + + + // Readjust slider + void MoveTimeSliderToPos( int x ); + void OnChangeScale(); + int ComputeHPixelsNeeded( void ); + float GetPixelsPerSecond( void ); + void InvalidateLayout( void ); + void RepositionHSlider( void ); + void GetStartAndEndTime( float& st, float& ed ); + float GetEventEndTime(); + float GetTimeValueForMouse( int mx, bool clip = false ); + int GetPixelForTimeValue( float time, bool *clipped = NULL ); + + float m_flScrub; + float m_flScrubTarget; + + enum + { + DRAGTYPE_NONE = 0, + DRAGTYPE_SCRUBBER, + DRAGTYPE_MOVEPOINTS_VALUE, + DRAGTYPE_MOVEPOINTS_TIME, + DRAGTYPE_SELECTION, + }; + + int m_nFocusEventGlobalID; + + int m_nMousePos[ 2 ]; + + bool m_bUseBounds; + int m_nMinX; + int m_nMaxX; + + HCURSOR m_hPrevCursor; + int m_nDragType; + + int m_nStartX; + int m_nStartY; + int m_nLastX; + int m_nLastY; + + int m_nClickedX; + int m_nClickedY; + + struct CFocusRect + { + RECT m_rcOrig; + RECT m_rcFocus; + }; + CUtlVector < CFocusRect > m_FocusRects; + CChoreoEvent *m_pLastEvent; + + bool m_bSuppressLayout; + // Height/width of scroll bars + int m_nScrollbarHeight; + float m_flLeftOffset; + mxScrollbar *m_pHorzScrollBar; + int m_nLastHPixelsNeeded; + // How many pixels per second we are showing in the UI + float m_flPixelsPerSecond; + // Do we need to move controls? + bool m_bLayoutIsValid; + float m_flLastDuration; + bool m_bInSetEvent; + float m_flScrubberTimeOffset; + int m_nUndoSetup; + + CCurveEditorHelper< RampTool > *m_pHelper; + friend class CChoreoView; +}; + +extern RampTool *g_pRampTool; + +#endif // RAMPTOOL_H diff --git a/utils/hlfaceposer/SceneRampTool.cpp b/utils/hlfaceposer/SceneRampTool.cpp new file mode 100644 index 0000000..4cedbdb --- /dev/null +++ b/utils/hlfaceposer/SceneRampTool.cpp @@ -0,0 +1,2244 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include <stdio.h> +#include "hlfaceposer.h" +#include "SceneRampTool.h" +#include "mdlviewer.h" +#include "choreowidgetdrawhelper.h" +#include "TimelineItem.h" +#include "expressions.h" +#include "expclass.h" +#include "choreoevent.h" +#include "StudioModel.h" +#include "choreoscene.h" +#include "choreoactor.h" +#include "choreochannel.h" +#include "ChoreoView.h" +#include "InputProperties.h" +#include "ControlPanel.h" +#include "FlexPanel.h" +#include "mxExpressionTray.h" +#include "ExpressionProperties.h" +#include "tier1/strtools.h" +#include "faceposer_models.h" +#include "UtlBuffer.h" +#include "filesystem.h" +#include "iscenetokenprocessor.h" +#include "choreoviewcolors.h" +#include "MatSysWin.h" +#include "curveeditorhelpers.h" +#include "EdgeProperties.h" + +SceneRampTool *g_pSceneRampTool = 0; + +#define TRAY_HEIGHT 20 +#define TRAY_ITEM_INSET 10 + +#define TAG_TOP ( TRAY_HEIGHT + 12 ) +#define TAG_BOTTOM ( TAG_TOP + 20 ) + +#define MAX_TIME_ZOOM 1000 +// 10% per step +#define TIME_ZOOM_STEP 2 + +SceneRampTool::SceneRampTool( mxWindow *parent ) +: IFacePoserToolWindow( "SceneRampTool", "Scene Ramp" ), mxWindow( parent, 0, 0, 0, 0 ) +{ + m_pHelper = new CCurveEditorHelper< SceneRampTool >( this ); + + m_bSuppressLayout = false; + + SetAutoProcess( true ); + + m_flScrub = 0.0f; + m_flScrubTarget = 0.0f; + m_nDragType = DRAGTYPE_NONE; + + m_nClickedX = 0; + m_nClickedY = 0; + + m_hPrevCursor = 0; + + m_nStartX = 0; + m_nStartY = 0; + + m_nMousePos[ 0 ] = m_nMousePos[ 1 ] = 0; + + m_nMinX = 0; + m_nMaxX = 0; + m_bUseBounds = false; + + m_bLayoutIsValid = false; + m_flPixelsPerSecond = 500.0f; + + m_flLastDuration = 0.0f; + m_nScrollbarHeight = 12; + m_flLeftOffset = 0.0f; + m_nLastHPixelsNeeded = -1; + m_pHorzScrollBar = new mxScrollbar( this, 0, 0, 18, 100, IDC_SRT_RAMPHSCROLL, mxScrollbar::Horizontal ); + m_pHorzScrollBar->setVisible( false ); + + m_flScrubberTimeOffset = 0.0f; + + m_nUndoSetup = 0; +} + +SceneRampTool::~SceneRampTool( void ) +{ + delete m_pHelper; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CChoreoScene *SceneRampTool::GetSafeScene( void ) +{ + if ( !g_pChoreoView ) + return NULL; + + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( !scene ) + return NULL; + + return scene; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rcHandle - +//----------------------------------------------------------------------------- +void SceneRampTool::GetScrubHandleRect( RECT& rcHandle, float scrub, bool clipped ) +{ + float pixel = 0.0f; + if ( w2() > 0 ) + { + pixel = GetPixelForTimeValue( scrub ); + + if ( clipped ) + { + pixel = clamp( pixel, SCRUBBER_HANDLE_WIDTH / 2, w2() - SCRUBBER_HANDLE_WIDTH / 2 ); + } + } + + rcHandle.left = pixel- SCRUBBER_HANDLE_WIDTH / 2; + rcHandle.right = pixel + SCRUBBER_HANDLE_WIDTH / 2; + rcHandle.top = 2 + GetCaptionHeight(); + rcHandle.bottom = rcHandle.top + SCRUBBER_HANDLE_HEIGHT; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rcHandle - +//----------------------------------------------------------------------------- +void SceneRampTool::DrawScrubHandle( CChoreoWidgetDrawHelper& drawHelper, RECT& rcHandle, float scrub, bool reference ) +{ + HBRUSH br = CreateSolidBrush( reference ? RGB( 150, 0, 0 ) : RGB( 0, 150, 100 ) ); + + COLORREF areaBorder = RGB( 230, 230, 220 ); + + drawHelper.DrawColoredLine( areaBorder, + PS_SOLID, 1, 0, rcHandle.top, w2(), rcHandle.top ); + drawHelper.DrawColoredLine( areaBorder, + PS_SOLID, 1, 0, rcHandle.bottom, w2(), rcHandle.bottom ); + + drawHelper.DrawFilledRect( br, rcHandle ); + + // + char sz[ 32 ]; + sprintf( sz, "%.3f", scrub ); + + CChoreoScene *scene = GetSafeScene(); + if ( scene ) + { + float st, ed; + st = 0.0f; + ed = scene->FindStopTime(); + + float dt = ed - st; + if ( dt > 0.0f ) + { + sprintf( sz, "%.3f", st + scrub ); + } + } + + int len = drawHelper.CalcTextWidth( "Arial", 9, 500, sz ); + + RECT rcText = rcHandle; + + int textw = rcText.right - rcText.left; + + rcText.left += ( textw - len ) / 2; + + drawHelper.DrawColoredText( "Arial", 9, 500, RGB( 255, 255, 255 ), rcText, sz ); + + DeleteObject( br ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool SceneRampTool::IsMouseOverScrubHandle( mxEvent *event ) +{ + RECT rcHandle; + GetScrubHandleRect( rcHandle, m_flScrub, true ); + InflateRect( &rcHandle, 2, 2 ); + + POINT pt; + pt.x = (short)event->x; + pt.y = (short)event->y; + if ( PtInRect( &rcHandle, pt ) ) + { + return true; + } + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool SceneRampTool::IsProcessing( void ) +{ + if ( !GetSafeScene() ) + return false; + + if ( m_flScrub != m_flScrubTarget ) + return true; + + return false; +} + +bool SceneRampTool::IsScrubbing( void ) const +{ + bool scrubbing = ( m_nDragType == DRAGTYPE_SCRUBBER ) ? true : false; + return scrubbing; +} + +void SceneRampTool::SetScrubTime( float t ) +{ + m_flScrub = t; + CChoreoScene *scene = GetSafeScene(); + if ( scene && scene->FindStopTime() ) + { + float realtime = m_flScrub; + + g_pChoreoView->SetScrubTime( realtime ); + g_pChoreoView->DrawScrubHandle(); + } +} + +void SceneRampTool::SetScrubTargetTime( float t ) +{ + m_flScrubTarget = t; + CChoreoScene *scene = GetSafeScene(); + if ( scene && scene->FindStopTime() ) + { + float realtime = m_flScrubTarget; + + g_pChoreoView->SetScrubTargetTime( realtime ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dt - +//----------------------------------------------------------------------------- +void SceneRampTool::Think( float dt ) +{ + if ( !GetSafeScene() ) + return; + + bool scrubbing = IsScrubbing(); + ScrubThink( dt, scrubbing ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dt - +//----------------------------------------------------------------------------- +void SceneRampTool::ScrubThink( float dt, bool scrubbing ) +{ + if ( !GetSafeScene() ) + return; + + if ( m_flScrubTarget == m_flScrub && !scrubbing ) + return; + + float d = m_flScrubTarget - m_flScrub; + int sign = d > 0.0f ? 1 : -1; + + float maxmove = dt; + + if ( sign > 0 ) + { + if ( d < maxmove ) + { + SetScrubTime( m_flScrubTarget ); + } + else + { + SetScrubTime( m_flScrub + maxmove ); + } + } + else + { + if ( -d < maxmove ) + { + SetScrubTime( m_flScrubTarget ); + } + else + { + SetScrubTime( m_flScrub - maxmove ); + } + } + + if ( scrubbing ) + { + g_pMatSysWindow->Frame(); + } +} + +void SceneRampTool::DrawScrubHandles() +{ + RECT rcTray; + + RECT rcHandle; + GetScrubHandleRect( rcHandle, m_flScrub, true ); + + rcTray = rcHandle; + rcTray.left = 0; + rcTray.right = w2(); + + CChoreoWidgetDrawHelper drawHelper( this, rcTray ); + DrawScrubHandle( drawHelper, rcHandle, m_flScrub, false ); +} + +void SceneRampTool::redraw() +{ + if ( !ToolCanDraw() ) + return; + + CChoreoWidgetDrawHelper drawHelper( this ); + HandleToolRedraw( drawHelper ); + + RECT rc; + drawHelper.GetClientRect( rc ); + + CChoreoScene *scene = GetSafeScene(); + if ( scene ) + { + RECT rcText; + drawHelper.GetClientRect( rcText ); + rcText.top += GetCaptionHeight()+1; + rcText.bottom = rcText.top + 13; + rcText.left += 5; + rcText.right -= 5; + + OffsetRect( &rcText, 0, 12 ); + + int current, total; + + g_pChoreoView->GetUndoLevels( current, total ); + if ( total > 0 ) + { + RECT rcUndo = rcText; + OffsetRect( &rcUndo, 0, 2 ); + + drawHelper.DrawColoredText( "Small Fonts", 8, FW_NORMAL, RGB( 0, 100, 0 ), rcUndo, + "Undo: %i/%i", current, total ); + } + + rcText.left += 60; + + // Found it, write out description + // + RECT rcTextLine = rcText; + + drawHelper.DrawColoredText( "Arial", 11, 900, RGB( 200, 0, 0 ), rcTextLine, + "Scene: %s", + g_pChoreoView->GetChoreoFile() ); + + RECT rcTimeLine; + drawHelper.GetClientRect( rcTimeLine ); + rcTimeLine.left = 0; + rcTimeLine.right = w2(); + rcTimeLine.top += ( GetCaptionHeight() + 50 ); + + float lefttime = GetTimeValueForMouse( 0 ); + float righttime = GetTimeValueForMouse( w2() ); + + DrawTimeLine( drawHelper, rcTimeLine, lefttime, righttime ); + + OffsetRect( &rcText, 0, 28 ); + + rcText.left = 5; + + RECT timeRect = rcText; + + timeRect.right = timeRect.left + 100; + + char sz[ 32 ]; + + Q_snprintf( sz, sizeof( sz ), "%.2f", lefttime ); + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 0, 0, 0 ), timeRect, sz ); + + timeRect = rcText; + + Q_snprintf( sz, sizeof( sz ), "%.2f", righttime ); + + int textW = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, sz ); + + timeRect.right = w2() - 10; + timeRect.left = timeRect.right - textW; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 0, 0, 0 ), timeRect, sz ); + } + + RECT rcHandle; + GetScrubHandleRect( rcHandle, m_flScrub, true ); + DrawScrubHandle( drawHelper, rcHandle, m_flScrub, false ); + + RECT rcSamples; + GetSampleTrayRect( rcSamples ); + DrawSamples( drawHelper, rcSamples ); + + DrawSceneEnd( drawHelper ); + + RECT rcTags = rc; + rcTags.top = TAG_TOP + GetCaptionHeight(); + rcTags.bottom = TAG_BOTTOM + GetCaptionHeight(); + + DrawTimingTags( drawHelper, rcTags ); + + RECT rcPos; + GetMouseOverPosRect( rcPos ); + DrawMouseOverPos( drawHelper, rcPos ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void SceneRampTool::ShowContextMenu( mxEvent *event, bool include_track_menus ) +{ + // Construct main menu + mxPopupMenu *pop = new mxPopupMenu(); + + int current, total; + g_pChoreoView->GetUndoLevels( current, total ); + if ( total > 0 ) + { + if ( current > 0 ) + { + pop->add( va( "Undo %s", g_pChoreoView->GetUndoDescription() ), IDC_UNDO_SRT ); + } + + if ( current <= total - 1 ) + { + pop->add( va( "Redo %s", g_pChoreoView->GetRedoDescription() ), IDC_REDO_SRT ); + } + pop->addSeparator(); + } + + CChoreoScene *scene = GetSafeScene(); + if ( scene ) + { + if ( CountSelected() > 0 ) + { + pop->add( va( "Delete" ), IDC_SRT_DELETE ); + pop->add( "Deselect all", IDC_SRT_DESELECT ); + } + pop->add( "Select all", IDC_SRT_SELECTALL ); + } + + pop->add( va( "Change scale..." ), IDC_SRT_CHANGESCALE ); + pop->addSeparator(); + pop->add( "Edge Properties...", IDC_SRT_EDGEPROPERTIES ); + + pop->popup( this, (short)event->x, (short)event->y ); +} + +void SceneRampTool::GetWorkspaceLeftRight( int& left, int& right ) +{ + left = 0; + right = w2(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void SceneRampTool::DrawFocusRect( void ) +{ + HDC dc = GetDC( NULL ); + + for ( int i = 0; i < m_FocusRects.Size(); i++ ) + { + RECT rc = m_FocusRects[ i ].m_rcFocus; + + ::DrawFocusRect( dc, &rc ); + } + + ReleaseDC( NULL, dc ); +} + +void SceneRampTool::SetClickedPos( int x, int y ) +{ + m_nClickedX = x; + m_nClickedY = y; +} + +float SceneRampTool::GetTimeForClickedPos( void ) +{ + CChoreoScene *scene = GetSafeScene(); + if ( !scene ) + return 0.0f; + + float t = GetTimeValueForMouse( m_nClickedX ); + return t; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dragtype - +// startx - +// cursor - +//----------------------------------------------------------------------------- +void SceneRampTool::StartDragging( int dragtype, int startx, int starty, HCURSOR cursor ) +{ + m_nDragType = dragtype; + m_nStartX = startx; + m_nLastX = startx; + m_nStartY = starty; + m_nLastY = starty; + + if ( m_hPrevCursor ) + { + SetCursor( m_hPrevCursor ); + m_hPrevCursor = NULL; + } + m_hPrevCursor = SetCursor( cursor ); + + m_FocusRects.Purge(); + + RECT rcStart; + rcStart.left = startx; + rcStart.right = startx; + + bool addrect = true; + switch ( dragtype ) + { + case DRAGTYPE_SCRUBBER: + { + RECT rcScrub; + GetScrubHandleRect( rcScrub, m_flScrub, true ); + + rcStart = rcScrub; + rcStart.left = ( rcScrub.left + rcScrub.right ) / 2; + rcStart.right = rcStart.left; + rcStart.top = rcScrub.bottom; + + rcStart.bottom = h2(); + } + break; + default: + { + rcStart.top = starty; + rcStart.bottom = starty; + } + break; + } + + + if ( addrect ) + { + AddFocusRect( rcStart ); + } + + DrawFocusRect(); +} + +void SceneRampTool::OnMouseMove( mxEvent *event ) +{ + int mx = (short)event->x; + int my = (short)event->y; + + event->x = (short)mx; + + if ( m_nDragType != DRAGTYPE_NONE ) + { + DrawFocusRect(); + + for ( int i = 0; i < m_FocusRects.Size(); i++ ) + { + CFocusRect *f = &m_FocusRects[ i ]; + f->m_rcFocus = f->m_rcOrig; + + switch ( m_nDragType ) + { + default: + { + OffsetRect( &f->m_rcFocus, ( mx - m_nStartX ), ( my - m_nStartY ) ); + } + break; + case DRAGTYPE_SCRUBBER: + { + ApplyBounds( mx, my ); + if ( w2() > 0 ) + { + float t = GetTimeValueForMouse( mx ); + t += m_flScrubberTimeOffset; + ForceScrubPosition( t ); + } + + OffsetRect( &f->m_rcFocus, ( mx - m_nStartX ), 0 ); + } + break; + case DRAGTYPE_MOVEPOINTS_TIME: + case DRAGTYPE_MOVEPOINTS_VALUE: + { + int dx = mx - m_nLastX; + int dy = my - m_nLastY; + + if ( !( event->modifiers & mxEvent::KeyCtrl ) ) + { + // Zero out motion on other axis + if ( m_nDragType == DRAGTYPE_MOVEPOINTS_VALUE ) + { + dx = 0; + mx = m_nLastX; + } + else + { + dy = 0; + my = m_nLastY; + } + } + else + { + SetCursor( LoadCursor( NULL, IDC_SIZEALL ) ); + } + + RECT rcSamples; + GetSampleTrayRect( rcSamples ); + + int height = rcSamples.bottom - rcSamples.top; + Assert( height > 0 ); + + float dfdx = (float)dx / GetPixelsPerSecond(); + float dfdy = (float)dy / (float)height; + + MoveSelectedSamples( dfdx, dfdy ); + + // Update the scrubber + if ( w2() > 0 ) + { + float t = GetTimeValueForMouse( mx ); + ForceScrubPosition( t ); + g_pMatSysWindow->Frame(); + } + + OffsetRect( &f->m_rcFocus, dx, dy ); + } + break; + case DRAGTYPE_SELECTION: + { + RECT rcFocus; + + rcFocus.left = m_nStartX < m_nLastX ? m_nStartX : m_nLastX; + rcFocus.right = m_nStartX < m_nLastX ? m_nLastX : m_nStartX; + + rcFocus.top = m_nStartY < m_nLastY ? m_nStartY : m_nLastY; + rcFocus.bottom = m_nStartY < m_nLastY ? m_nLastY : m_nStartY; + + POINT offset; + offset.x = 0; + offset.y = 0; + ClientToScreen( (HWND)getHandle(), &offset ); + OffsetRect( &rcFocus, offset.x, offset.y ); + + f->m_rcFocus = rcFocus; + } + break; + } + } + + DrawFocusRect(); + } + else + { + if ( m_hPrevCursor ) + { + SetCursor( m_hPrevCursor ); + m_hPrevCursor = NULL; + } + + if ( IsMouseOverScrubHandle( event ) ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + } + /* + else if ( IsMouseOverTag( mx, my ) ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + } + */ + // See if anything is selected + if ( CountSelected() <= 0 ) + { + // Nothing selected + // Draw auto highlight + DrawAutoHighlight( event ); + } + } + + m_nLastX = (short)event->x; + m_nLastY = (short)event->y; +} + +int SceneRampTool::handleEvent( mxEvent *event ) +{ + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + int iret = 0; + + if ( HandleToolEvent( event ) ) + { + return iret; + } + + // Give helper a shot at the event + if ( m_pHelper->HelperHandleEvent( event ) ) + { + return 1; + } + + switch ( event->event ) + { + case mxEvent::Size: + { + int w, h; + w = event->width; + h = event->height; + + m_nLastHPixelsNeeded = 0; + InvalidateLayout(); + iret = 1; + } + break; + case mxEvent::MouseWheeled: + { + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( scene ) + { + int tz = g_pChoreoView->GetTimeZoom( GetToolName() ); + bool shiftdown = ( event->modifiers & mxEvent::KeyShift ) ? true : false; + int stepMultipiler = shiftdown ? 5 : 1; + + // Zoom time in / out + if ( event->height > 0 ) + { + tz = min( tz + TIME_ZOOM_STEP * stepMultipiler, MAX_TIME_ZOOM ); + } + else + { + tz = max( tz - TIME_ZOOM_STEP * stepMultipiler, TIME_ZOOM_STEP ); + } + + g_pChoreoView->SetPreservedTimeZoom( this, tz ); + } + redraw(); + iret = 1; + } + break; + case mxEvent::MouseDown: + { + bool ctrldown = ( event->modifiers & mxEvent::KeyCtrl ) ? true : false; + bool shiftdown = ( event->modifiers & mxEvent::KeyShift ) ? true : false; + + bool rightbutton = ( event->buttons & mxEvent::MouseRightButton ) ? true : false; + + iret = 1; + + int mx = (short)event->x; + int my = (short)event->y; + + SetClickedPos( mx, my ); + + SetMouseOverPos( mx, my ); + DrawMouseOverPos(); + + POINT pt; + pt.x = mx; + pt.y = my; + + RECT rcSamples; + GetSampleTrayRect( rcSamples ); + + bool insamplearea = PtInRect( &rcSamples, pt ) ? true : false; + + if ( m_nDragType == DRAGTYPE_NONE ) + { + bool ctrlDown = ( event->modifiers & mxEvent::KeyCtrl ) ? true : false; + + CExpressionSample *sample = GetSampleUnderMouse( event->x, event->y, ctrlDown ? FP_SRT_ADDSAMPLE_TOLERANCE : FP_SRT_SELECTION_TOLERANCE ); + + if ( IsMouseOverScrubHandle( event ) ) + { + if ( w2() > 0 ) + { + float t = GetTimeValueForMouse( (short)event->x ); + m_flScrubberTimeOffset = m_flScrub - t; + float maxoffset = 0.5f * (float)SCRUBBER_HANDLE_WIDTH / GetPixelsPerSecond(); + m_flScrubberTimeOffset = clamp( m_flScrubberTimeOffset, -maxoffset, maxoffset ); + t += m_flScrubberTimeOffset; + ForceScrubPosition( t ); + } + + StartDragging( DRAGTYPE_SCRUBBER, m_nClickedX, m_nClickedY, LoadCursor( NULL, IDC_SIZEWE ) ); + } + else if ( insamplearea ) + { + if ( sample ) + { + if ( shiftdown ) + { + sample->selected = !sample->selected; + redraw(); + } + else if ( sample->selected ) + { + PreDataChanged( "move scene ramp points" ); + + StartDragging( + rightbutton ? DRAGTYPE_MOVEPOINTS_TIME : DRAGTYPE_MOVEPOINTS_VALUE, + m_nClickedX, m_nClickedY, + LoadCursor( NULL, rightbutton ? IDC_SIZEWE : IDC_SIZENS ) ); + } + else + { + if ( !shiftdown ) + { + DeselectAll(); + } + + StartDragging( DRAGTYPE_SELECTION, m_nClickedX, m_nClickedY, LoadCursor( NULL, IDC_ARROW ) ); + } + } + else if ( ctrldown ) + { + CChoreoScene *s = GetSafeScene(); + if ( s ) + { + // Add a sample point + float t = GetTimeValueForMouse( mx ); + + t = FacePoser_SnapTime( t ); + float value = 1.0f - (float)( (short)event->y - rcSamples.top ) / (float)( rcSamples.bottom - rcSamples.top ); + value = clamp( value, 0.0f, 1.0f ); + + PreDataChanged( "Add scene ramp point" ); + + s->AddSceneRamp( t, value, false ); + + s->ResortSceneRamp(); + + PostDataChanged( "Add scene ramp point" ); + } + } + else + { + if ( event->buttons & mxEvent::MouseRightButton ) + { + ShowContextMenu( event, false ); + iret = 1; + return iret; + } + else + { + if ( !shiftdown ) + { + DeselectAll(); + } + + StartDragging( DRAGTYPE_SELECTION, m_nClickedX, m_nClickedY, LoadCursor( NULL, IDC_ARROW ) ); + } + } + } + else + { + if ( event->buttons & mxEvent::MouseRightButton ) + { + ShowContextMenu( event, false ); + iret = 1; + return iret; + } + else + { + if ( w2() > 0 ) + { + float t = GetTimeValueForMouse( (short)event->x ); + + SetScrubTargetTime( t ); + } + } + } + + CalcBounds( m_nDragType ); + } + } + break; + case mxEvent::MouseDrag: + case mxEvent::MouseMove: + { + int mx = (short)event->x; + int my = (short)event->y; + + SetMouseOverPos( mx, my ); + DrawMouseOverPos(); + + OnMouseMove( event ); + + iret = 1; + } + break; + case mxEvent::MouseUp: + { + OnMouseMove( event ); + + int mx = (short)event->x; + int my = (short)event->y; + + if ( m_nDragType != DRAGTYPE_NONE ) + { + DrawFocusRect(); + } + + if ( m_hPrevCursor ) + { + SetCursor( m_hPrevCursor ); + m_hPrevCursor = 0; + } + + switch ( m_nDragType ) + { + case DRAGTYPE_NONE: + break; + case DRAGTYPE_SCRUBBER: + { + ApplyBounds( mx, my ); + + if ( w2() > 0 ) + { + float t = GetTimeValueForMouse( (short)event->x ); + t += m_flScrubberTimeOffset; + ForceScrubPosition( t ); + m_flScrubberTimeOffset = 0.0f; + } + } + break; + case DRAGTYPE_MOVEPOINTS_VALUE: + case DRAGTYPE_MOVEPOINTS_TIME: + { + PostDataChanged( "move ramp points" ); + } + break; + case DRAGTYPE_SELECTION: + { + SelectPoints(); + } + break; + } + + m_nDragType = DRAGTYPE_NONE; + + SetMouseOverPos( mx, my ); + DrawMouseOverPos(); + + redraw(); + + iret = 1; + } + break; + case mxEvent::Action: + { + iret = 1; + switch ( event->action ) + { + default: + iret = 0; + break; + case IDC_UNDO_SRT: + { + OnUndo(); + } + break; + case IDC_REDO_SRT: + { + OnRedo(); + } + break; + case IDC_SRT_DELETE: + { + Delete(); + } + break; + case IDC_SRT_DESELECT: + { + DeselectAll(); + } + break; + case IDC_SRT_SELECTALL: + { + SelectAll(); + } + break; + case IDC_SRT_RAMPHSCROLL: + { + int offset = 0; + bool processed = true; + + switch ( event->modifiers ) + { + case SB_THUMBTRACK: + offset = event->height; + break; + case SB_PAGEUP: + offset = m_pHorzScrollBar->getValue(); + offset -= 20; + offset = max( offset, m_pHorzScrollBar->getMinValue() ); + break; + case SB_PAGEDOWN: + offset = m_pHorzScrollBar->getValue(); + offset += 20; + offset = min( offset, m_pHorzScrollBar->getMaxValue() ); + break; + case SB_LINEUP: + offset = m_pHorzScrollBar->getValue(); + offset -= 10; + offset = max( offset, m_pHorzScrollBar->getMinValue() ); + break; + case SB_LINEDOWN: + offset = m_pHorzScrollBar->getValue(); + offset += 10; + offset = min( offset, m_pHorzScrollBar->getMaxValue() ); + break; + default: + processed = false; + break; + } + + if ( processed ) + { + MoveTimeSliderToPos( offset ); + } + } + break; + case IDC_SRT_CHANGESCALE: + { + OnChangeScale(); + } + break; + case IDC_SRT_EDGEPROPERTIES: + { + OnEdgeProperties(); + } + break; + } + } + break; + case mxEvent::KeyDown: + { + iret = 1; + switch ( event->key ) + { + default: + iret = g_pChoreoView->HandleZoomKey( this, event->key ); + break; + case VK_ESCAPE: + DeselectAll(); + break; + case VK_DELETE: + Delete(); + break; + } + } + } + return iret; +} + +void SceneRampTool::ApplyBounds( int& mx, int& my ) +{ + if ( !m_bUseBounds ) + return; + + mx = clamp( mx, m_nMinX, m_nMaxX ); +} + +void SceneRampTool::CalcBounds( int movetype ) +{ + switch ( movetype ) + { + default: + case DRAGTYPE_NONE: + { + m_bUseBounds = false; + m_nMinX = 0; + m_nMaxX = 0; + } + break; + case DRAGTYPE_SCRUBBER: + { + m_bUseBounds = true; + m_nMinX = 0; + m_nMaxX = w2(); + } + break; + } +} + +bool SceneRampTool::PaintBackground() +{ + redraw(); + return false; +} + +void SceneRampTool::OnUndo( void ) +{ + g_pChoreoView->Undo(); +} + +void SceneRampTool::OnRedo( void ) +{ + g_pChoreoView->Redo(); +} + +void SceneRampTool::ForceScrubPositionFromSceneTime( float scenetime ) +{ + CChoreoScene *s = GetSafeScene(); + if ( !s || !s->FindStopTime() ) + return; + + float t = scenetime; + m_flScrub = t; + m_flScrubTarget = t; + DrawScrubHandles(); +} + +void SceneRampTool::ForceScrubPosition( float t ) +{ + m_flScrub = t; + m_flScrubTarget = t; + + CChoreoScene *s = GetSafeScene(); + if ( s && s->FindStopTime() ) + { + float realtime = t; + + g_pChoreoView->SetScrubTime( realtime ); + g_pChoreoView->SetScrubTargetTime( realtime ); + + g_pChoreoView->DrawScrubHandle(); + } + + DrawScrubHandles(); +} + +void SceneRampTool::SetMouseOverPos( int x, int y ) +{ + m_nMousePos[ 0 ] = x; + m_nMousePos[ 1 ] = y; +} + +void SceneRampTool::GetMouseOverPos( int &x, int& y ) +{ + x = m_nMousePos[ 0 ]; + y = m_nMousePos[ 1 ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rcPos - +//----------------------------------------------------------------------------- +void SceneRampTool::GetMouseOverPosRect( RECT& rcPos ) +{ + rcPos.top = GetCaptionHeight() + 12; + rcPos.left = w2() - 200; + rcPos.right = w2() - 5; + rcPos.bottom = rcPos.top + 13; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rcPos - +//----------------------------------------------------------------------------- +void SceneRampTool::DrawMouseOverPos( CChoreoWidgetDrawHelper& drawHelper, RECT& rcPos ) +{ + // Compute time for pixel x + float t = GetTimeValueForMouse( m_nMousePos[ 0 ] ); + CChoreoScene *s = GetSafeScene(); + if ( !s ) + return; + + float snapped = FacePoser_SnapTime( t ); + + // Found it, write out description + // + char sz[ 128 ]; + if ( t != snapped ) + { + Q_snprintf( sz, sizeof( sz ), "%s", FacePoser_DescribeSnappedTime( t ) ); + } + else + { + Q_snprintf( sz, sizeof( sz ), "%.3f", t ); + } + + int len = drawHelper.CalcTextWidth( "Arial", 11, 900, sz ); + + RECT rcText = rcPos; + rcText.left = max( rcPos.left, rcPos.right - len ); + + drawHelper.DrawColoredText( "Arial", 11, 900, RGB( 255, 50, 70 ), rcText, sz ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void SceneRampTool::DrawMouseOverPos() +{ + RECT rcPos; + GetMouseOverPosRect( rcPos ); + + CChoreoWidgetDrawHelper drawHelper( this, rcPos ); + DrawMouseOverPos( drawHelper, rcPos ); +} + +void SceneRampTool::AddFocusRect( RECT& rc ) +{ + RECT rcFocus = rc; + + POINT offset; + offset.x = 0; + offset.y = 0; + ClientToScreen( (HWND)getHandle(), &offset ); + OffsetRect( &rcFocus, offset.x, offset.y ); + + // Convert to screen space? + CFocusRect fr; + fr.m_rcFocus = rcFocus; + fr.m_rcOrig = rcFocus; + + m_FocusRects.AddToTail( fr ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rc - +// left - +// right - +//----------------------------------------------------------------------------- +void SceneRampTool::DrawTimeLine( CChoreoWidgetDrawHelper& drawHelper, RECT& rc, float left, float right ) +{ + RECT rcLabel; + float granularity = 0.5f; + + drawHelper.DrawColoredLine( RGB( 150, 150, 200 ), PS_SOLID, 1, rc.left, rc.top + 2, rc.right, rc.top + 2 ); + + float f = SnapTime( left, granularity ); + while ( f < right ) + { + float frac = ( f - left ) / ( right - left ); + if ( frac >= 0.0f && frac <= 1.0f ) + { + rcLabel.left = GetPixelForTimeValue( f ); + rcLabel.top = rc.top + 5; + rcLabel.bottom = rcLabel.top + 10; + + if ( f != left ) + { + drawHelper.DrawColoredLine( RGB( 220, 220, 240 ), PS_DOT, 1, + rcLabel.left, rc.top, rcLabel.left, h2() ); + } + + char sz[ 32 ]; + sprintf( sz, "%.2f", f ); + + int textWidth = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, sz ); + + rcLabel.right = rcLabel.left + textWidth; + + OffsetRect( &rcLabel, -textWidth / 2, 0 ); + + RECT rcOut = rcLabel; + if ( rcOut.left <= 0 ) + { + OffsetRect( &rcOut, -rcOut.left + 2, 0 ); + } + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 0, 50, 150 ), rcOut, sz ); + + } + f += granularity; + } +} + +void SceneRampTool::DrawTimingTags( CChoreoWidgetDrawHelper& drawHelper, RECT& rc ) +{ + CChoreoScene *scene = GetSafeScene(); + if ( !scene ) + return; + + float starttime = GetTimeValueForMouse( 0 ); + float endtime = GetTimeValueForMouse( w2() ); + + if ( endtime - starttime <= 0.0f ) + return; + + RECT rcText = rc; + rcText.bottom = rcText.top + 10; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 0, 100, 200 ), rcText, "Timing Tags:" ); + + // Loop through all events in scene + + int c = scene->GetNumEvents(); + int i; + for ( i = 0; i < c; i++ ) + { + CChoreoEvent *e = scene->GetEvent( i ); + if ( !e ) + continue; + + // See if time overlaps + if ( !e->HasEndTime() ) + continue; + + if ( e->GetEndTime() < starttime ) + continue; + + if ( e->GetStartTime() > endtime ) + continue; + + if ( e->GetNumRelativeTags() > 0 ) + { + DrawRelativeTagsForEvent( drawHelper, rc, e, starttime, endtime ); + } + if ( e->GetNumAbsoluteTags( CChoreoEvent::PLAYBACK ) > 0 ) + { + DrawAbsoluteTagsForEvent( drawHelper, rc, e, starttime, endtime ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// &rc - +//----------------------------------------------------------------------------- +void SceneRampTool::DrawAbsoluteTagsForEvent( CChoreoWidgetDrawHelper& drawHelper, RECT &rc, CChoreoEvent *event, float starttime, float endtime ) +{ + if ( !event ) + return; + + for ( int i = 0; i < event->GetNumAbsoluteTags( CChoreoEvent::PLAYBACK ); i++ ) + { + CEventAbsoluteTag *tag = event->GetAbsoluteTag( CChoreoEvent::PLAYBACK, i ); + if ( !tag ) + continue; + + float tagtime = ( event->GetStartTime() + tag->GetPercentage() * event->GetDuration() ); + if ( tagtime < starttime || tagtime > endtime ) + continue; + + bool clipped = false; + int left = GetPixelForTimeValue( tagtime, &clipped ); + if ( clipped ) + continue; + + if ( event->GetType() == CChoreoEvent::GESTURE ) + { + continue; + } + + COLORREF clr = RGB( 0, 100, 250 ); + + RECT rcMark; + rcMark = rc; + rcMark.top = rc.bottom - 8; + rcMark.bottom = rc.bottom; + rcMark.left = left - 4; + rcMark.right = left + 4; + + drawHelper.DrawTriangleMarker( rcMark, clr ); + + RECT rcText; + rcText = rcMark; + rcText.top -= 12; + + int len = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, tag->GetName() ); + rcText.left = left - len / 2; + rcText.right = rcText.left + len + 2; + + rcText.bottom = rcText.top + 10; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, clr, rcText, tag->GetName() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rc - +//----------------------------------------------------------------------------- +void SceneRampTool::DrawRelativeTagsForEvent( CChoreoWidgetDrawHelper& drawHelper, RECT& rc, CChoreoEvent *event, float starttime, float endtime ) +{ + if ( !event ) + return; + + //drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, PEColor( COLOR_PHONEME_TIMING_TAG ), rc, "Timing Tags:" ); + + for ( int i = 0; i < event->GetNumRelativeTags(); i++ ) + { + CEventRelativeTag *tag = event->GetRelativeTag( i ); + if ( !tag ) + continue; + + // + float tagtime = ( event->GetStartTime() + tag->GetPercentage() * event->GetDuration() ); + if ( tagtime < starttime || tagtime > endtime ) + continue; + + bool clipped = false; + int left = GetPixelForTimeValue( tagtime, &clipped ); + if ( clipped ) + continue; + + //float frac = ( tagtime - starttime ) / ( endtime - starttime ); + + //int left = rc.left + (int)( frac * ( float )( rc.right - rc.left ) + 0.5f ); + + COLORREF clr = RGB( 100, 100, 100 ); + + RECT rcMark; + rcMark = rc; + rcMark.top = rc.bottom - 8; + rcMark.bottom = rc.bottom; + rcMark.left = left - 4; + rcMark.right = left + 4; + + drawHelper.DrawTriangleMarker( rcMark, clr ); + + RECT rcText; + rcText = rc; + rcText.bottom = rc.bottom - 10; + rcText.top = rcText.bottom - 10; + + int len = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, tag->GetName() ); + rcText.left = left - len / 2; + rcText.right = rcText.left + len + 2; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, clr, rcText, tag->GetName() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int SceneRampTool::ComputeHPixelsNeeded( void ) +{ + CChoreoScene *scene = GetSafeScene(); + if ( !scene ) + return 0; + + int pixels = 0; + float maxtime = scene->FindStopTime(); + pixels = (int)( ( maxtime ) * GetPixelsPerSecond() + 10 ); + + return pixels; + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void SceneRampTool::RepositionHSlider( void ) +{ + int pixelsneeded = ComputeHPixelsNeeded(); + + if ( pixelsneeded <= w2() ) + { + m_pHorzScrollBar->setVisible( false ); + } + else + { + m_pHorzScrollBar->setVisible( true ); + } + m_pHorzScrollBar->setBounds( 0, h2() - m_nScrollbarHeight, w2() - m_nScrollbarHeight, m_nScrollbarHeight ); + + m_flLeftOffset = max( 0.f, m_flLeftOffset ); + m_flLeftOffset = min( (float)pixelsneeded, m_flLeftOffset ); + + m_pHorzScrollBar->setRange( 0, pixelsneeded ); + m_pHorzScrollBar->setValue( (int)m_flLeftOffset ); + m_pHorzScrollBar->setPagesize( w2() ); + + m_nLastHPixelsNeeded = pixelsneeded; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float SceneRampTool::GetPixelsPerSecond( void ) +{ + return m_flPixelsPerSecond * (float)g_pChoreoView->GetTimeZoom( GetToolName() )/100.0f; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : x - +//----------------------------------------------------------------------------- +void SceneRampTool::MoveTimeSliderToPos( int x ) +{ + m_flLeftOffset = (float)x; + m_pHorzScrollBar->setValue( (int)m_flLeftOffset ); + InvalidateRect( (HWND)m_pHorzScrollBar->getHandle(), NULL, TRUE ); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void SceneRampTool::InvalidateLayout( void ) +{ + if ( m_bSuppressLayout ) + return; + + if ( ComputeHPixelsNeeded() != m_nLastHPixelsNeeded ) + { + RepositionHSlider(); + } + + m_bLayoutIsValid = false; + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : st - +// ed - +//----------------------------------------------------------------------------- +void SceneRampTool::GetStartAndEndTime( float& st, float& ed ) +{ + st = m_flLeftOffset / GetPixelsPerSecond(); + ed = st + (float)w2() / GetPixelsPerSecond(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : float +//----------------------------------------------------------------------------- +float SceneRampTool::GetEventEndTime() +{ + CChoreoScene *scene = GetSafeScene(); + if ( !scene ) + return 1.0f; + + return scene->FindStopTime(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : time - +// *clipped - +// Output : int +//----------------------------------------------------------------------------- +int SceneRampTool::GetPixelForTimeValue( float time, bool *clipped /*=NULL*/ ) +{ + if ( clipped ) + { + *clipped = false; + } + + float st, ed; + GetStartAndEndTime( st, ed ); + + float frac = ( time - st ) / ( ed - st ); + if ( frac < 0.0 || frac > 1.0 ) + { + if ( clipped ) + { + *clipped = true; + } + } + + int pixel = ( int )( frac * w2() ); + return pixel; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// clip - +// Output : float +//----------------------------------------------------------------------------- +float SceneRampTool::GetTimeValueForMouse( int mx, bool clip /*=false*/) +{ + float st, ed; + GetStartAndEndTime( st, ed ); + + if ( clip ) + { + if ( mx < 0 ) + { + return st; + } + if ( mx > w2() ) + { + return ed; + } + } + + float frac = (float)( mx ) / (float)( w2() ); + return st + frac * ( ed - st ); +} + +void SceneRampTool::OnChangeScale( void ) +{ + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( !scene ) + { + return; + } + + // Zoom time in / out + CInputParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Change Zoom" ); + strcpy( params.m_szPrompt, "New scale (e.g., 2.5x):" ); + + Q_snprintf( params.m_szInputText, sizeof( params.m_szInputText ), "%.2f", (float)g_pChoreoView->GetTimeZoom( GetToolName() ) / 100.0f ); + + if ( !InputProperties( ¶ms ) ) + return; + + g_pChoreoView->SetTimeZoom( GetToolName(), clamp( (int)( 100.0f * atof( params.m_szInputText ) ), 1, MAX_TIME_ZOOM ), false ); + + m_nLastHPixelsNeeded = -1; + m_flLeftOffset= 0.0f; + InvalidateLayout(); + Con_Printf( "Zoom factor %i %%\n", g_pChoreoView->GetTimeZoom( GetToolName() ) ); +} + +void SceneRampTool::DrawSceneEnd( CChoreoWidgetDrawHelper& drawHelper ) +{ + CChoreoScene *s = GetSafeScene(); + if ( !s ) + return; + + float duration = s->FindStopTime(); + if ( !duration ) + return; + + int leftx = GetPixelForTimeValue( duration ); + if ( leftx >= w2() ) + return; + + RECT rcSample; + GetSampleTrayRect( rcSample ); + + drawHelper.DrawColoredLine( + COLOR_CHOREO_ENDTIME, PS_SOLID, 1, + leftx, rcSample.top, leftx, rcSample.bottom ); +} + +void SceneRampTool::GetSampleTrayRect( RECT& rc ) +{ + rc.left = 0; + rc.right = w2(); + rc.top = GetCaptionHeight() + 65; + + rc.bottom = h2() - m_nScrollbarHeight-2; +} + +void SceneRampTool::DrawSamplesSimple( CChoreoWidgetDrawHelper& drawHelper, CChoreoScene *scene, bool clearbackground, COLORREF sampleColor, RECT &rcSamples ) +{ + if ( clearbackground ) + { + drawHelper.DrawFilledRect( RGB( 230, 230, 215 ), rcSamples ); + } + + if ( !scene ) + return; + + float starttime = 0.0f; + float endtime = scene->FindStopTime(); + + COLORREF lineColor = sampleColor; + + int width = rcSamples.right - rcSamples.left; + if ( width <= 0.0f ) + return; + + int height = rcSamples.bottom - rcSamples.top; + int bottom = rcSamples.bottom; + + float timestepperpixel = endtime / (float)width; + + float prev_value = scene->GetSceneRampIntensity( starttime ); + int prev_x = rcSamples.left; + float prev_t = 0.0f; + + for ( float x = rcSamples.left; x < rcSamples.right; x+=3 ) + { + float t = (float)( x - rcSamples.left ) * timestepperpixel; + + float value = scene->GetSceneRampIntensity( starttime + t ); + + // Draw segment + drawHelper.DrawColoredLine( lineColor, PS_SOLID, 1, + prev_x, bottom - prev_value * height, + x, bottom - value * height ); + + prev_x = x; + prev_t = t; + prev_value = value; + } +} + +void SceneRampTool::DrawSamples( CChoreoWidgetDrawHelper& drawHelper, RECT &rcSamples ) +{ + drawHelper.DrawFilledRect( RGB( 230, 230, 215 ), rcSamples ); + + CChoreoScene *s = GetSafeScene(); + if ( !s ) + return; + + int rampCount = s->GetSceneRampCount(); + if ( !rampCount ) + return; + + float starttime; + float endtime; + + GetStartAndEndTime( starttime, endtime ); + + COLORREF lineColor = RGB( 0, 0, 255 ); + COLORREF dotColor = RGB( 0, 0, 255 ); + COLORREF dotColorSelected = RGB( 240, 80, 20 ); + COLORREF shadowColor = RGB( 150, 150, 250 ); + + int height = rcSamples.bottom - rcSamples.top; + int bottom = rcSamples.bottom; + int top = rcSamples.top; + + float timestepperpixel = 1.0f / GetPixelsPerSecond(); + + float stoptime = min( endtime, s->FindStopTime() ); + + float prev_t = starttime; + float prev_value = s->GetSceneRampIntensity( prev_t ); + + for ( float t = starttime-timestepperpixel; t <= stoptime; t += timestepperpixel ) + { + float value = s->GetSceneRampIntensity( t ); + + int prevx, x; + + bool clipped1, clipped2; + x = GetPixelForTimeValue( t, &clipped1 ); + prevx = GetPixelForTimeValue( prev_t, &clipped2 ); + + if ( !clipped1 && !clipped2 ) + { + // Draw segment + drawHelper.DrawColoredLine( lineColor, PS_SOLID, 1, + prevx, clamp( bottom - prev_value * height, top, bottom ), + x, clamp( bottom - value * height, top, bottom ) ); + } + + prev_t = t; + prev_value = value; + + } + + for ( int sample = 0; sample < rampCount; sample++ ) + { + CExpressionSample *start = s->GetSceneRamp( sample ); + + /* + int pixel = (int)( ( start->time / event_time ) * width + 0.5f); + int x = m_rcBounds.left + pixel; + float roundedfrac = (float)pixel / (float)width; + */ + float value = start->value; + bool clipped = false; + int x = GetPixelForTimeValue( start->time, &clipped ); + if ( clipped ) + continue; + int y = bottom - value * height; + + int dotsize = 6; + int dotSizeSelected = 6; + + COLORREF clr = dotColor; + COLORREF clrSelected = dotColorSelected; + + drawHelper.DrawCircle( + start->selected ? clrSelected : clr, + x, y, + start->selected ? dotSizeSelected : dotsize, + true ); + + if ( !start->selected ) + continue; + + if ( start->GetCurveType() == CURVE_DEFAULT ) + continue; + + // Draw curve type indicator... + char sz[ 128 ]; + Q_snprintf( sz, sizeof( sz ), "%s", Interpolator_NameForCurveType( start->GetCurveType(), true ) ); + RECT rc; + int fontSize = 9; + rc.top = clamp( y + 5, rcSamples.top + 2, rcSamples.bottom - 2 - fontSize ); + rc.bottom = rc.top + fontSize + 1; + rc.left = x - 75; + rc.right = x + 175; + drawHelper.DrawColoredText( "Arial", fontSize, 500, shadowColor, rc, sz ); + } +} + +void SceneRampTool::DrawAutoHighlight( mxEvent *event ) +{ + CChoreoScene *s = GetSafeScene(); + if ( !s ) + return; + + CExpressionSample *hover = GetSampleUnderMouse( event->x, event->y, 0.0f ); + RECT rcSamples; + GetSampleTrayRect( rcSamples ); + + CChoreoWidgetDrawHelper drawHelper( this, rcSamples, true ); + + RECT rcClient = rcSamples; + + COLORREF dotColor = RGB( 0, 0, 255 ); + COLORREF dotColorSelected = RGB( 240, 80, 20 ); + COLORREF clrHighlighted = RGB( 0, 200, 0 ); + + int height = rcClient.bottom - rcClient.top; + int bottom = rcClient.bottom; + + int dotsize = 6; + int dotSizeSelected = 6; + int dotSizeHighlighted = 6; + + COLORREF clr = dotColor; + COLORREF clrSelected = dotColorSelected; + COLORREF bgColor = RGB( 230, 230, 200 ); + + // Fixme, could look at 1st derivative and do more sampling at high rate of change? + // or near actual sample points! + int sampleCount = s->GetSceneRampCount(); + for ( int sample = 0; sample < sampleCount; sample++ ) + { + CExpressionSample *start = s->GetSceneRamp( sample ); + + float value = start->value; + bool clipped = false; + int x = GetPixelForTimeValue( start->time, &clipped ); + if ( clipped ) + continue; + int y = bottom - value * height; + + if ( hover == start ) + { + drawHelper.DrawCircle( + bgColor, + x, y, + dotSizeHighlighted, + true ); + + drawHelper.DrawCircle( + clrHighlighted, + x, y, + dotSizeHighlighted, + false ); + + + } + else + { + drawHelper.DrawCircle( + start->selected ? clrSelected : clr, + x, y, + start->selected ? dotSizeSelected : dotsize, + true ); + } + } +} + + +int SceneRampTool::NumSamples() +{ + CChoreoScene *s = GetSafeScene(); + if ( !s ) + return 0; + + return s->GetSceneRampCount(); +} + +CExpressionSample *SceneRampTool::GetSample( int idx ) +{ + CChoreoScene *s = GetSafeScene(); + if ( !s ) + return NULL; + + return s->GetSceneRamp( idx ); +} + +CExpressionSample *SceneRampTool::GetSampleUnderMouse( int mx, int my, float tolerance /*= FP_SRT_SELECTION_TOLERANCE*/ ) +{ + CChoreoScene *s = GetSafeScene(); + if ( !s ) + return NULL; + + RECT rcSamples; + GetSampleTrayRect( rcSamples ); + + POINT pt; + pt.x = mx; + pt.y = my; + + if ( !PtInRect( &rcSamples, pt ) ) + return NULL; + + pt.y -= rcSamples.top; + + float closest_dist = 9999999.f; + CExpressionSample *bestsample = NULL; + + int height = rcSamples.bottom - rcSamples.top; + + for ( int i = 0; i < s->GetSceneRampCount(); i++ ) + { + CExpressionSample *sample = s->GetSceneRamp( i ); + Assert( sample ); + + bool clipped = false; + int px = GetPixelForTimeValue( sample->time, &clipped ); + int py = height * ( 1.0f - sample->value ); + + int dx = px - pt.x; + int dy = py - pt.y; + + float dist = sqrt( (float)(dx * dx + dy * dy) ); + + if ( dist < closest_dist ) + { + bestsample = sample; + closest_dist = dist; + } + + } + + // Not close to any of them!!! + if ( ( tolerance != 0.0f ) && + ( closest_dist > tolerance ) ) + return NULL; + + return bestsample; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void SceneRampTool::SelectPoints( void ) +{ + RECT rcSelection; + + rcSelection.left = m_nStartX < m_nLastX ? m_nStartX : m_nLastX; + rcSelection.right = m_nStartX < m_nLastX ? m_nLastX : m_nStartX; + + rcSelection.top = m_nStartY < m_nLastY ? m_nStartY : m_nLastY; + rcSelection.bottom = m_nStartY < m_nLastY ? m_nLastY : m_nStartY; + + int selW = rcSelection.right - rcSelection.left; + int selH = rcSelection.bottom - rcSelection.top; + + float tolerance = FP_SRT_SELECTION_RECTANGLE_TOLERANCE; + // If they are just clicking and releasing in one spot, capture any items w/in a larger tolerance + if ( selW <= 2 && selH <= 2 ) + { + tolerance = FP_SRT_SELECTION_TOLERANCE; + + CExpressionSample *sample = GetSampleUnderMouse( rcSelection.left + selW * 0.5f, rcSelection.top + selH * 0.5f ); + if ( sample ) + { + sample->selected = true; + return; + } + } + else + { + InflateRect( &rcSelection, 3, 3 ); + } + + RECT rcSamples; + GetSampleTrayRect( rcSamples ); + + int height = rcSamples.bottom - rcSamples.top; + + CChoreoScene *s = GetSafeScene(); + if ( !s ) + return; + + float duration = s->FindStopTime(); + if ( !duration ) + return; + + float fleft = (float)GetTimeValueForMouse( rcSelection.left ); + float fright = (float)GetTimeValueForMouse( rcSelection.right ); + + //fleft *= duration; + //fright *= duration; + + float ftop = (float)( rcSelection.top - rcSamples.top ) / (float)height; + float fbottom = (float)( rcSelection.bottom - rcSamples.top ) / (float)height; + + fleft = clamp( fleft, 0.0f, duration ); + fright = clamp( fright, 0.0f, duration ); + ftop = clamp( ftop, 0.0f, 1.0f ); + fbottom = clamp( fbottom, 0.0f, 1.0f ); + + float timestepperpixel = 1.0f / GetPixelsPerSecond(); + float yfracstepperpixel = 1.0f / (float)height; + + + float epsx = tolerance*timestepperpixel; + float epsy = tolerance*yfracstepperpixel; + + for ( int i = 0; i < s->GetSceneRampCount(); i++ ) + { + CExpressionSample *sample = s->GetSceneRamp( i ); + + if ( sample->time + epsx < fleft ) + continue; + + if ( sample->time - epsx > fright ) + continue; + + if ( (1.0f - sample->value ) + epsy < ftop ) + continue; + + if ( (1.0f - sample->value ) - epsy > fbottom ) + continue; + + sample->selected = true; + } + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int SceneRampTool::CountSelected( void ) +{ + return m_pHelper->CountSelected( false ); +} + +void SceneRampTool::MoveSelectedSamples( float dfdx, float dfdy ) +{ + int selecteditems = CountSelected(); + if ( !selecteditems ) + return; + + CChoreoScene *s = GetSafeScene(); + if ( !s ) + return; + + int c = s->GetSceneRampCount(); + + float duration = s->FindStopTime(); + //dfdx *= duration; + + for ( int i = 0; i < c; i++ ) + { + CExpressionSample *sample = s->GetSceneRamp( i ); + if ( !sample || !sample->selected ) + continue; + + sample->time += dfdx; + sample->time = clamp( sample->time, 0.0f, duration ); + + sample->value -= dfdy; + sample->value = clamp( sample->value, 0.0f, 1.0f ); + } + + s->ResortSceneRamp(); + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void SceneRampTool::DeselectAll( void ) +{ + int i; + + int selecteditems = CountSelected(); + if ( !selecteditems ) + return; + + CChoreoScene *s = GetSafeScene(); + Assert( s ); + if ( !s ) + return; + + for ( i = s->GetSceneRampCount() - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = s->GetSceneRamp( i ); + sample->selected = false; + } + + redraw(); +} + +void SceneRampTool::SelectAll( void ) +{ + int i; + + CChoreoScene *s = GetSafeScene(); + Assert( s ); + if ( !s ) + return; + + for ( i = s->GetSceneRampCount() - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = s->GetSceneRamp( i ); + sample->selected = true; + } + + redraw(); +} + +void SceneRampTool::Delete( void ) +{ + int i; + + CChoreoScene *s = GetSafeScene(); + if ( !s ) + return; + + int selecteditems = CountSelected(); + if ( !selecteditems ) + return; + + PreDataChanged( "Delete scene ramp points" ); + + for ( i = s->GetSceneRampCount() - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = s->GetSceneRamp( i ); + if ( !sample->selected ) + continue; + + s->DeleteSceneRamp( i ); + } + + PostDataChanged( "Delete scene ramp points" ); +} + +void SceneRampTool::OnModelChanged() +{ + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *undodescription - +//----------------------------------------------------------------------------- +void SceneRampTool::PreDataChanged( char const *undodescription ) +{ + if ( m_nUndoSetup == 0 ) + { + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( undodescription ); + } + ++m_nUndoSetup; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *redodescription - +//----------------------------------------------------------------------------- +void SceneRampTool::PostDataChanged( char const *redodescription ) +{ + --m_nUndoSetup; + if ( m_nUndoSetup == 0 ) + { + g_pChoreoView->PushRedo( redodescription ); + redraw(); + } +} + +void SceneRampTool::SetMousePositionForEvent( mxEvent *event ) +{ + POINT pt; + GetCursorPos( &pt ); + ScreenToClient( (HWND)getHandle(), &pt ); + + event->x = pt.x; + event->y = pt.y; +} + +void SceneRampTool::OnEdgeProperties() +{ + CChoreoScene *s = GetSafeScene(); + if ( !s ) + return; + + CEdgePropertiesParams params; + Q_memset( ¶ms, 0, sizeof( params ) ); + Q_strcpy( params.m_szDialogTitle, "Edge Properties" ); + + params.SetFromCurve( s->GetSceneRamp() ); + + if ( !EdgeProperties( ¶ms ) ) + { + return; + } + + char *undotext = "Change Scene Ramp Edge Properties"; + + PreDataChanged( undotext ); + + // Apply changes. + params.ApplyToCurve( s->GetSceneRamp() ); + + PostDataChanged( undotext ); +} + +void SceneRampTool::GetWorkList( bool reflect, CUtlVector< SceneRampTool * >& list ) +{ + NOTE_UNUSED( reflect ); + list.AddToTail( this ); +} diff --git a/utils/hlfaceposer/SceneRampTool.h b/utils/hlfaceposer/SceneRampTool.h new file mode 100644 index 0000000..6e89c04 --- /dev/null +++ b/utils/hlfaceposer/SceneRampTool.h @@ -0,0 +1,201 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef SCENERAMPTOOL_H +#define SCENERAMPTOOL_H +#ifdef _WIN32 +#pragma once +#endif + +#include <mxtk/mx.h> +#include "studio.h" +#include "utlvector.h" +#include "faceposertoolwindow.h" + +class CChoreoEvent; +class CChoreoScene; +class CChoreoWidgetDrawHelper; +class CChoreoView; +struct CExpressionSample; + +#define IDC_REDO_SRT 1000 +#define IDC_UNDO_SRT 1001 + +#define IDC_SRT_DELETE 1002 +#define IDC_SRT_DESELECT 1003 +#define IDC_SRT_SELECTALL 1004 + +#define IDC_SRT_CHANGESCALE 1005 +#define IDC_SRT_RAMPHSCROLL 1006 + +#define IDC_SRT_EDGEPROPERTIES 1007 + +#define FP_SRT_SELECTION_TOLERANCE 30.0f +#define FP_SRT_SELECTION_RECTANGLE_TOLERANCE 5.0f +#define FP_SRT_ADDSAMPLE_TOLERANCE 5.0f + +template< class T > class CCurveEditorHelper; + +class SceneRampTool : public mxWindow, public IFacePoserToolWindow +{ +public: + // Construction + SceneRampTool( mxWindow *parent ); + ~SceneRampTool( void ); + + virtual void Think( float dt ); + void ScrubThink( float dt, bool scrubbing ); + virtual bool IsScrubbing( void ) const; + virtual bool IsProcessing( void ); + + virtual int handleEvent( mxEvent *event ); + virtual void redraw( void ); + virtual bool PaintBackground(); + + void GetScrubHandleRect( RECT& rcHandle, float scrub, bool clipped = false ); + + void DrawScrubHandle( CChoreoWidgetDrawHelper& drawHelper, RECT& rcHandle, float scrub, bool reference ); + void DrawTimeLine( CChoreoWidgetDrawHelper& drawHelper, RECT& rc, float left, float right ); + void DrawSceneEnd( CChoreoWidgetDrawHelper& drawHelper ); + + void SetMouseOverPos( int x, int y ); + void GetMouseOverPos( int &x, int& y ); + void GetMouseOverPosRect( RECT& rcPos ); + void DrawMouseOverPos( CChoreoWidgetDrawHelper& drawHelper, RECT& rcPos ); + void DrawMouseOverPos(); + + void DrawScrubHandles(); + + CChoreoScene *GetSafeScene( void ); + + bool IsMouseOverScrubHandle( mxEvent *event ); + void ForceScrubPosition( float newtime ); + void ForceScrubPositionFromSceneTime( float scenetime ); + + void SetScrubTime( float t ); + void SetScrubTargetTime( float t ); + + void DrawSamplesSimple( CChoreoWidgetDrawHelper& drawHelper, CChoreoScene *scene, bool clearbackground, COLORREF sampleColor, RECT &rcSamples ); + + virtual void OnModelChanged(); + + void SetMousePositionForEvent( mxEvent *event ); + + int NumSamples(); + CExpressionSample *GetSample( int idx ); + void PreDataChanged( char const *undodescription ); + void PostDataChanged( char const *redodescription ); + CExpressionSample *GetSampleUnderMouse( int mx, int my, float tolerance = FP_SRT_SELECTION_TOLERANCE ); + void GetWorkList( bool reflect, CUtlVector< SceneRampTool * >& list ); + +private: + + void GetSampleTrayRect( RECT& rc ); + void DrawSamples( CChoreoWidgetDrawHelper& drawHelper, RECT &rcSamples ); + + void SelectPoints( void ); + void DeselectAll(); + void SelectAll(); + void Delete( void ); + + int CountSelected( void ); + void MoveSelectedSamples( float dfdx, float dfdy ); + + void StartDragging( int dragtype, int startx, int starty, HCURSOR cursor ); + void AddFocusRect( RECT& rc ); + void OnMouseMove( mxEvent *event ); + void DrawFocusRect( void ); + void ShowContextMenu( mxEvent *event, bool include_track_menus ); + void GetWorkspaceLeftRight( int& left, int& right ); + void SetClickedPos( int x, int y ); + float GetTimeForClickedPos( void ); + + void DrawAutoHighlight( mxEvent *event ); + + void ApplyBounds( int& mx, int& my ); + void CalcBounds( int movetype ); + void OnUndo( void ); + void OnRedo( void ); + + void OnRevert( void ); + + void OnEdgeProperties(); + + void DrawTimingTags( CChoreoWidgetDrawHelper& drawHelper, RECT& rc ); + void DrawRelativeTagsForEvent( CChoreoWidgetDrawHelper& drawHelper, RECT& rc, CChoreoEvent *event, float starttime, float endtime ); + void DrawAbsoluteTagsForEvent( CChoreoWidgetDrawHelper& drawHelper, RECT &rc, CChoreoEvent *event, float starttime, float endtime ); + + + // Readjust slider + void MoveTimeSliderToPos( int x ); + void OnChangeScale(); + int ComputeHPixelsNeeded( void ); + float GetPixelsPerSecond( void ); + void InvalidateLayout( void ); + void RepositionHSlider( void ); + void GetStartAndEndTime( float& st, float& ed ); + float GetEventEndTime(); + float GetTimeValueForMouse( int mx, bool clip = false ); + int GetPixelForTimeValue( float time, bool *clipped = NULL ); + + float m_flScrub; + float m_flScrubTarget; + + enum + { + DRAGTYPE_NONE = 0, + DRAGTYPE_SCRUBBER, + DRAGTYPE_MOVEPOINTS_VALUE, + DRAGTYPE_MOVEPOINTS_TIME, + DRAGTYPE_SELECTION, + }; + + int m_nMousePos[ 2 ]; + + bool m_bUseBounds; + int m_nMinX; + int m_nMaxX; + + HCURSOR m_hPrevCursor; + int m_nDragType; + + int m_nStartX; + int m_nStartY; + int m_nLastX; + int m_nLastY; + + int m_nClickedX; + int m_nClickedY; + + struct CFocusRect + { + RECT m_rcOrig; + RECT m_rcFocus; + }; + CUtlVector < CFocusRect > m_FocusRects; + + bool m_bSuppressLayout; + // Height/width of scroll bars + int m_nScrollbarHeight; + float m_flLeftOffset; + mxScrollbar *m_pHorzScrollBar; + int m_nLastHPixelsNeeded; + // How many pixels per second we are showing in the UI + float m_flPixelsPerSecond; + // Do we need to move controls? + bool m_bLayoutIsValid; + float m_flLastDuration; + float m_flScrubberTimeOffset; + int m_nUndoSetup; + + CCurveEditorHelper< SceneRampTool > *m_pHelper; + + friend class CChoreoView; +}; + +extern SceneRampTool *g_pSceneRampTool; + +#endif // SCENERAMPTOOL_H diff --git a/utils/hlfaceposer/VGuiWnd.cpp b/utils/hlfaceposer/VGuiWnd.cpp new file mode 100644 index 0000000..3e49c43 --- /dev/null +++ b/utils/hlfaceposer/VGuiWnd.cpp @@ -0,0 +1,324 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +#include "cbase.h" +#include "vguiwnd.h" +#include <vgui_controls/EditablePanel.h> +#include "vgui/ISurface.h" +#include "vgui/IVGui.h" +#include "VGuiMatSurface/IMatSystemSurface.h" +#include "FacePoser_VGui.h" +// #include "material.h" +#include "materialsystem/imaterialvar.h" +#include "materialsystem/imaterial.h" + +#define REPAINT_TIMER_ID 1042 //random value, hopfully no collisions + +inline MaterialSystem_Config_t& MaterialSystemConfig() +{ + extern MaterialSystem_Config_t g_materialSystemConfig; + return g_materialSystemConfig; +} + +inline IMaterialSystemHardwareConfig* MaterialSystemHardwareConfig() +{ + extern IMaterialSystemHardwareConfig* g_pMaterialSystemHardwareConfig; + return g_pMaterialSystemHardwareConfig; +} + +class CBaseMainPanel : public vgui::EditablePanel +{ +public: + + CBaseMainPanel(Panel *parent, const char *panelName) : vgui::EditablePanel( parent, panelName ) {}; + + virtual void OnSizeChanged(int newWide, int newTall) + { + // call Panel and not EditablePanel OnSizeChanged. + Panel::OnSizeChanged(newWide, newTall); + } +}; + +int CVGuiPanelWnd::handleEvent( mxEvent *event ) +{ + if ( !HandeEventVGui( event ) ) + { + return BaseClass::handleEvent( event ); + } + + return 1; +} + +CVGuiWnd::CVGuiWnd(void) +{ + m_pMainPanel = NULL; + m_pParentWnd = NULL; + m_hVGuiContext = vgui::DEFAULT_VGUI_CONTEXT; + m_bIsDrawing = false; + m_ClearColor.SetColor( 0,0,0,255 ); + m_bClearZBuffer = true; +} + +CVGuiWnd::~CVGuiWnd(void) +{ + if ( FaceposerVGui()->HasFocus( this ) ) + { + FaceposerVGui()->SetFocus( NULL ); + } + + if ( m_hVGuiContext != vgui::DEFAULT_VGUI_CONTEXT ) + { + vgui::ivgui()->DestroyContext( m_hVGuiContext ); + m_hVGuiContext = vgui::DEFAULT_VGUI_CONTEXT; + } + + // kill the timer if any + ::KillTimer( (HWND)m_pParentWnd->getHandle(), REPAINT_TIMER_ID ); + + + if ( m_pMainPanel ) + m_pMainPanel->MarkForDeletion(); +} + +void CVGuiWnd::SetParentWindow(mxWindow *pParent) +{ + m_pParentWnd = pParent; + + /* + m_pParentWnd->EnableWindow( true ); + m_pParentWnd->SetFocus(); + */ + + HWND h = (HWND)m_pParentWnd->getHandle(); + EnableWindow( h, TRUE ); + SetFocus( h ); +} + +int CVGuiWnd::GetVGuiContext() +{ + return m_hVGuiContext; +} + +void CVGuiWnd::SetCursor(vgui::HCursor cursor) +{ + if ( m_pMainPanel ) + { + m_pMainPanel->SetCursor( cursor ); + } +} + +void CVGuiWnd::SetCursor(const char *filename) +{ + vgui::HCursor hCursor = vgui::surface()->CreateCursorFromFile( filename ); + m_pMainPanel->SetCursor( hCursor ); +} + +void CVGuiWnd::SetMainPanel( vgui::EditablePanel * pPanel ) +{ + SetRepaintInterval( 75 ); + + Assert( m_pMainPanel == NULL ); + Assert( m_hVGuiContext == vgui::DEFAULT_VGUI_CONTEXT ); + + m_pMainPanel = pPanel; + + m_pMainPanel->SetParent( vgui::surface()->GetEmbeddedPanel() ); + m_pMainPanel->SetVisible( true ); + m_pMainPanel->SetPaintBackgroundEnabled( false ); + m_pMainPanel->SetCursor( vgui::dc_arrow ); + + m_hVGuiContext = vgui::ivgui()->CreateContext(); + vgui::ivgui()->AssociatePanelWithContext( m_hVGuiContext, m_pMainPanel->GetVPanel() ); +} + +vgui::EditablePanel *CVGuiWnd::CreateDefaultPanel() +{ + return new CBaseMainPanel( NULL, "mainpanel" ); +} + +vgui::EditablePanel *CVGuiWnd::GetMainPanel() +{ + return m_pMainPanel; +} + +mxWindow *CVGuiWnd::GetParentWnd() +{ + return m_pParentWnd; +} + +void CVGuiWnd::SetRepaintInterval( int msecs ) +{ + m_pParentWnd->setTimer( msecs ); +} + +void CVGuiWnd::DrawVGuiPanel() +{ + if ( !m_pMainPanel || !m_pParentWnd || m_bIsDrawing ) + return; + + + m_bIsDrawing = true; // avoid recursion + + HWND hWnd = (HWND)m_pParentWnd->getHandle(); + + int w,h; + RECT rect; + ::GetClientRect(hWnd, &rect); + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + + g_pMaterialSystem->SetView( hWnd ); + + pRenderContext->Viewport( rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top ); + + pRenderContext->ClearColor4ub( m_ClearColor.r(), m_ClearColor.g(), m_ClearColor.b(), m_ClearColor.a() ); + + pRenderContext->ClearBuffers( true, m_bClearZBuffer ); + + g_pMaterialSystem->BeginFrame( 0 ); + + // draw from the main panel down + + m_pMainPanel->GetSize( w , h ); + + if ( w != rect.right || h != rect.bottom ) + { + m_pMainPanel->SetBounds( 2 + rect.left, 2 + rect.top, rect.right - rect.left - 4, rect.bottom - rect.top - 4 ); + m_pMainPanel->Repaint(); + } + + FaceposerVGui()->Simulate(); + + vgui::surface()->PaintTraverseEx( m_pMainPanel->GetVPanel(), true ); + + g_pMaterialSystem->EndFrame(); + + g_pMaterialSystem->SwapBuffers(); + + m_bIsDrawing = false; +} + +CVGuiPanelWnd::CVGuiPanelWnd( mxWindow *parent, int x, int y, int w, int h ) +: BaseClass( parent, x, y, w, h ) +{ +} + +void CVGuiPanelWnd::redraw() +{ + DrawVGuiPanel(); +} + +int CVGuiWnd::HandeEventVGui( mxEvent *event ) +{ + if ( !m_pParentWnd ) + return 0; + + HWND hWnd = (HWND)m_pParentWnd->getHandle(); + +// switch( uMsg ) +// { + +// case WM_GETDLGCODE : +// { +// // forward all keyboard into to vgui panel +// return DLGC_WANTALLKEYS|DLGC_WANTCHARS; +// } + +// case WM_PAINT : +// { +// // draw the VGUI panel now +// DrawVGuiPanel(); +// break; +// } + +// case WM_TIMER : +// { +// if ( wParam == REPAINT_TIMER_ID ) +// { +// m_pParentWnd->Invalidate(); +// } +// break; +// } + +// case WM_SETCURSOR: +// return 1; // don't pass WM_SETCURSOR + +/* + case WM_LBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_MOUSEMOVE: + { + // switch vgui focus to this panel + FaceposerVGui()->SetFocus( this ); + + // request keyboard focus too on mouse down + if ( uMsg != WM_MOUSEMOVE) + { + m_pParentWnd->Invalidate(); + m_pParentWnd->SetFocus(); + } + break; + } + case WM_KILLFOCUS: + { + // restore normal arrow cursor when mouse leaves VGUI panel + SetCursor( vgui::dc_arrow ); + break; + } + + case WM_LBUTTONUP: + case WM_RBUTTONUP: + case WM_MBUTTONUP: + case WM_LBUTTONDBLCLK: + case WM_RBUTTONDBLCLK: + case WM_MBUTTONDBLCLK: + case WM_MOUSEWHEEL: + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_SYSCHAR: + case WM_CHAR: + case WM_KEYUP: + case WM_SYSKEYUP: + { + // redraw window + m_pParentWnd->Invalidate(); + break; + } + } +*/ + + switch ( event->event ) + { + case mxEvent::KeyUp: + case mxEvent::KeyDown: + case mxEvent::MouseUp: + case mxEvent::MouseWheeled: + case mxEvent::Char: + case mxEvent::MouseMove: + { + InvalidateRect( hWnd, NULL, FALSE ); + } + break; + + case mxEvent::Timer: + { + InvalidateRect( hWnd, NULL, FALSE ); + } + break; + case mxEvent::Focus: + break; + case mxEvent::MouseDown: + { + // switch vgui focus to this panel + FaceposerVGui()->SetFocus( this ); + + // request keyboard focus too on mouse down + if ( event->event != mxEvent::MouseMove ) + { + InvalidateRect( hWnd, NULL, FALSE ); + SetFocus( hWnd ); + } + } + break; + } + + return 0; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/VGuiWnd.h b/utils/hlfaceposer/VGuiWnd.h new file mode 100644 index 0000000..0c8964a --- /dev/null +++ b/utils/hlfaceposer/VGuiWnd.h @@ -0,0 +1,69 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef VGUIWND_H +#define VGUIWND_H +#ifdef _WIN32 +#pragma once +#endif + +#include "mxtk/mx.h" +#include "color.h" + +namespace vgui +{ + class EditablePanel; + typedef unsigned long HCursor; +} + +class CVGuiWnd +{ + +public: + CVGuiWnd(void); + ~CVGuiWnd(void); + +public: + + void SetMainPanel( vgui::EditablePanel * pPanel ); + vgui::EditablePanel *GetMainPanel(); // returns VGUI main panel + vgui::EditablePanel *CreateDefaultPanel(); + + void SetParentWindow(mxWindow *pParent); + mxWindow *GetParentWnd(); // return mxWindow handle + + void SetCursor(vgui::HCursor cursor); + void SetCursor(const char *filename); + + void SetRepaintInterval( int msecs ); + int GetVGuiContext(); + +protected: + void DrawVGuiPanel(); // overridden to draw this view + int HandeEventVGui( mxEvent *event ); + + vgui::EditablePanel *m_pMainPanel; + mxWindow *m_pParentWnd; + int m_hVGuiContext; + bool m_bIsDrawing; + Color m_ClearColor; + bool m_bClearZBuffer; +}; + +class CVGuiPanelWnd: public mxWindow, public CVGuiWnd +{ + typedef mxWindow BaseClass; + +public: + + CVGuiPanelWnd( mxWindow *parent, int x, int y, int w, int h ); + + virtual int handleEvent( mxEvent *event ); + virtual void redraw(); +}; + + +#endif // VGUIWND_H diff --git a/utils/hlfaceposer/actorproperties.cpp b/utils/hlfaceposer/actorproperties.cpp new file mode 100644 index 0000000..dfd6248 --- /dev/null +++ b/utils/hlfaceposer/actorproperties.cpp @@ -0,0 +1,77 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "resource.h" +#include "ActorProperties.h" +#include "ChoreoView.h" +#include "choreoactor.h" +#include "mdlviewer.h" + +static CActorParams g_Params; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK ActorPropertiesDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + switch(uMsg) + { + case WM_INITDIALOG: + // Insert code here to put the string (to find and replace with) + // into the edit controls. + // ... + { + g_Params.PositionSelf( hwndDlg ); + + SetDlgItemText( hwndDlg, IDC_ACTORNAME, g_Params.m_szName ); + + SetWindowText( hwndDlg, g_Params.m_szDialogTitle ); + + SetFocus( GetDlgItem( hwndDlg, IDC_ACTORNAME ) ); + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + g_Params.m_szName[ 0 ] = 0; + GetDlgItemText( hwndDlg, IDC_ACTORNAME, g_Params.m_szName, 256 ); + EndDialog( hwndDlg, 1 ); + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int ActorProperties( CActorParams *params ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_ACTORPROPERTIES ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)ActorPropertiesDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/actorproperties.h b/utils/hlfaceposer/actorproperties.h new file mode 100644 index 0000000..83ff364 --- /dev/null +++ b/utils/hlfaceposer/actorproperties.h @@ -0,0 +1,28 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ACTORPROPERTIES_H +#define ACTORPROPERTIES_H +#ifdef _WIN32 +#pragma once +#endif + +#include "basedialogparams.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +struct CActorParams : public CBaseDialogParams +{ + // i/o actor name + char m_szName[ 256 ]; +}; + +// Display/create actor info +int ActorProperties( CActorParams *params ); + +#endif // ACTORPROPERTIES_H diff --git a/utils/hlfaceposer/addsoundentry.cpp b/utils/hlfaceposer/addsoundentry.cpp new file mode 100644 index 0000000..979af56 --- /dev/null +++ b/utils/hlfaceposer/addsoundentry.cpp @@ -0,0 +1,164 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#include "cbase.h" +#include "mxtk/mx.h" +#include "resource.h" +#include "AddSoundEntry.h" +#include "mdlviewer.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "filesystem.h" + +static CAddSoundParams g_Params; + +static void PopulateScriptList( HWND wnd ) +{ + HWND control = GetDlgItem( wnd, IDC_SOUNDSCRIPT ); + if ( !control ) + { + return; + } + + SendMessage( control, CB_RESETCONTENT, 0, 0 ); + + int c = soundemitter->GetNumSoundScripts(); + for ( int i = 0; i < c; i++ ) + { + // add text to combo box + SendMessage( control, CB_ADDSTRING, 0, (LPARAM)soundemitter->GetSoundScriptName( i ) ); + + if ( i == 0 && !g_Params.m_szScriptName[ 0 ] ) + { + SendMessage( control, WM_SETTEXT , 0, (LPARAM)soundemitter->GetSoundScriptName( i ) ); + } + } + + if ( g_Params.m_szScriptName[ 0 ] ) + { + SendMessage( control, WM_SETTEXT , 0, (LPARAM)g_Params.m_szScriptName ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK AddSoundPropertiesDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + switch(uMsg) + { + case WM_INITDIALOG: + // Insert code here to put the string (to find and replace with) + // into the edit controls. + // ... + { + g_Params.PositionSelf( hwndDlg ); + + PopulateScriptList( hwndDlg ); + + SetDlgItemText( hwndDlg, IDC_SOUNDNAME, g_Params.m_szSoundName ); + if ( g_Params.m_bReadOnlySoundName ) + { + HWND ctrl = GetDlgItem( hwndDlg, IDC_SOUNDNAME ); + if ( ctrl ) + { + SendMessage( ctrl, EM_SETREADONLY, (WPARAM)TRUE, 0 ); + } + } + + SetWindowText( hwndDlg, g_Params.m_szDialogTitle ); + + SetFocus( GetDlgItem( hwndDlg, IDC_SOUNDNAME ) ); + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + g_Params.m_szSoundName[ 0 ] = 0; + g_Params.m_szScriptName[ 0 ] = 0; + GetDlgItemText( hwndDlg, IDC_SOUNDNAME, g_Params.m_szSoundName, sizeof( g_Params.m_szSoundName ) ); + GetDlgItemText( hwndDlg, IDC_SOUNDSCRIPT, g_Params.m_szScriptName, sizeof( g_Params.m_szScriptName ) ); + + // Don't exit... + if ( !g_Params.m_szSoundName[ 0 ] || !g_Params.m_szScriptName[ 0 ] ) + return TRUE; + + // Don't stompt existing sounds + int idx = soundemitter->GetSoundIndex( g_Params.m_szSoundName ); + if ( soundemitter->IsValidIndex( idx ) ) + { + if ( !g_Params.m_bAllowExistingSound ) + { + mxMessageBox( NULL, va( "Sound '%s' already exists", + g_Params.m_szSoundName ), g_appTitle, MX_MB_OK ); + } + else + { + EndDialog( hwndDlg, 1 ); + } + return TRUE; + } + + // Check out script + if ( !filesystem->FileExists( g_Params.m_szScriptName ) ) + { + mxMessageBox( NULL, va( "Script '%s' does not exist", + g_Params.m_szScriptName ), g_appTitle, MX_MB_OK ); + return TRUE; + } + + if ( !filesystem->IsFileWritable( g_Params.m_szScriptName ) ) + { + mxMessageBox( NULL, va( "Script '%s' is read-only, you need to check it out of VSS", + g_Params.m_szScriptName ), g_appTitle, MX_MB_OK ); + return TRUE; + } + + // Add the entry + CSoundParametersInternal params; + params.SetChannel( CHAN_VOICE ); + params.SetSoundLevel( SNDLVL_TALKING ); + + soundemitter->ExpandSoundNameMacros( params, g_Params.m_szWaveFile ); + soundemitter->AddSound( g_Params.m_szSoundName, g_Params.m_szScriptName, params ); + + EndDialog( hwndDlg, 1 ); + } + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int AddSound( CAddSoundParams *params, HWND parent ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_ADDSOUNDENTRY ), + parent, + (DLGPROC)AddSoundPropertiesDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/addsoundentry.h b/utils/hlfaceposer/addsoundentry.h new file mode 100644 index 0000000..b5945bd --- /dev/null +++ b/utils/hlfaceposer/addsoundentry.h @@ -0,0 +1,40 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef ADDSOUNDENTRY_H +#define ADDSOUNDENTRY_H +#ifdef _WIN32 +#pragma once +#endif + +#include "basedialogparams.h" +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +struct CAddSoundParams : public CBaseDialogParams +{ + CAddSoundParams() + { + m_szWaveFile[ 0 ] = 0; + m_szSoundName[ 0 ] = 0; + m_szScriptName[ 0 ] = 0; + m_bAllowExistingSound = false; + m_bReadOnlySoundName = false; + } + + char m_szWaveFile[ 256 ]; + + // i/o input text + char m_szSoundName[ 256 ]; + char m_szScriptName[ 256 ]; + bool m_bAllowExistingSound; + bool m_bReadOnlySoundName; +}; + +// Display/create dialog +int AddSound( CAddSoundParams *params, HWND parent ); + +#endif // ADDSOUNDENTRY_H diff --git a/utils/hlfaceposer/audiowaveoutput.h b/utils/hlfaceposer/audiowaveoutput.h new file mode 100644 index 0000000..c169c16 --- /dev/null +++ b/utils/hlfaceposer/audiowaveoutput.h @@ -0,0 +1,127 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef AUDIOWAVEOUTPUT_H +#define AUDIOWAVEOUTPUT_H +#ifdef _WIN32 +#pragma once +#endif + +#include "sound.h" +#include "utlvector.h" + +#define OUTPUT_BUFFER_COUNT 32 +#define MAX_CHANNELS 16 + +class CAudioMixer; + +class CAudioMixerState +{ +public: + CAudioMixer *mixer; + int submit_mixer_sample; +}; + +class CAudioBuffer +{ +public: + WAVEHDR *hdr; + bool submitted; + int submit_sample_count; + + CUtlVector< CAudioMixerState > m_Referenced; +}; + +#define OUTPUT_SAMPLE_RATE 44100 +#define PAINTBUFFER_SIZE 1024 + +typedef struct +{ + int left; + int right; +} portable_samplepair_t; + +class CAudioDeviceSWMix : public IAudioDevice +{ +public: + virtual void Mix8Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, int rateScaleFix, int outCount, int timecompress, bool forward = true ); + virtual void Mix8Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, int rateScaleFix, int outCount, int timecompress, bool forward = true ); + virtual void Mix16Mono( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, int rateScaleFix, int outCount, int timecompress, bool forward = true ); + virtual void Mix16Stereo( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, int rateScaleFix, int outCount, int timecompress, bool forward = true ); + virtual int MaxSampleCount( void ); + virtual void MixBegin( void ); + + void TransferBufferStereo16( short *pOutput, int sampleCount ); + +private: + portable_samplepair_t m_paintbuffer[ PAINTBUFFER_SIZE ]; +}; + +class CAudioWaveOutput : public CAudioOutput +{ +public: + CAudioWaveOutput( void ); + ~CAudioWaveOutput( void ); + + // returns the size of each sample in bytes + virtual int SampleSize( void ) { return 2; } + + // returns the sampling rate of the data + virtual int SampleRate( void ) { return OUTPUT_SAMPLE_RATE; } + + // returns the mono/stereo status of this device (true if stereo) + virtual bool IsStereo( void ) { return true; } + + // mix a buffer up to time (time is absolute) + virtual void Update( float time ); + + virtual void Flush( void ); + + virtual void AddSource( CAudioMixer *pSource ); + virtual void StopSounds( void ); + virtual int FindSourceIndex( CAudioMixer *pSource ); + + virtual int GetOutputPosition( void ); + virtual float GetAmountofTimeAhead( void ); + virtual int GetNumberofSamplesAhead( void ); + + virtual CAudioMixer *GetMixerForSource( CAudioSource *pSource ); + + +private: + void OpenDevice( void ); + bool ValidDevice( void ) { return m_deviceHandle != 0; } + void ClearDevice( void ) { m_deviceHandle = NULL; } + CAudioBuffer *GetEmptyBuffer( void ); + void SilenceBuffer( short *pSamples, int sampleCount ); + + void SetChannel( int channelIndex, CAudioMixer *pSource ); + void FreeChannel( int channelIndex ); + + void RemoveMixerChannelReferences( CAudioMixer *mixer ); + void AddToReferencedList( CAudioMixer *mixer, CAudioBuffer *buffer ); + void RemoveFromReferencedList( CAudioMixer *mixer, CAudioBuffer *buffer ); + bool IsSourceReferencedByActiveBuffer( CAudioMixer *mixer ); + bool IsSoundInReferencedList( CAudioMixer *mixer, CAudioBuffer *buffer ); + + // Compute how many samples we've mixed since most recent buffer submission + void ComputeSampleAheadAmount( void ); + + HWAVEOUT m_deviceHandle; + + float m_mixTime; + float m_baseTime; + int m_sampleIndex; + CAudioBuffer m_buffers[ OUTPUT_BUFFER_COUNT ]; + + CAudioMixer *m_sourceList[MAX_CHANNELS]; + int m_nEstimatedSamplesAhead; +public: + CAudioDeviceSWMix m_audioDevice; + +}; +#endif // AUDIOWAVEOUTPUT_H diff --git a/utils/hlfaceposer/basedialogparams.cpp b/utils/hlfaceposer/basedialogparams.cpp new file mode 100644 index 0000000..dba7bd9 --- /dev/null +++ b/utils/hlfaceposer/basedialogparams.cpp @@ -0,0 +1,65 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <mxtk/mx.h> +#include "tier0/dbg.h" +#include "basedialogparams.h" + +//----------------------------------------------------------------------------- +// Purpose: +// Input : self - +//----------------------------------------------------------------------------- +void CBaseDialogParams::PositionSelf( void *self ) +{ + RECT rcDlg; + HWND dlgWindow = (HWND)self; + GetWindowRect( dlgWindow, &rcDlg ); + + // Get relative to primary monitor instead of actual window parent + RECT rcParent; + rcParent.left = 0; + rcParent.right = rcParent.left + GetSystemMetrics( SM_CXFULLSCREEN ); + rcParent.top = 0; + rcParent.bottom = rcParent.top + GetSystemMetrics( SM_CYFULLSCREEN ); + + int dialogw, dialogh; + int parentw, parenth; + + parentw = rcParent.right - rcParent.left; + parenth = rcParent.bottom - rcParent.top; + dialogw = rcDlg.right - rcDlg.left; + dialogh = rcDlg.bottom - rcDlg.top; + + int dlgleft, dlgtop; + dlgleft = ( parentw - dialogw ) / 2; + dlgtop = ( parenth - dialogh ) / 2; + + if ( m_bPositionDialog ) + { + int top = m_nTop - dialogh - 5; + int left = m_nLeft; + + MoveWindow( dlgWindow, + left, + top, + dialogw, + dialogh, + TRUE ); + } + else + { + + MoveWindow( dlgWindow, + dlgleft, + dlgtop, + dialogw, + dialogh, + TRUE + ); + } + +}
\ No newline at end of file diff --git a/utils/hlfaceposer/basedialogparams.h b/utils/hlfaceposer/basedialogparams.h new file mode 100644 index 0000000..9fb3463 --- /dev/null +++ b/utils/hlfaceposer/basedialogparams.h @@ -0,0 +1,29 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef BASEDIALOGPARAMS_H +#define BASEDIALOGPARAMS_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tier0/dbg.h" +#include "tier1/strtools.h" + +struct CBaseDialogParams +{ + // i dialog title + char m_szDialogTitle[ 128 ]; + + bool m_bPositionDialog; + int m_nLeft; + int m_nTop; + + void PositionSelf( void * self ); +}; + +#endif // BASEDIALOGPARAMS_H diff --git a/utils/hlfaceposer/cbase.h b/utils/hlfaceposer/cbase.h new file mode 100644 index 0000000..7657527 --- /dev/null +++ b/utils/hlfaceposer/cbase.h @@ -0,0 +1,21 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef CBASE_H +#define CBASE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "hlfaceposer.h" +#include "tier1/strtools.h" +#include "vstdlib/random.h" +#include "sharedInterface.h" + +extern class ISoundEmitterSystemBase *soundemitter; + +#endif // CBASE_H diff --git a/utils/hlfaceposer/cclookup.cpp b/utils/hlfaceposer/cclookup.cpp new file mode 100644 index 0000000..37f3fc3 --- /dev/null +++ b/utils/hlfaceposer/cclookup.cpp @@ -0,0 +1,240 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include <commctrl.h> +#include "mxtk/mx.h" +#include "resource.h" +#include "CCLookup.h" +#include "mdlviewer.h" +#include "addsoundentry.h" +#include <vgui/ILocalize.h> + +using namespace vgui; + +extern vgui::ILocalize *g_pLocalize; + +static CCloseCaptionLookupParams g_Params; + +static HFONT g_UnicodeFont = NULL; + +static void InsertTextColumn( HWND listcontrol, int column, int width, char const *label ) +{ + LVCOLUMN col; + memset( &col, 0, sizeof( col ) ); + + col.mask = LVCF_TEXT | LVCF_SUBITEM | LVCF_WIDTH | LVCF_ORDER; + col.iOrder = column; + col.pszText = (char *)label; + col.cchTextMax = 256; + col.iSubItem = column; + col.cx = width; + + ListView_InsertColumn( listcontrol, column, &col ); +} + +static void PopulateCloseCaptionTokenList( HWND wnd, CCloseCaptionLookupParams *params ) +{ + HWND control = GetDlgItem( wnd, IDC_CCTOKENLIST ); + if ( !control ) + return; + + InsertTextColumn( control, 0, 200, "CloseCaption Token" ); + InsertTextColumn( control, 1, 800, "Text" ); + + + ListView_DeleteAllItems( control ); + + SendMessage( control, WM_SETFONT, (WPARAM)g_UnicodeFont, MAKELPARAM (TRUE, 0) ); + + StringIndex_t i = g_pLocalize->GetFirstStringIndex(); + int saveSelected = -1; + + while ( INVALID_LOCALIZE_STRING_INDEX != i ) + { + char const *name = g_pLocalize->GetNameByIndex( i ); + + LV_ITEMW lvItem; + memset( &lvItem, 0, sizeof( lvItem ) ); + lvItem.iItem = ListView_GetItemCount( control ); + lvItem.mask = LVIF_TEXT | LVIF_PARAM; + lvItem.lParam = (LPARAM)i; + + wchar_t label[ 256 ]; + g_pLocalize->ConvertANSIToUnicode( name, label, sizeof( label ) ); + + lvItem.pszText = label; + lvItem.cchTextMax = 256; + + SendMessage( control, LVM_INSERTITEMW, 0, (LPARAM)(const LV_ITEMW FAR*)(&lvItem)); + + lvItem.mask = LVIF_TEXT; + lvItem.iSubItem = 1; + + wchar_t *value = g_pLocalize->GetValueByIndex( i ); + + lvItem.pszText = (wchar_t *)value; + lvItem.cchTextMax = 1024; + + SendMessage( control, LVM_SETITEMW, 0, (LPARAM)(const LV_ITEMW FAR*)(&lvItem)); + + if ( !Q_stricmp( name, params->m_szCCToken ) ) + { + ListView_SetItemState( control, lvItem.iItem, LVIS_SELECTED, LVIS_STATEIMAGEMASK ); + saveSelected = lvItem.iItem; + } + i = g_pLocalize->GetNextStringIndex( i ); + } + + if ( saveSelected != -1 ) + { + ListView_EnsureVisible(control, saveSelected, FALSE ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK CloseCaptionLookupDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + switch(uMsg) + { + case WM_INITDIALOG: + // Insert code here to put the string (to find and replace with) + // into the edit controls. + // ... + { + g_Params.PositionSelf( hwndDlg ); + + HWND control = GetDlgItem( hwndDlg, IDC_CCTOKENLIST ); + DWORD exStyle = GetWindowLong( control, GWL_EXSTYLE ); + exStyle |= LVS_EX_FULLROWSELECT; + SetWindowLong( control, GWL_EXSTYLE, exStyle ); + + PopulateCloseCaptionTokenList( hwndDlg, &g_Params ); + + SetWindowText( hwndDlg, g_Params.m_szDialogTitle ); + + SetDlgItemText( hwndDlg, IDC_CCTOKEN, g_Params.m_szCCToken ); + + SetFocus( GetDlgItem( hwndDlg, IDC_CCTOKENLIST ) ); + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + /* + //int selindex = SendMessage( GetDlgItem( hwndDlg, IDC_CCTOKENLIST ), LB_GETCURSEL, 0, 0 ); + if ( selindex == LB_ERR ) + { + mxMessageBox( NULL, "You must select an entry from the list", g_appTitle, MB_OK ); + return TRUE; + } + */ + + SendMessage( GetDlgItem( hwndDlg, IDC_CCTOKEN ), WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szCCToken ), (LPARAM)g_Params.m_szCCToken ); + + EndDialog( hwndDlg, 1 ); + } + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + } + return TRUE; + case WM_NOTIFY: + { + if ( wParam == IDC_CCTOKENLIST ) + { + NMHDR *hdr = ( NMHDR * )lParam; + if ( hdr->code == LVN_ITEMCHANGED ) + { + HWND control = GetDlgItem( hwndDlg, IDC_CCTOKENLIST ); + + NM_LISTVIEW *nmlv = ( NM_LISTVIEW * )lParam; + + int item = nmlv->iItem; + + if ( item >= 0 ) + { + // look up the lparam value + LVITEM lvi; + memset( &lvi, 0, sizeof( lvi ) ); + lvi.mask = LVIF_PARAM; + lvi.iItem = item; + + if ( ListView_GetItem( control, &lvi ) ) + { + char const *name = g_pLocalize->GetNameByIndex( lvi.lParam ); + if ( name ) + { + Q_strncpy( g_Params.m_szCCToken, name, sizeof( g_Params.m_szCCToken ) ); + SendMessage( GetDlgItem( hwndDlg, IDC_CCTOKEN ), WM_SETTEXT, (WPARAM)sizeof( g_Params.m_szCCToken ), (LPARAM)g_Params.m_szCCToken ); + } + } + } + return FALSE; + } + if ( hdr->code == NM_DBLCLK ) + { + SendMessage( GetDlgItem( hwndDlg, IDC_CCTOKEN ), WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szCCToken ), (LPARAM)g_Params.m_szCCToken ); + EndDialog( hwndDlg, 1 ); + return FALSE; + } + } + + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int CloseCaptionLookup( CCloseCaptionLookupParams *params ) +{ + g_Params = *params; + + g_UnicodeFont = CreateFont( + -10, + 0, + 0, + 0, + FW_NORMAL, + FALSE, + FALSE, + FALSE, + ANSI_CHARSET, + OUT_TT_PRECIS, + CLIP_DEFAULT_PRECIS, + ANTIALIASED_QUALITY, + DEFAULT_PITCH, + "Tahoma" ); + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_CCLOOKUP ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)CloseCaptionLookupDialogProc ); + + DeleteObject( g_UnicodeFont ); + g_UnicodeFont = NULL; + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/cclookup.h b/utils/hlfaceposer/cclookup.h new file mode 100644 index 0000000..047ac7f --- /dev/null +++ b/utils/hlfaceposer/cclookup.h @@ -0,0 +1,27 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef CCLOOKUP_H +#define CCLOOKUP_H +#ifdef _WIN32 +#pragma once +#endif + +#include "basedialogparams.h" +#include "utlvector.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +struct CCloseCaptionLookupParams : public CBaseDialogParams +{ + char m_szCCToken[ 1024 ]; +}; + +// Display/create dialog +int CloseCaptionLookup( CCloseCaptionLookupParams *params ); + +#endif // CCLOOKUP_H diff --git a/utils/hlfaceposer/channelproperties.cpp b/utils/hlfaceposer/channelproperties.cpp new file mode 100644 index 0000000..2161081 --- /dev/null +++ b/utils/hlfaceposer/channelproperties.cpp @@ -0,0 +1,124 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include <stdio.h> +#include "resource.h" +#include "ChannelProperties.h" +#include "ChoreoView.h" +#include "choreoactor.h" +#include "choreoscene.h" +#include "mdlviewer.h" + +static CChannelParams g_Params; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK ChannelPropertiesDialogProc +//----------------------------------------------------------------------------- +static BOOL CALLBACK ChannelPropertiesDialogProc ( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + switch(uMsg) + { + case WM_INITDIALOG: + // Insert code here to put the string (to find and replace with) + // into the edit controls. + // ... + { + g_Params.PositionSelf( hwndDlg ); + + SetDlgItemText( hwndDlg, IDC_CHANNELNAME, g_Params.m_szName ); + + HWND control = GetDlgItem( hwndDlg, IDC_ACTORCHOICE ); + + if ( !g_Params.m_bShowActors ) + { + // Hide the combo box + if ( control ) + { + ShowWindow( control, SW_HIDE ); + } + control = GetDlgItem( hwndDlg, IDC_STATIC_ACTOR ); + if ( control ) + { + ShowWindow( control, SW_HIDE ); + } + } + else + { + SendMessage( control, CB_RESETCONTENT, 0, 0 ); + + if ( g_Params.m_pScene ) + { + for ( int i = 0 ; i < g_Params.m_pScene->GetNumActors() ; i++ ) + { + CChoreoActor *actor = g_Params.m_pScene->GetActor( i ); + if ( actor ) + { + // add text to combo box + SendMessage( control, CB_ADDSTRING, 0, (LPARAM)actor->GetName() ); + } + } + } + + SendMessage( control, CB_SETCURSEL, (WPARAM)0, (LPARAM)0 ); + } + + SetWindowText( hwndDlg, g_Params.m_szDialogTitle ); + + SetFocus( GetDlgItem( hwndDlg, IDC_CHANNELNAME ) ); + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + g_Params.m_szName[ 0 ] = 0; + GetDlgItemText( hwndDlg, IDC_CHANNELNAME, g_Params.m_szName, 256 ); + + if ( g_Params.m_bShowActors ) + { + HWND control = GetDlgItem( hwndDlg, IDC_ACTORCHOICE ); + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szSelectedActor ), (LPARAM)g_Params.m_szSelectedActor ); + } + } + + EndDialog( hwndDlg, 1 ); + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int ChannelProperties( CChannelParams *params ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_CHANNELPROPERTIES ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)ChannelPropertiesDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/channelproperties.h b/utils/hlfaceposer/channelproperties.h new file mode 100644 index 0000000..9a4402f --- /dev/null +++ b/utils/hlfaceposer/channelproperties.h @@ -0,0 +1,38 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef CHANNELPROPERTIES_H +#define CHANNELPROPERTIES_H +#ifdef _WIN32 +#pragma once +#endif + +class CChoreoScene; + +#include "basedialogparams.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +struct CChannelParams : public CBaseDialogParams +{ + // i/o channel name + char m_szName[ 256 ]; + + // For creating a new channel: + // i + bool m_bShowActors; + // i/o + char m_szSelectedActor[ 256 ]; + // i + CChoreoScene *m_pScene; +}; + +// set/create channel properties +int ChannelProperties( CChannelParams *params ); + +#endif // CHANNELPROPERTIES_H diff --git a/utils/hlfaceposer/choiceproperties.cpp b/utils/hlfaceposer/choiceproperties.cpp new file mode 100644 index 0000000..30956c1 --- /dev/null +++ b/utils/hlfaceposer/choiceproperties.cpp @@ -0,0 +1,125 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "resource.h" +#include "ChoiceProperties.h" +#include <mxtk/mx.h> +#include "mdlviewer.h" + +static CChoiceParams g_Params; + +static void PopulateChoiceList( HWND wnd, CChoiceParams *params ) +{ + HWND control = GetDlgItem( wnd, IDC_CHOICE ); + if ( !control ) + return; + + SendMessage( control, CB_RESETCONTENT, 0, 0 ); + + int c = params->m_Choices.Count(); + + if ( params->m_nSelected == -1 ) + params->m_nSelected = 0; + + if ( params->m_nSelected >= 0 && params->m_nSelected < c ) + { + SendMessage( control, WM_SETTEXT , 0, (LPARAM)params->m_Choices[ params->m_nSelected ].choice ); + } + + for ( int i = 0; i < c; i++ ) + { + char const *text = params->m_Choices[ i ].choice; + SendMessage( control, CB_ADDSTRING, 0, (LPARAM)text ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK ChoicePropertiesDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + switch(uMsg) + { + case WM_INITDIALOG: + // Insert code here to put the string (to find and replace with) + // into the edit controls. + // ... + { + g_Params.PositionSelf( hwndDlg ); + + PopulateChoiceList( hwndDlg, &g_Params ); + + SetDlgItemText( hwndDlg, IDC_STATIC_PROMPT, g_Params.m_szPrompt ); + + SetWindowText( hwndDlg, g_Params.m_szDialogTitle ); + } + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + char selected[ MAX_CHOICE_TEXT_SIZE ]; + selected[ 0 ] = 0; + HWND control = GetDlgItem( hwndDlg, IDC_CHOICE ); + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( selected ), (LPARAM)selected ); + } + + g_Params.m_nSelected = -1; + int c = g_Params.m_Choices.Count(); + + for ( int i = 0; i < c; i++ ) + { + char const *text = g_Params.m_Choices[ i ].choice; + if ( stricmp( text, selected ) ) + { + continue; + } + + g_Params.m_nSelected = i; + break; + } + + EndDialog( hwndDlg, 1 ); + } + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int ChoiceProperties( CChoiceParams *params ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_CHOICEDIALOG ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)ChoicePropertiesDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/choiceproperties.h b/utils/hlfaceposer/choiceproperties.h new file mode 100644 index 0000000..d13211b --- /dev/null +++ b/utils/hlfaceposer/choiceproperties.h @@ -0,0 +1,42 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef CHOICEPROPERTIES_H +#define CHOICEPROPERTIES_H +#ifdef _WIN32 +#pragma once +#endif + +#include "utlvector.h" + +#define MAX_CHOICE_TEXT_SIZE 128 + +struct ChoiceText +{ + char choice[ MAX_CHOICE_TEXT_SIZE ]; +}; + +#include "basedialogparams.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +struct CChoiceParams : public CBaseDialogParams +{ + + char m_szPrompt[ 256 ]; + + CUtlVector< ChoiceText > m_Choices; + + // i/o active choice and output choice + int m_nSelected; // -1 for none +}; + +// Display/create dialog +int ChoiceProperties( CChoiceParams *params ); + +#endif // CHOICEPROPERTIES_H diff --git a/utils/hlfaceposer/choreoactorwidget.cpp b/utils/hlfaceposer/choreoactorwidget.cpp new file mode 100644 index 0000000..aefe8e0 --- /dev/null +++ b/utils/hlfaceposer/choreoactorwidget.cpp @@ -0,0 +1,415 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// +#include <stdio.h> +#include "hlfaceposer.h" +#include "choreoactorwidget.h" +#include "choreochannelwidget.h" +#include "choreoactor.h" +#include "choreoview.h" +#include "choreowidgetdrawhelper.h" +#include "mxBitmapButton.h" +#include "choreoviewcolors.h" +#include "choreochannel.h" +#include "filesystem.h" +#include "StudioModel.h" + +#define ACTOR_NAME_HEIGHT 26 +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +// *parent - +// x - +// y - +// w - +// h - +// 0 - +// 0 - +//----------------------------------------------------------------------------- +CActorBitmapButton::CActorBitmapButton( CChoreoActorWidget *actor, mxWindow *parent, int x, int y, int w, int h, int id /*= 0*/, const char *bitmap /*= 0*/ ) +: mxBitmapButton( parent, x, y, w, h, id, bitmap ) +{ + m_pActor = actor; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CChoreoActorWidget *CActorBitmapButton::GetActor( void ) +{ + return m_pActor; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CActorActiveCheckBox::CActorActiveCheckBox( CChoreoActorWidget *actor, mxWindow *parent, int x, int y, int w, int h, const char *label /*= 0*/, int id /*= 0*/ ) + : mxCheckBox( parent, x, y, w, h, label, id ) +{ + m_pActor = actor; +} + +CChoreoActorWidget *CActorActiveCheckBox::GetActor( void ) +{ + return m_pActor; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *parent - +//----------------------------------------------------------------------------- +CChoreoActorWidget::CChoreoActorWidget( CChoreoWidget *parent ) +: CChoreoWidget( parent ) +{ + m_pParent = parent; + + m_pActor = NULL; + + m_bShowChannels = true; + + m_btnOpen = new CActorBitmapButton( this, m_pView, 0, 0, 0, 0, IDC_CHANNELOPEN, "gfx/hlfaceposer/channelopen.bmp" ); + m_btnClose = new CActorBitmapButton( this, m_pView, 0, 0, 0, 0, IDC_CHANNELCLOSE, "gfx/hlfaceposer/channelclose.bmp" ); + + ShowChannels( m_bShowChannels ); + + memset( m_rgCurrentSetting, 0, sizeof( m_rgCurrentSetting ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CChoreoActorWidget::~CChoreoActorWidget( void ) +{ + for ( int i = 0 ; i < m_Channels.Size(); i++ ) + { + CChoreoChannelWidget *c = m_Channels[ i ]; + delete c; + } + m_Channels.RemoveAll(); + + delete m_btnOpen; + delete m_btnClose; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoActorWidget::GetShowChannels( void ) +{ + return m_bShowChannels; +} + +//----------------------------------------------------------------------------- +// Purpose: Switch modes +// Input : show - +//----------------------------------------------------------------------------- +void CChoreoActorWidget::ShowChannels( bool show ) +{ + m_bShowChannels = show; + + m_btnOpen->setVisible( !m_bShowChannels ); + m_btnClose->setVisible( m_bShowChannels ); + + m_pView->InvalidateLayout(); + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoActorWidget::Create( void ) +{ + // Create objects for children + Assert( m_pActor ); + + // Create objects for children + for ( int i = 0; i < m_pActor->GetNumChannels(); i++ ) + { + CChoreoChannel *channel = m_pActor->GetChannel( i ); + Assert( channel ); + if ( !channel ) + { + continue; + } + + CChoreoChannelWidget *channelWidget = new CChoreoChannelWidget( this ); + channelWidget->SetChannel( channel ); + channelWidget->Create(); + + AddChannel( channelWidget ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rc - +//----------------------------------------------------------------------------- +void CChoreoActorWidget::Layout( RECT& rc ) +{ + setBounds( rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top ); + + int buttonSize = 16; + int ypos = rc.top + ( ACTOR_NAME_HEIGHT - buttonSize )/ 2; + + m_btnOpen->setBounds( rc.left + 2, ypos, buttonSize, buttonSize ); + m_btnClose->setBounds( rc.left + 2, ypos, buttonSize, buttonSize ); + + bool buttonsVisible = ( ypos > m_pView->GetStartRow() && ( ypos + buttonSize ) < m_pView->GetEndRow() ) ? true : false; + + if ( !buttonsVisible ) + { + m_btnOpen->setVisible( false ); + m_btnClose->setVisible( false ); + } + else + { + m_btnOpen->setVisible( !m_bShowChannels ); + m_btnClose->setVisible( m_bShowChannels ); + } + + RECT rcChannels; + rcChannels = rc; + rcChannels.top += ACTOR_NAME_HEIGHT; + + // Create objects for children + for ( int i = 0; i < m_Channels.Size(); i++ ) + { + CChoreoChannelWidget *channel = m_Channels[ i ]; + Assert( channel ); + if ( !channel ) + { + continue; + } + + rcChannels.bottom = rcChannels.top + channel->GetItemHeight(); + + channel->Layout( rcChannels ); + + OffsetRect( &rcChannels, 0, channel->GetItemHeight() ); + + channel->setVisible( m_bShowChannels ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CChoreoActorWidget::GetItemHeight( void ) +{ + int itemHeight = ACTOR_NAME_HEIGHT + 2; + if ( m_bShowChannels ) + { + for ( int i = 0; i < m_Channels.Size(); i++ ) + { + CChoreoChannelWidget *channel = m_Channels[ i ]; + itemHeight += channel->GetItemHeight(); + } + } + return itemHeight; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoActorWidget::redraw( CChoreoWidgetDrawHelper& drawHelper ) +{ + if ( !getVisible() ) + return; + + CChoreoActor *actor = GetActor(); + if ( !actor ) + return; + + RECT rcClient = getBounds(); + + if ( !actor->GetActive() ) + { + RECT rcBg = rcClient; + rcBg.right = rcBg.left + m_pView->GetLabelWidth() ; + InflateRect( &rcBg, -3, -5 ); + + drawHelper.DrawFilledRect( RGB( 220, 220, 220 ), rcBg ); + } + + RECT rcText; + + rcText.left = rcClient.left; + rcText.right = rcClient.left + m_pView->GetLabelWidth(); + rcText.top = rcClient.top; + rcText.bottom = rcClient.top + ACTOR_NAME_HEIGHT; + + drawHelper.DrawColoredLine( COLOR_CHOREO_ACTORLINE, PS_SOLID, 1, 0, rcText.top, + rcClient.right, rcText.top ); + + drawHelper.DrawColoredLine( COLOR_CHOREO_ACTORLINE, PS_SOLID, 1, 0, rcClient.bottom-2, + rcClient.right, rcClient.bottom-2 ); + drawHelper.DrawColoredLine( RGB(200,206,255), PS_SOLID, 1, 0, rcClient.bottom-1, + rcClient.right, rcClient.bottom-1 ); + + drawHelper.DrawColoredLine( COLOR_CHOREO_DIVIDER, PS_SOLID, 1, rcText.right, rcClient.top, + rcText.right, rcClient.bottom-1 ); + + RECT rcName = rcText; + + rcName.left += 18; + char n[ 512 ]; + V_strcpy_safe( n, actor->GetName() ); + + drawHelper.DrawColoredText( "Arial", + m_pView->GetFontSize() + 5, + 1000, + actor->GetActive() ? COLOR_CHOREO_ACTORNAME : COLOR_CHOREO_ACTORNAME_INACTIVE, + rcName, n ); + + if ( !actor->GetActive() ) + { + strcpy( n, "(inactive)" ); + + RECT rcInactive = rcName; + int len = drawHelper.CalcTextWidth( "Arial", m_pView->GetFontSize() - 2, 500, n ); + rcInactive.left = rcInactive.right - len - 5; + rcInactive.top += 3; + rcInactive.bottom = rcInactive.top + m_pView->GetFontSize() - 2; + + drawHelper.DrawColoredText( "Arial", m_pView->GetFontSize() - 2, 500, + COLOR_CHOREO_ACTORNAME_INACTIVE, rcInactive, n ); + } + + rcName.left -= 18; + + + if ( actor->GetFacePoserModelName()[0] ) + { + int textWidth = drawHelper.CalcTextWidth( "Arial", m_pView->GetFontSize() + 5, 1000, actor->GetName() ); + RECT rcModelName = rcName; + rcModelName.left += ( 14 + textWidth + 2 ); + + int fontsize = m_pView->GetFontSize() - 2; + + char shortname[ 512 ]; + Q_FileBase (actor->GetFacePoserModelName(), shortname, sizeof( shortname ) ); + strcat( shortname, ".mdl" ); + + int len = drawHelper.CalcTextWidth( "Arial", fontsize, FW_NORMAL, shortname ); + + rcModelName.left = rcModelName.right - len - 5; + + rcModelName.top = rcModelName.bottom - fontsize; + OffsetRect( &rcModelName, 0, -3 ); + drawHelper.DrawColoredText( "Arial", fontsize, FW_NORMAL, COLOR_CHOREO_LIGHTTEXT, rcModelName, shortname ); + } + if ( m_bShowChannels ) + { + for ( int j = 0; j < GetNumChannels(); j++ ) + { + CChoreoChannelWidget *channel = GetChannel( j ); + if ( channel ) + { + channel->redraw( drawHelper ); + } + + RECT rcChannel = channel->getBounds(); + + drawHelper.DrawColoredLine( COLOR_CHOREO_ACTORLINE, PS_SOLID, 1, rcText.right+1, rcChannel.top, + rcChannel.right, rcChannel.top ); + + drawHelper.DrawColoredLine( COLOR_CHOREO_ACTORLINE, PS_SOLID, 1, rcText.right+1, rcChannel.bottom, + rcChannel.right, rcChannel.bottom ); + + } + return; + } + + OffsetRect( &rcName, m_pView->GetLabelWidth() + 10, 0 ); + rcName.right = w(); + + char sz[ 256 ]; + // count channels and events + int ev = 0; + for ( int i = 0; i < actor->GetNumChannels(); i++ ) + { + CChoreoChannel *ch = actor->GetChannel( i ); + if ( ch ) + { + ev += ch->GetNumEvents(); + } + } + sprintf( sz, "%i channels with %i events hidden", actor->GetNumChannels(), ev ); + drawHelper.DrawColoredText( "Arial", m_pView->GetFontSize(), FW_NORMAL, COLOR_CHOREO_ACTORNAME, rcName, sz ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CChoreoActor +//----------------------------------------------------------------------------- +CChoreoActor *CChoreoActorWidget::GetActor( void ) +{ + return m_pActor; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +//----------------------------------------------------------------------------- +void CChoreoActorWidget::SetActor( CChoreoActor *actor ) +{ + m_pActor = actor; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *channel - +//----------------------------------------------------------------------------- +void CChoreoActorWidget::AddChannel( CChoreoChannelWidget *channel ) +{ + m_Channels.AddToTail( channel ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *channel - +//----------------------------------------------------------------------------- +void CChoreoActorWidget::RemoveChannel( CChoreoChannelWidget *channel ) +{ + m_Channels.FindAndRemove( channel ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : num - +// Output : CChoreoChannelWidget +//----------------------------------------------------------------------------- +CChoreoChannelWidget *CChoreoActorWidget::GetChannel( int num ) +{ + return m_Channels[ num ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoActorWidget::GetNumChannels( void ) +{ + return m_Channels.Size(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float *CChoreoActorWidget::GetSettings( void ) +{ + return m_rgCurrentSetting; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoActorWidget::ResetSettings( void ) +{ + memset( m_rgCurrentSetting, 0, sizeof( m_rgCurrentSetting ) ); +} diff --git a/utils/hlfaceposer/choreoactorwidget.h b/utils/hlfaceposer/choreoactorwidget.h new file mode 100644 index 0000000..b3cfb72 --- /dev/null +++ b/utils/hlfaceposer/choreoactorwidget.h @@ -0,0 +1,118 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#ifndef CHOREOACTORWIDGET_H +#define CHOREOACTORWIDGET_H +#ifdef _WIN32 +#pragma once +#endif + +#include "studio.h" +#include "choreowidget.h" +#include "utlvector.h" +#include "mxBitmapButton.h" +#include "expressions.h" + +class CChoreoActor; +class CChoreoChannelWidget; +class mxCheckBox; +class CChoreoActorWidget; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CActorBitmapButton : public mxBitmapButton +{ +public: + CActorBitmapButton( CChoreoActorWidget *actor, mxWindow *parent, int x, int y, int w, int h, int id = 0, const char *bitmap = 0 ); + + CChoreoActorWidget *GetActor( void ); +private: + + CChoreoActorWidget *m_pActor; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CActorActiveCheckBox : public mxCheckBox +{ +public: + CActorActiveCheckBox( CChoreoActorWidget *actor, mxWindow *parent, int x, int y, int w, int h, const char *label = 0, int id = 0); + + CChoreoActorWidget *GetActor( void ); +private: + + CChoreoActorWidget *m_pActor; +}; + +//----------------------------------------------------------------------------- +// Purpose: The base actor ui widget. Owns the channels +//----------------------------------------------------------------------------- +class CChoreoActorWidget : public CChoreoWidget +{ +public: + typedef CChoreoWidget BaseClass; + + // Construction / destruction + CChoreoActorWidget( CChoreoWidget *parent ); + virtual ~CChoreoActorWidget( void ); + + virtual void Create( void ); + virtual void Layout( RECT& rc ); + + virtual void redraw(CChoreoWidgetDrawHelper& drawHelper); + + // Accessors + CChoreoActor *GetActor( void ); + void SetActor( CChoreoActor *actor ); + + // Manipulate channels + void AddChannel( CChoreoChannelWidget *channel ); + void RemoveChannel( CChoreoChannelWidget *channel ); + CChoreoChannelWidget *GetChannel( int num ); + int GetNumChannels( void ); + + // Override height because we can be open/collapsed and we contain the channels + virtual int GetItemHeight( void ); + + // UI interactions + void DeleteChannel( void ); + void NewChannel( void ); + void MoveChannelUp( void ); + void MoveChannelDown( void ); + + // Expanded view or contracted view + void ShowChannels( bool show ); + bool GetShowChannels( void ); + + float *GetSettings( void ); + + void ResetSettings( void ); + +private: + // Context menu handler + void ShowRightClickMenu( int mx, int my ); + + // The underlying actor + CChoreoActor *m_pActor; + + // Children + CUtlVector < CChoreoChannelWidget * > m_Channels; + + // Expanded mode? + bool m_bShowChannels; + + // Expand/collapse buttons + CActorBitmapButton *m_btnOpen; + CActorBitmapButton *m_btnClose; + + CActorActiveCheckBox *m_cbActive; + + float m_rgCurrentSetting[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ]; +}; + +#endif // CHOREOACTORWIDGET_H diff --git a/utils/hlfaceposer/choreochannelwidget.cpp b/utils/hlfaceposer/choreochannelwidget.cpp new file mode 100644 index 0000000..d41b209 --- /dev/null +++ b/utils/hlfaceposer/choreochannelwidget.cpp @@ -0,0 +1,1207 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include <stdio.h> +#include <mxtk/mxPopupMenu.h> +#include "hlfaceposer.h" +#include "choreochannelwidget.h" +#include "choreoeventwidget.h" +#include "choreoactorwidget.h" +#include "choreochannel.h" +#include "choreowidgetdrawhelper.h" +#include "choreoview.h" +#include "choreoevent.h" +#include "choreoviewcolors.h" +#include "utlrbtree.h" +#include "utllinkedlist.h" +#include "iclosecaptionmanager.h" +#include "PhonemeEditor.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "filesystem.h" + +#define AUDIO_HEIGHT 18 +#define STREAM_FONT "Tahoma" + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *parent - +//----------------------------------------------------------------------------- +CChoreoChannelWidget::CChoreoChannelWidget( CChoreoActorWidget *parent ) +: CChoreoWidget( parent ) +{ + m_pChannel = NULL; + m_pParent = parent; + m_bHasAudio = false; + m_nBaseHeight = 0; + m_nSelectorEventIndex = -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CChoreoChannelWidget::~CChoreoChannelWidget( void ) +{ + for ( int i = 0 ; i < m_Events.Size(); i++ ) + { + CChoreoEventWidget *e = m_Events[ i ]; + delete e; + } + m_Events.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: Create child windows +//----------------------------------------------------------------------------- +void CChoreoChannelWidget::Create( void ) +{ + Assert( m_pChannel ); + + // Create objects for children + for ( int i = 0; i < m_pChannel->GetNumEvents(); i++ ) + { + CChoreoEvent *e = m_pChannel->GetEvent( i ); + Assert( e ); + if ( !e ) + { + continue; + } + + CChoreoEventWidget *eventWidget = new CChoreoEventWidget( this ); + eventWidget->SetEvent( e ); + eventWidget->Create(); + + AddEvent( eventWidget ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// Output : float +//----------------------------------------------------------------------------- +float CChoreoChannelWidget::GetTimeForMousePosition( int mx ) +{ + int dx = mx - m_pView->GetLabelWidth(); + float windowfrac = ( float ) dx / ( float ) ( w() - m_pView->GetLabelWidth() ); + float time = m_pView->GetStartTime() + windowfrac * ( m_pView->GetEndTime() - m_pView->GetStartTime() ); + return time; +} + +static bool EventStartTimeLessFunc( CChoreoEventWidget * const &p1, CChoreoEventWidget * const &p2 ) +{ + CChoreoEventWidget *w1; + CChoreoEventWidget *w2; + + w1 = const_cast< CChoreoEventWidget * >( p1 ); + w2 = const_cast< CChoreoEventWidget * >( p2 ); + + CChoreoEvent *e1; + CChoreoEvent *e2; + + e1 = w1->GetEvent(); + e2 = w2->GetEvent(); + + return e1->GetStartTime() < e2->GetStartTime(); +} + +void CChoreoChannelWidget::LayoutEventInRow( CChoreoEventWidget *event, int row, RECT& rc ) +{ + int itemHeight = BaseClass::GetItemHeight(); + + RECT rcEvent; + rcEvent.left = m_pView->GetPixelForTimeValue( event->GetEvent()->GetStartTime() ); + if ( event->GetEvent()->HasEndTime() ) + { + rcEvent.right = m_pView->GetPixelForTimeValue( event->GetEvent()->GetEndTime() ); + } + else + { + rcEvent.right = rcEvent.left + 8; + } + rcEvent.top = rc.top + ( row ) * itemHeight + 2; + rcEvent.bottom = rc.top + ( row + 1 ) * itemHeight - 2; + event->Layout( rcEvent ); +} + +static bool EventCollidesWithRows( CUtlLinkedList< CChoreoEventWidget *, int >& list, CChoreoEventWidget *event ) +{ + float st = event->GetEvent()->GetStartTime(); + float ed = event->GetEvent()->HasEndTime() ? event->GetEvent()->GetEndTime() : event->GetEvent()->GetStartTime(); + + for ( int i = list.Head(); i != list.InvalidIndex(); i = list.Next( i ) ) + { + CChoreoEvent *test = list[ i ]->GetEvent(); + + float teststart = test->GetStartTime(); + float testend = test->HasEndTime() ? test->GetEndTime() : test->GetStartTime(); + + // See if spans overlap + if ( teststart >= ed ) + continue; + if ( testend <= st ) + continue; + + return true; + } + + return false; +} + +int CChoreoChannelWidget::GetVerticalStackingCount( bool layout, RECT *rc ) +{ + CUtlRBTree< CChoreoEventWidget * > sorted( 0, 0, EventStartTimeLessFunc ); + + CUtlVector< CUtlLinkedList< CChoreoEventWidget *, int > > rows; + + int i; + // Sort items + int c = m_Events.Size(); + for ( i = 0; i < c; i++ ) + { + sorted.Insert( m_Events[ i ] ); + } + + for ( i = sorted.FirstInorder(); i != sorted.InvalidIndex(); i = sorted.NextInorder( i ) ) + { + CChoreoEventWidget *event = sorted[ i ]; + Assert( event ); + if ( !rows.Count() ) + { + rows.AddToTail(); + + CUtlLinkedList< CChoreoEventWidget *, int >& list = rows[ 0 ]; + list.AddToHead( event ); + + if ( layout ) + { + LayoutEventInRow( event, 0, *rc ); + } + continue; + } + + // Does it come totally after what's in rows[0]? + int rowCount = rows.Count(); + bool addrow = true; + + for ( int j = 0; j < rowCount; j++ ) + { + CUtlLinkedList< CChoreoEventWidget *, int >& list = rows[ j ]; + + if ( !EventCollidesWithRows( list, event ) ) + { + // Update row event list + list.AddToHead( event ); + addrow = false; + if ( layout ) + { + LayoutEventInRow( event, j, *rc ); + } + break; + } + } + + if ( addrow ) + { + // Add a new row + int idx = rows.AddToTail(); + CUtlLinkedList< CChoreoEventWidget *, int >& list = rows[ idx ]; + list.AddToHead( event ); + if ( layout ) + { + LayoutEventInRow( event, rows.Count() - 1, *rc ); + } + } + } + + return max( 1, rows.Count() ); +} + +int CChoreoChannelWidget::GetItemHeight( void ) +{ + int itemHeight = BaseClass::GetItemHeight(); + int stackCount = GetVerticalStackingCount( false, NULL ); + + CheckHasAudio(); + + int h = stackCount * itemHeight; + + // Remember the base height + m_nBaseHeight = h; + + if ( m_bHasAudio && m_pView->GetShowCloseCaptionData() ) + { + h += 2 * AUDIO_HEIGHT; + } + + return h; +} + +bool CChoreoChannelWidget::CheckHasAudio() +{ + m_bHasAudio = false; + // Create objects for children + for ( int i = 0; i < m_Events.Size(); i++ ) + { + CChoreoEventWidget *event = m_Events[ i ]; + if ( event->GetEvent()->GetType() == CChoreoEvent::SPEAK ) + { + m_bHasAudio = true; + break; + } + } + return m_bHasAudio; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rc - +//----------------------------------------------------------------------------- +void CChoreoChannelWidget::Layout( RECT& rc ) +{ + setBounds( rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top ); + + GetVerticalStackingCount( true, &rc ); + CheckHasAudio(); + + /* + // Create objects for children + for ( int i = 0; i < m_Events.Size(); i++ ) + { + CChoreoEventWidget *event = m_Events[ i ]; + Assert( event ); + if ( !event ) + { + continue; + } + + RECT rcEvent; + rcEvent.left = m_pView->GetPixelForTimeValue( event->GetEvent()->GetStartTime() ); + if ( event->GetEvent()->HasEndTime() ) + { + rcEvent.right = m_pView->GetPixelForTimeValue( event->GetEvent()->GetEndTime() ); + } + else + { + rcEvent.right = rcEvent.left + 8; + } + rcEvent.top = rc.top + 2; + rcEvent.bottom = rc.bottom - 2; + event->Layout( rcEvent ); + } + */ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoChannelWidget::redraw( CChoreoWidgetDrawHelper& drawHelper ) +{ + if ( !getVisible() ) + return; + + CChoreoChannel *channel = GetChannel(); + if ( !channel ) + return; + + RECT rcText; + rcText = getBounds(); + + rcText.right = m_pView->GetLabelWidth(); + + if ( !channel->GetActive() ) + { + RECT rcBg = rcText; + InflateRect( &rcBg, -5, -5 ); + + drawHelper.DrawFilledRect( RGB( 210, 210, 210 ), rcBg ); + } + + RECT rcName = rcText; + + rcName.left += 20; + char n[ 512 ]; + V_strcpy_safe( n, channel->GetName() ); + + drawHelper.DrawColoredText( "Arial", + m_pView->GetFontSize() + 2, + FW_HEAVY, + channel->GetActive() ? COLOR_CHOREO_CHANNELNAME : COLOR_CHOREO_ACTORNAME_INACTIVE, + rcName, n ); + + if ( !channel->GetActive() ) + { + strcpy( n, "(inactive)" ); + + RECT rcInactive = rcName; + int len = drawHelper.CalcTextWidth( "Arial", m_pView->GetFontSize(), 500, n ); + rcInactive.left = rcInactive.right - len; + //rcInactive.top += 3; + //rcInactive.bottom = rcInactive.top + m_pView->GetFontSize() - 2; + + drawHelper.DrawColoredText( "Arial", m_pView->GetFontSize() - 2, 500, + COLOR_CHOREO_ACTORNAME_INACTIVE, rcInactive, n ); + } + + rcName.left -= 20; + + RECT rcEventArea = getBounds(); + rcEventArea.left = m_pView->GetLabelWidth() + 1; + rcEventArea.top -= 20; + + drawHelper.StartClipping( rcEventArea ); + + if ( m_bHasAudio ) + { + RenderCloseCaptionExpandCollapseRect( drawHelper, rcEventArea ); + if ( m_pView->GetShowCloseCaptionData() ) + { + RenderCloseCaptionExpandCollapseRect( drawHelper, rcEventArea ); + RenderCloseCaptionInfo( drawHelper, rcEventArea ); + RenderCloseCaptions( drawHelper, rcEventArea ); + RenderCloseCaptionSelectors( drawHelper, rcEventArea ); + } + } + + for ( int j = GetNumEvents()-1; j >= 0; j-- ) + { + CChoreoEventWidget *event = GetEvent( j ); + if ( event ) + { + event->redraw( drawHelper ); + } + } + + drawHelper.StopClipping(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rcEventArea - +//----------------------------------------------------------------------------- +void CChoreoChannelWidget::RenderCloseCaptionInfo( CChoreoWidgetDrawHelper& drawHelper, RECT& rcEventArea ) +{ + wchar_t wstr[ 1024 ]; + COLORREF barColor = RGB( 100, 200, 255 ); + + { + RECT rcText = rcEventArea; + rcText.left += 2; + rcText.top = rcEventArea.bottom - 15; + rcText.bottom = rcText.top + 12; + drawHelper.DrawColoredText( "Arial", m_pView->GetFontSize() - 2, 500, + COLOR_CHOREO_TEXT, rcText, "token/data:" ); + } + + // Walk the events looking for SPEAK events (esp if marked as MASTER with >= 1 slave) + for ( int j = GetNumEvents()-1; j >= 0; j-- ) + { + CChoreoEventWidget *event = GetEvent( j ); + CChoreoEvent *e = event->GetEvent(); + + if ( e->GetType() != CChoreoEvent::SPEAK ) + continue; + + if ( e->GetCloseCaptionType() == CChoreoEvent::CC_SLAVE ) + continue; + + char const *label = ""; + + bool showState = false; + bool stateValid = false; + + if ( e->GetCloseCaptionType() == CChoreoEvent::CC_MASTER ) + { + showState = true; + if ( e->GetNumSlaves() >= 1 ) + { + barColor = RGB( 100, 200, 255 ); + label = e->GetCloseCaptionToken(); + } + else + { + barColor = RGB( 100, 150, 100 ); + label = e->GetParameters(); + } + + char cctoken[ CChoreoEvent::MAX_CCTOKEN_STRING ]; + if ( e->GetPlaybackCloseCaptionToken( cctoken, sizeof( cctoken ) ) ) + { + stateValid = closecaptionmanager->LookupUnicodeText( GetCloseCaptionLanguageId(), cctoken, wstr, sizeof( wstr ) / sizeof( wchar_t ) ); + } + } + else + { + barColor = RGB( 150, 150, 150 ); + label = "-disabled-"; + } + + // Found one!!! + RECT rcEvent = event->getBounds(); + + float bestEndTime = max( e->GetEndTime(), e->GetLastSlaveEndTime() ); + int pixeloffset = (int)( ( bestEndTime - e->GetStartTime() ) * m_pView->GetPixelsPerSecond() + 0.5f ); + + rcEvent.right = rcEvent.left + pixeloffset; + rcEvent.top = rcEventArea.bottom - 3; + rcEvent.bottom = rcEventArea.bottom; + + + drawHelper.DrawFilledRect( barColor, rcEvent ); + + RECT rcTriangle; + rcTriangle = rcEvent; + rcTriangle.right = rcTriangle.left + 3; + rcTriangle.left -= 3; + + OffsetRect( &rcTriangle, 0, -6 ); + rcTriangle.bottom += 6; + drawHelper.DrawTriangleMarker( rcTriangle, barColor, true ); + + rcTriangle.left = rcEvent.right - 3; + rcTriangle.right = rcEvent.right + 3; + + drawHelper.DrawTriangleMarker( rcTriangle, barColor, true ); + + RECT rcText = rcEvent; + rcText.bottom = rcText.top + 12; + OffsetRect( &rcText, 5, -12 ); + + if ( showState ) + { + int stateMarkWidth = 12; + RECT rcState = rcText; + rcState.right = rcState.left + stateMarkWidth; + rcText.left += stateMarkWidth; + + COLORREF symColor = stateValid ? RGB( 40, 100, 40 ) : RGB( 200, 40, 40 ); + + drawHelper.DrawColoredTextCharset( + "Marlett", + m_pView->GetFontSize() - 2, + 500, + SYMBOL_CHARSET, + symColor, + rcState, + stateValid ? "a" : "r" ); + + } + + if ( e->IsSuppressingCaptionAttenuation() ) + { + drawHelper.DrawColoredText( "Arial", m_pView->GetFontSize() - 2, 500, + RGB( 80, 80, 100 ), rcText, "%s [no attenuate]", label ); + + } + else + { + drawHelper.DrawColoredText( "Arial", m_pView->GetFontSize() - 2, 500, + RGB( 80, 80, 100 ), rcText, label ); + } + + + + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rcEventArea - +//----------------------------------------------------------------------------- +void CChoreoChannelWidget::RenderCloseCaptions( CChoreoWidgetDrawHelper& drawHelper, RECT& rcEventArea ) +{ + { + RECT rcText = rcEventArea; + rcText.top = rcEventArea.top + m_nBaseHeight + AUDIO_HEIGHT + 5; + rcText.bottom = rcText.top + 12; + rcText.left += 12; + drawHelper.DrawColoredText( "Arial", m_pView->GetFontSize() - 2, 500, + COLOR_CHOREO_TEXT, rcText, "%s", CSentence::NameForLanguage( GetCloseCaptionLanguageId() ) ); + + // Previous + GetCloseCaptionLanguageRect( rcText, true ); + drawHelper.DrawColoredTextCharset( + "Marlett", + m_pView->GetFontSize(), + 500, + SYMBOL_CHARSET, + COLOR_CHOREO_TEXT, + rcText, + "3" ); + + // Next + GetCloseCaptionLanguageRect( rcText, false ); + drawHelper.DrawColoredTextCharset( + "Marlett", + m_pView->GetFontSize(), + 500, + SYMBOL_CHARSET, + COLOR_CHOREO_TEXT, + rcText, + "4" ); + } + + // Walk the events looking for SPEAK events (esp if marked as MASTER with >= 1 slave) + for ( int j = GetNumEvents()-1; j >= 0; j-- ) + { + CChoreoEventWidget *event = GetEvent( j ); + CChoreoEvent *e = event->GetEvent(); + + if ( e->GetType() != CChoreoEvent::SPEAK ) + continue; + + if ( e->GetCloseCaptionType() == CChoreoEvent::CC_SLAVE || + e->GetCloseCaptionType() == CChoreoEvent::CC_DISABLED ) + continue; + + char cctoken[ CChoreoEvent::MAX_CCTOKEN_STRING ]; + + bool valid = e->GetPlaybackCloseCaptionToken( cctoken, sizeof( cctoken ) ); + if ( !valid ) + continue; + + wchar_t wstr[ 1024 ]; + + valid = closecaptionmanager->LookupStrippedUnicodeText( GetCloseCaptionLanguageId(), cctoken, wstr, sizeof( wstr ) / sizeof( wchar_t ) ); + + // Found one!!! + RECT rcEvent = event->getBounds(); + + float bestEndTime = max( e->GetEndTime(), e->GetLastSlaveEndTime() ); + int pixeloffset = (int)( ( bestEndTime - e->GetStartTime() ) * m_pView->GetPixelsPerSecond() + 0.5f ); + + rcEvent.right = rcEvent.left + pixeloffset; + rcEvent.top = rcEventArea.top + m_nBaseHeight + AUDIO_HEIGHT + 5; + rcEvent.bottom = rcEvent.top + 12; + rcEvent.left += 5; + + COLORREF textColor = valid ? RGB( 80, 80, 100 ) : RGB( 225, 40, 40 ); + + drawHelper.DrawColoredTextW( STREAM_FONT, m_pView->GetFontSize() - 2, 500, + textColor, rcEvent, wstr ); + + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CChoreoChannel +//----------------------------------------------------------------------------- +CChoreoChannel *CChoreoChannelWidget::GetChannel( void ) +{ + return m_pChannel; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *channel - +//----------------------------------------------------------------------------- +void CChoreoChannelWidget::SetChannel( CChoreoChannel *channel ) +{ + m_pChannel = channel; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void CChoreoChannelWidget::AddEvent( CChoreoEventWidget *event ) +{ + m_Events.AddToTail( event ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void CChoreoChannelWidget::RemoveEvent( CChoreoEventWidget *event ) +{ + m_Events.FindAndRemove( event ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : num - +// Output : CChoreoEventWidget +//----------------------------------------------------------------------------- +CChoreoEventWidget *CChoreoChannelWidget::GetEvent( int num ) +{ + return m_Events[ num ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoChannelWidget::GetNumEvents( void ) +{ + return m_Events.Size(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void CChoreoChannelWidget::MoveEventToTail( CChoreoEventWidget *event ) +{ + for ( int i = 0; i < GetNumEvents(); i++ ) + { + CChoreoEventWidget *ew = GetEvent( i ); + if ( ew == event ) + { + m_Events.Remove( i ); + m_Events.AddToTail( ew ); + break; + } + } +} + +int CChoreoChannelWidget::GetChannelItemUnderMouse( int mx, int my ) +{ + m_nSelectorEventIndex = -1; + + if ( !m_bHasAudio ) + return CLOSECAPTION_NONE; + + RECT rcCCArea; + GetCloseCaptionExpandCollapseRect( rcCCArea ); + + POINT pt; + pt.x = mx; + pt.y = my; + + if ( PtInRect( &rcCCArea, pt ) ) + { + return CLOSECAPTION_EXPANDCOLLAPSE; + } + + // previous + GetCloseCaptionLanguageRect( rcCCArea, true ); + if ( PtInRect( &rcCCArea, pt ) ) + { + return CLOSECAPTION_PREVLANGUAGE; + } + + // next language + GetCloseCaptionLanguageRect( rcCCArea, false ); + if ( PtInRect( &rcCCArea, pt ) ) + { + return CLOSECAPTION_NEXTLANGUAGE; + } + + CUtlVector< CloseCaptionInfo > vecSelectors; + GetCloseCaptions( vecSelectors ); + int c = vecSelectors.Count(); + if ( vecSelectors.Count() > 0 ) + { + int i; + for ( i = 0; i < c; ++i ) + { + CloseCaptionInfo& check = vecSelectors[ i ]; + if ( check.isSelector && PtInRect( &check.rcSelector, pt ) ) + { + m_nSelectorEventIndex = check.eventindex; + return CLOSECAPTION_SELECTOR; + } + } + + for ( i = 0; i < c; ++i ) + { + CloseCaptionInfo& check = vecSelectors[ i ]; + if ( PtInRect( &check.rcCaption, pt ) ) + { + m_nSelectorEventIndex = check.eventindex; + return CLOSECAPTION_CAPTION; + } + } + } + + return CLOSECAPTION_NONE; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoChannelWidget::HandleSelectorClicked() +{ + if ( m_nSelectorEventIndex < 0 ) + return; + + if ( m_nSelectorEventIndex >= m_Events.Count() ) + return; + + CChoreoEvent *event = GetEvent( m_nSelectorEventIndex )->GetEvent(); + SetUsingCombinedFieldByTokenName( event->GetCloseCaptionToken(), !event->IsUsingCombinedFile() ); +} + +void CChoreoChannelWidget::SetUsingCombinedFieldByTokenName( char const *token, bool usingcombinedfile ) +{ + int c = GetNumEvents(); + for ( int i = 0; i < c; ++i ) + { + CChoreoEvent *e = GetEvent( i )->GetEvent(); + if ( !Q_stricmp( e->GetCloseCaptionToken(), token ) ) + { + e->SetUsingCombinedFile( usingcombinedfile ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CChoreoEvent +//----------------------------------------------------------------------------- +CChoreoEvent *CChoreoChannelWidget::GetCaptionClickedEvent() +{ + if ( m_nSelectorEventIndex < 0 ) + return NULL; + + if ( m_nSelectorEventIndex >= m_Events.Count() ) + return NULL; + + CChoreoEvent *event = GetEvent( m_nSelectorEventIndex )->GetEvent(); + return event; +} + +void CChoreoChannelWidget::GetCloseCaptionExpandCollapseRect( RECT& rc ) +{ + Assert( m_bHasAudio ); + + rc = getBounds(); + rc.left = m_pView->GetLabelWidth() + 2; + rc.right = rc.left + 12; + + rc.top += 2; + rc.bottom = rc.top + 12; +} + +void CChoreoChannelWidget::GetCloseCaptionLanguageRect( RECT& rc, bool previous ) +{ + Assert( m_bHasAudio ); + + RECT rcEventArea = getBounds(); + rcEventArea.left = m_pView->GetLabelWidth() + 1; + rcEventArea.top -= 20; + + rc = rcEventArea; + rc.top = rcEventArea.top + m_nBaseHeight + AUDIO_HEIGHT + 5; + rc.bottom = rc.top + 12; + rc.left += 2; + rc.right = rc.left + 12; + + if ( !previous ) + { + int textlen = CChoreoWidgetDrawHelper::CalcTextWidth + ( + "Arial", + m_pView->GetFontSize()-2, + 500, + CSentence::NameForLanguage( GetCloseCaptionLanguageId() ) + ); + + OffsetRect( &rc, textlen + 10, 0 ); + } +} + +void CChoreoChannelWidget::RenderCloseCaptionSelectors( CChoreoWidgetDrawHelper& drawHelper, RECT& rcEventArea ) +{ + CUtlVector< CloseCaptionInfo > vecSelectors; + GetCloseCaptions( vecSelectors ); + int c = vecSelectors.Count(); + if ( vecSelectors.Count() > 0 ) + { + for ( int i = 0; i < c; ++i ) + { + CloseCaptionInfo& check = vecSelectors[ i ]; + + if ( !check.isSelector ) + continue; + + CChoreoEventWidget *e = GetEvent( check.eventindex ); + if ( !e ) + continue; + + CChoreoEvent *event = e->GetEvent(); + + bool upArrow = !event->IsUsingCombinedFile(); + COLORREF clr = RGB( 63, 63, 63 ); // upArrow ? RGB( 255, 0, 0 ) : RGB( 0, 0, 255 ); + + RECT rc = check.rcSelector; + + POINT endpt; + endpt.x = rc.right - 2; + + if ( upArrow ) + { + endpt.y = rc.top - 9; + } + else + { + endpt.y = rc.bottom + 9; + } + + POINT startpt; + startpt.x = ( rc.left + rc.right ) * 0.5; + startpt.y = ( rc.top + rc.bottom ) * 0.5; + + drawHelper.DrawCircle( + clr, + endpt.x, + endpt.y, + 3 , true ); + + drawHelper.DrawColoredLine( clr, PS_SOLID, 1, startpt.x, startpt.y, endpt.x, endpt.y ); + + + drawHelper.DrawCircle( + clr, + startpt.x, + startpt.y, + 7, true ); + } + } +} + +void CChoreoChannelWidget::GetCloseCaptions( CUtlVector< CloseCaptionInfo >& selectors ) +{ + selectors.RemoveAll(); + + // Walk the events looking for SPEAK events (esp if marked as MASTER with >= 1 slave) + for ( int j = GetNumEvents()-1; j >= 0; j-- ) + { + CChoreoEventWidget *event = GetEvent( j ); + CChoreoEvent *e = event->GetEvent(); + + if ( e->GetType() != CChoreoEvent::SPEAK ) + continue; + + CChoreoEvent::CLOSECAPTION capType = e->GetCloseCaptionType(); + + if ( capType == CChoreoEvent::CC_SLAVE ) + continue; + + bool isSelector = ( e->GetNumSlaves() >= 1 ) && capType == CChoreoEvent::CC_MASTER; + + // Found one!!! + RECT rcEvent = event->getBounds(); + RECT rcCaption = rcEvent; + + rcEvent.right = rcEvent.left + 16; + OffsetRect( &rcEvent, -16, rcEvent.bottom - rcEvent.top ); + rcEvent.bottom = rcEvent.top + 16; + + CloseCaptionInfo ccs; + ccs.rcSelector = rcEvent; + ccs.isSelector = isSelector; + + rcCaption.top += rcEvent.bottom - rcEvent.top; + + RECT rcEventArea = getBounds(); + + rcCaption.bottom = rcEventArea.bottom; + + // Now compute true right edge + float bestEndTime = max( e->GetEndTime(), e->GetLastSlaveEndTime() ); + int pixeloffset = (int)( ( bestEndTime - e->GetStartTime() ) * m_pView->GetPixelsPerSecond() + 0.5f ); + rcCaption.right = rcCaption.left + pixeloffset; + + ccs.rcCaption = rcCaption; + + ccs.eventindex = j; + selectors.AddToTail( ccs ); + } +} + + +void CChoreoChannelWidget::RenderCloseCaptionExpandCollapseRect( CChoreoWidgetDrawHelper& drawHelper, RECT& rcEventArea ) +{ + if ( !m_bHasAudio ) + return; + + RECT rcCCArea; + GetCloseCaptionExpandCollapseRect( rcCCArea ); + + COLORREF symColor = RGB( 100, 100, 100 ); + + drawHelper.DrawColoredTextCharset( + "Marlett", + m_pView->GetFontSize(), + 900, + SYMBOL_CHARSET, + symColor, + rcCCArea, + m_pView->GetShowCloseCaptionData() ? "6" : "4" ); +} + +void CChoreoChannelWidget::GetMasterAndSlaves( CChoreoEvent *master, CUtlVector< CChoreoEvent * >& fulllist ) +{ + // Old + int c = GetNumEvents(); + int i; + for ( i = 0; i < c; ++i ) + { + CChoreoEvent *e = GetEvent( i )->GetEvent(); + if ( !Q_stricmp( master->GetCloseCaptionToken(), e->GetCloseCaptionToken() ) ) + { + if ( fulllist.Find( e ) == fulllist.InvalidIndex() ) + { + fulllist.AddToTail( e ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rcBounds - +//----------------------------------------------------------------------------- +void CChoreoChannelWidget::redrawStatus( CChoreoWidgetDrawHelper& drawHelper, RECT& rcClient, int areaUnderMouse ) +{ + if ( !getVisible() ) + return; + + if ( areaUnderMouse != CLOSECAPTION_CAPTION ) + return; + + CChoreoEvent *e = GetCaptionClickedEvent(); + if ( !e ) + return; + + int deflateborder = 1; + int fontsize = 9; + + // Now draw the label + RECT rcEventLabel; + rcEventLabel = rcClient; + + InflateRect( &rcEventLabel, 0, -deflateborder ); + + // rcEventLabel.top += 2; + rcEventLabel.left += 2; + //rcEventLabel.top = rcEventLabel.bottom - 2 * ( fontsize + 2 ) - 1; + //rcEventLabel.bottom = rcEventLabel.top + fontsize + 2; + + /* + HDC dc = drawHelper.GrabDC(); + + int leftAdd = 16; + + if ( CChoreoEventWidget::GetImage( event->GetType() ) ) + { + mxbitmapdata_t *image = CChoreoEventWidget::GetImage( event->GetType() ); + if ( image ) + { + RECT rcFixed = rcEventLabel; + drawHelper.OffsetSubRect( rcFixed ); + DrawBitmapToDC( dc, rcFixed.left, rcFixed.top, leftAdd, leftAdd, + *image ); + } + } + + // Draw Type Name: + //rcEventLabel.top -= 4; + + rcEventLabel.left = rcClient.left + 32; + rcEventLabel.bottom = rcEventLabel.top + fontsize + 2; + // OffsetRect( &rcEventLabel, 0, 2 ); + + int len = drawHelper.CalcTextWidth( "Arial", fontsize, FW_NORMAL, "%s event \"%s\"", + event->NameForType( event->GetType() ), event->GetName() ); + drawHelper.DrawColoredText( "Arial", fontsize, FW_NORMAL, COLOR_INFO_TEXT, rcEventLabel, "%s event \"%s\"", + event->NameForType( event->GetType() ), event->GetName() ); + + OffsetRect( &rcEventLabel, 0, fontsize + 2 ); + + drawHelper.DrawColoredText( "Arial", fontsize, FW_NORMAL, COLOR_INFO_TEXT, + rcEventLabel, "parameters \"%s\"", GetLabelText() ); + */ + + char const *label = ""; + + bool showState = false; + bool stateValid = false; + + wchar_t wstr[ 1024 ]; + COLORREF labelColor = COLOR_INFO_TEXT; + + if ( e->GetCloseCaptionType() == CChoreoEvent::CC_MASTER ) + { + showState = true; + if ( e->GetNumSlaves() >= 1 ) + { + label = e->GetCloseCaptionToken(); + } + else + { + label = e->GetParameters(); + } + } + else if ( e->GetCloseCaptionType() == CChoreoEvent::CC_SLAVE ) + { + showState = true; + label = e->GetCloseCaptionToken(); + } + else + { + label = "-disabled-"; + } + + char cctoken[ CChoreoEvent::MAX_CCTOKEN_STRING ]; + if ( showState && e->GetPlaybackCloseCaptionToken( cctoken, sizeof( cctoken ) ) ) + { + stateValid = closecaptionmanager->LookupUnicodeText( GetCloseCaptionLanguageId(), cctoken, wstr, sizeof( wstr ) / sizeof( wchar_t ) ); + } + + RECT rcText = rcEventLabel; + + rcText.left += 250; + rcText.bottom = rcText.top + fontsize + 1; + + if ( showState ) + { + int stateMarkWidth = 12; + RECT rcState = rcText; + rcState.right = rcState.left + stateMarkWidth; + rcText.left += stateMarkWidth; + + COLORREF symColor = stateValid ? RGB( 40, 100, 40 ) : RGB( 200, 40, 40 ); + + drawHelper.DrawColoredTextCharset( + "Marlett", + fontsize+2, + 500, + SYMBOL_CHARSET, + symColor, + rcState, + stateValid ? "a" : "r" ); + + } + + drawHelper.DrawColoredText( "Arial", fontsize, 500, + labelColor, rcText, "closecaption token: %s", label ); + + RECT saveText = rcText; + + COLORREF statusClr = RGB( 20, 150, 20 ); + + if ( e->GetCloseCaptionType() != CChoreoEvent::CC_DISABLED ) + { + if ( e->GetNumSlaves() >= 1 || + e->GetCloseCaptionType() == CChoreoEvent::CC_SLAVE ) + { + + bool combinedValid = m_pView->ValidateCombinedSoundCheckSum( e ); + + OffsetRect( &rcText, 0, fontsize + 3 ); + + char cf[ 256 ]; + Q_strncpy( cf, "(no file)", sizeof( cf ) ); + + // Get the filename, including expansion for gender + e->ComputeCombinedBaseFileName( cf, sizeof( cf ), e->IsCombinedUsingGenderToken() ); + bool gendermacro = Q_stristr( cf, SOUNDGENDER_MACRO ) ? true : false; + + char exist[ 256 ]; + + if ( gendermacro ) + { + bool valid[2]; + char actualfile[ 256 ]; + soundemitter->GenderExpandString( GENDER_MALE, cf, actualfile, sizeof( actualfile ) ); + valid[ 0 ] = filesystem->FileExists( actualfile ); + soundemitter->GenderExpandString( GENDER_FEMALE, cf, actualfile, sizeof( actualfile ) ); + valid[ 1 ] = filesystem->FileExists( actualfile ); + + if ( !valid[ 0 ] || !valid[ 1 ] ) + { + statusClr = RGB( 255, 0, 0 ); + } + + Q_snprintf( exist, sizeof( exist ), "%s", valid ? "exist" : "missing!" ); + } + else + { + bool valid = filesystem->FileExists( cf ); + if ( !valid ) + { + statusClr = RGB( 255, 0, 0 ); + } + + Q_snprintf( exist, sizeof( exist ), "%s", valid ? "exists" : "missing!" ); + } + + RECT rcPartial = rcText; + + char sz[ 256 ]; + Q_snprintf( sz, sizeof( sz ), + "combined file active [ %s ] gender[ %s ] up-to-date[ ", + e->IsUsingCombinedFile() ? "yes" : "no", + e->IsCombinedUsingGenderToken() ? "yes" : "no" ); + + int len = drawHelper.CalcTextWidth( "Arial", fontsize, 500, sz ); + + drawHelper.DrawColoredText( "Arial", fontsize, 500, + labelColor, rcPartial, sz ); + + rcPartial.left += len; + + Q_snprintf( sz, sizeof( sz ), + "%s", + combinedValid ? "yes" : "no" ); + + len = drawHelper.CalcTextWidth( "Arial", fontsize, 500, sz ); + + drawHelper.DrawColoredText( "Arial", fontsize, 500, + combinedValid ? RGB( 20, 150, 20 ) : RGB( 255, 0, 0 ), + rcPartial, sz ); + + rcPartial.left += len; + + Q_snprintf( sz, sizeof( sz ), + " ]: %s, %s ", + cf, + gendermacro ? "files" : "file" ); + + len = drawHelper.CalcTextWidth( "Arial", fontsize, 500, sz ); + + drawHelper.DrawColoredText( "Arial", fontsize, 500, + labelColor, rcPartial, sz ); + + rcPartial.left += len; + + drawHelper.DrawColoredText( "Arial", fontsize, 500, + statusClr, rcPartial, exist ); + + } + + rcText = saveText; + + OffsetRect( &rcText, 400, 0 ); + + // Print out script file for sound + int soundindex = soundemitter->GetSoundIndex( cctoken ); + if ( soundindex >= 0 ) + { + char const *scriptfile = soundemitter->GetSourceFileForSound( soundindex ); + Assert( scriptfile ); + if ( scriptfile ) + { + drawHelper.DrawColoredText( "Arial", fontsize, 500, + labelColor, rcText, "sound script: %s", scriptfile ); + } + } + else + { + drawHelper.DrawColoredText( "Arial", fontsize, 500, + RGB( 255, 0, 0 ), rcText, "sound not in game_sounds script files!" ); + } + } +}
\ No newline at end of file diff --git a/utils/hlfaceposer/choreochannelwidget.h b/utils/hlfaceposer/choreochannelwidget.h new file mode 100644 index 0000000..7558993 --- /dev/null +++ b/utils/hlfaceposer/choreochannelwidget.h @@ -0,0 +1,120 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#ifndef CHOREOCHANNELWIDGET_H +#define CHOREOCHANNELWIDGET_H +#ifdef _WIN32 +#pragma once +#endif + +#include "choreowidget.h" +#include "utlvector.h" + +class CChoreoEventWidget; +class CChoreoActorWidget; +class CChoreoChannel; +class CChoreoChannelWidget; + +//----------------------------------------------------------------------------- +// Purpose: The channel container +//----------------------------------------------------------------------------- +class CChoreoChannelWidget : public CChoreoWidget +{ +public: + typedef CChoreoWidget BaseClass; + + enum + { + FULLMENU = 0, + NEWEVENTMENU + }; + + enum + { + CLOSECAPTION_NONE = 0, + CLOSECAPTION_EXPANDCOLLAPSE, + CLOSECAPTION_PREVLANGUAGE, + CLOSECAPTION_NEXTLANGUAGE, + CLOSECAPTION_SELECTOR, + CLOSECAPTION_CAPTION, + }; + + // Construction + CChoreoChannelWidget( CChoreoActorWidget *parent ); + virtual ~CChoreoChannelWidget( void ); + + virtual void Create( void ); + virtual void Layout( RECT& rc ); + + virtual int GetItemHeight( void ); + + virtual void redraw(CChoreoWidgetDrawHelper& drawHelper); + virtual void redrawStatus( CChoreoWidgetDrawHelper& drawHelper, RECT& rcClient, int areaUnderMouse ); + + // Accessors + CChoreoChannel *GetChannel( void ); + void SetChannel( CChoreoChannel *channel ); + + // Manipulate child events + void AddEvent( CChoreoEventWidget *event ); + void RemoveEvent( CChoreoEventWidget *event ); + + void MoveEventToTail( CChoreoEventWidget *event ); + + CChoreoEventWidget *GetEvent( int num ); + int GetNumEvents( void ); + + // Determine time for click position + float GetTimeForMousePosition( int mx ); + + int GetChannelItemUnderMouse( int mx, int my ); + + CChoreoEvent *GetCaptionClickedEvent(); + void GetMasterAndSlaves( CChoreoEvent *master, CUtlVector< CChoreoEvent * >& fulllist ); + + void HandleSelectorClicked(); + +private: + + struct CloseCaptionInfo + { + bool isSelector; + RECT rcSelector; + RECT rcCaption; + int eventindex; + }; + + void GetCloseCaptionExpandCollapseRect( RECT& rc ); + void GetCloseCaptionLanguageRect( RECT& rc, bool previous ); + void GetCloseCaptions( CUtlVector< CloseCaptionInfo >& selectors ); + + int GetVerticalStackingCount( bool dolayout, RECT* rc ); + void LayoutEventInRow( CChoreoEventWidget *event, int row, RECT& rc ); + + void RenderCloseCaptionInfo( CChoreoWidgetDrawHelper& drawHelper, RECT& rcEventArea ); + void RenderCloseCaptions( CChoreoWidgetDrawHelper& drawHelper, RECT& rcEventArea ); + void RenderCloseCaptionExpandCollapseRect( CChoreoWidgetDrawHelper& drawHelper, RECT& rcEventArea ); + void RenderCloseCaptionSelectors( CChoreoWidgetDrawHelper& drawHelper, RECT& rcEventArea ); + + void SetUsingCombinedFieldByTokenName( char const *token, bool usingcombinedfile ); + + bool CheckHasAudio(); + + // The actor to whom we belong + CChoreoActorWidget *m_pParent; + + // The underlying scene object + CChoreoChannel *m_pChannel; + + // Children + CUtlVector < CChoreoEventWidget * > m_Events; + bool m_bHasAudio; + int m_nBaseHeight; + + int m_nSelectorEventIndex; +}; + +#endif // CHOREOCHANNELWIDGET_H diff --git a/utils/hlfaceposer/choreoeventwidget.cpp b/utils/hlfaceposer/choreoeventwidget.cpp new file mode 100644 index 0000000..01affbb --- /dev/null +++ b/utils/hlfaceposer/choreoeventwidget.cpp @@ -0,0 +1,784 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include <stdio.h> +#include "hlfaceposer.h" +#include "choreoeventwidget.h" +#include "choreochannelwidget.h" +#include "choreowidgetdrawhelper.h" +#include "choreoview.h" +#include "choreoevent.h" +#include "choreochannel.h" +#include "choreoscene.h" +#include "choreoviewcolors.h" +#include "ifaceposersound.h" +#include "snd_audio_source.h" +#include "RampTool.h" + +// Static members +mxbitmapdata_t CChoreoEventWidget::m_Bitmaps[ FP_NUM_BITMAPS ]; +mxbitmapdata_t CChoreoEventWidget::m_ResumeConditionBitmap; +mxbitmapdata_t CChoreoEventWidget::m_LockBodyFacingBitmap; +//----------------------------------------------------------------------------- +// Purpose: +// Input : *parent - +//----------------------------------------------------------------------------- +CChoreoEventWidget::CChoreoEventWidget( CChoreoWidget *parent ) + : CChoreoWidget( parent ) +{ + m_pEvent = NULL; + m_pParent = parent; + m_pWaveFile = NULL; + m_nDurationRightEdge= 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CChoreoEventWidget::~CChoreoEventWidget( void ) +{ + delete m_pWaveFile; + m_pWaveFile = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoEventWidget::Create( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CChoreoEventWidget::GetLabelText( void ) +{ + static char label[ 256 ]; + if ( GetEvent()->GetType() == CChoreoEvent::EXPRESSION ) + { + sprintf( label, "%s : %s", GetEvent()->GetParameters(), GetEvent()->GetParameters2() ); + } + else + { + V_strcpy_safe( label, GetEvent()->GetParameters() ); + } + + return label; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CChoreoEventWidget::GetDurationRightEdge( void ) +{ + return m_nDurationRightEdge; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rc - +//----------------------------------------------------------------------------- +void CChoreoEventWidget::Layout( RECT& rc ) +{ + int requestedW = rc.right - rc.left; + + m_nDurationRightEdge = requestedW; + + setBounds( rc.left, rc.top, requestedW, rc.bottom - rc.top ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// &rcWAV - +//----------------------------------------------------------------------------- +void CChoreoEventWidget::DrawRelativeTags( CChoreoWidgetDrawHelper& drawHelper, RECT &rcWAV, float length, CChoreoEvent *event ) +{ + for ( int i = 0; i < event->GetNumRelativeTags(); i++ ) + { + CEventRelativeTag *tag = event->GetRelativeTag( i ); + if ( !tag ) + continue; + + // + int left = rcWAV.left + (int)( tag->GetPercentage() * ( float )( rcWAV.right - rcWAV.left ) + 0.5f ); + + RECT rcMark; + rcMark = rcWAV; + rcMark.top -= 2; + rcMark.bottom = rcMark.top + 6; + rcMark.left = left - 3; + rcMark.right = left + 3; + + drawHelper.DrawTriangleMarker( rcMark, RGB( 0, 100, 250 ) ); + + RECT rcText; + rcText = rcMark; + rcText.top -= 12; + + int len = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, tag->GetName() ); + rcText.left = left - len / 2; + rcText.right = rcText.left + len + 2; + + rcText.bottom = rcText.top + 10; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 0, 100, 200 ), rcText, tag->GetName() ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// &rcWAV - +//----------------------------------------------------------------------------- +void CChoreoEventWidget::DrawAbsoluteTags( CChoreoWidgetDrawHelper& drawHelper, RECT &rcWAV, float length, CChoreoEvent *event ) +{ + for ( int i = 0; i < event->GetNumAbsoluteTags( CChoreoEvent::PLAYBACK ); i++ ) + { + CEventAbsoluteTag *tag = event->GetAbsoluteTag( CChoreoEvent::PLAYBACK, i ); + if ( !tag ) + continue; + + // + int left = rcWAV.left + (int)( tag->GetPercentage() * ( float )( rcWAV.right - rcWAV.left ) + 0.5f ); + + RECT rcMark; + rcMark = rcWAV; + rcMark.top -= 2; + rcMark.bottom = rcMark.top + 6; + rcMark.left = left - 3; + rcMark.right = left + 3; + + drawHelper.DrawTriangleMarker( rcMark, RGB( 0, 100, 250 ) ); + + RECT rcText; + rcText = rcMark; + rcText.top -= 12; + + int len = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, tag->GetName() ); + rcText.left = left - len / 2; + rcText.right = rcText.left + len + 2; + + rcText.bottom = rcText.top + 10; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 0, 100, 200 ), rcText, tag->GetName() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rcBounds - +//----------------------------------------------------------------------------- +void CChoreoEventWidget::redrawStatus( CChoreoWidgetDrawHelper& drawHelper, RECT& rcClient ) +{ + if ( !getVisible() ) + return; + + CChoreoEvent *event = GetEvent(); + if ( !event ) + return; + + int deflateborder = 1; + int fontsize = 9; + + HDC dc = drawHelper.GrabDC(); + + // Now draw the label + RECT rcEventLabel; + rcEventLabel = rcClient; + + InflateRect( &rcEventLabel, 0, -deflateborder ); + + // rcEventLabel.top += 2; + rcEventLabel.left += 2; + //rcEventLabel.top = rcEventLabel.bottom - 2 * ( fontsize + 2 ) - 1; + //rcEventLabel.bottom = rcEventLabel.top + fontsize + 2; + + int leftAdd = 16; + + if ( CChoreoEventWidget::GetImage( event->GetType() ) ) + { + mxbitmapdata_t *image = CChoreoEventWidget::GetImage( event->GetType() ); + if ( image ) + { + RECT rcFixed = rcEventLabel; + drawHelper.OffsetSubRect( rcFixed ); + DrawBitmapToDC( dc, rcFixed.left, rcFixed.top, leftAdd, leftAdd, + *image ); + } + } + + OffsetRect( &rcEventLabel, leftAdd, 0 ); + + if ( event->IsResumeCondition() ) + { + RECT rc = rcEventLabel; + OffsetRect( &rcEventLabel, 16, 0 ); + rc.right = rc.left + leftAdd; + rc.bottom = rc.top + leftAdd; + + RECT rcFixed = rc; + drawHelper.OffsetSubRect( rcFixed ); + DrawBitmapToDC( dc, rcFixed.left, rcFixed.top, + rcFixed.right - rcFixed.left, rcFixed.bottom - rcFixed.top, + *CChoreoEventWidget::GetPauseImage() ); + } + + if ( event->IsLockBodyFacing() ) + { + RECT rc = rcEventLabel; + OffsetRect( &rcEventLabel, 16, 0 ); + rc.right = rc.left + leftAdd; + rc.bottom = rc.top + leftAdd; + + RECT rcFixed = rc; + drawHelper.OffsetSubRect( rcFixed ); + DrawBitmapToDC( dc, rcFixed.left, rcFixed.top, + rcFixed.right - rcFixed.left, rcFixed.bottom - rcFixed.top, + *CChoreoEventWidget::GetLockImage() ); + } + + // Draw Type Name: + OffsetRect( &rcEventLabel, 2, 1 ); + + rcEventLabel.left = rcClient.left + 32; + rcEventLabel.bottom = rcEventLabel.top + fontsize + 2; + // OffsetRect( &rcEventLabel, 0, 2 ); + + drawHelper.CalcTextWidth( "Arial", fontsize, FW_NORMAL, "%s event \"%s\"", event->NameForType( event->GetType() ), event->GetName() ); + drawHelper.DrawColoredText( "Arial", fontsize, FW_NORMAL, COLOR_INFO_TEXT, rcEventLabel, "%s event \"%s\"", event->NameForType( event->GetType() ), event->GetName() ); + + OffsetRect( &rcEventLabel, 0, fontsize + 2 ); + + drawHelper.DrawColoredText( "Arial", fontsize, FW_NORMAL, COLOR_INFO_TEXT, rcEventLabel, "parameters \"%s\"", GetLabelText() ); + +} + +COLORREF CChoreoEventWidget::GrayOutColor( COLORREF clr ) +{ + CChoreoEvent *event = GetEvent(); + if ( !event ) + return clr; + if ( event->GetActive() ) + return clr; + + int r, g, b; + r = GetRValue( clr ); + g = GetGValue( clr ); + b = GetBValue( clr ); + int val = ( r + g + b ) / 3; + val += ( 255 - val ) * 0.25f; + + clr = RGB( val, val, val ); + return clr; +} + + +void CChoreoEventWidget::DrawSpeakEvent( CChoreoWidgetDrawHelper& drawHelper, RECT& rcEventLine ) +{ + if ( !m_pWaveFile ) + return; + + bool ramponly = m_pView->IsRampOnly(); + + CChoreoEvent *event = GetEvent(); + Assert( event ); + + HDC dc = drawHelper.GrabDC(); + + HBRUSH brEvent = CreateSolidBrush( GrayOutColor( COLOR_CHOREO_EVENT ) ); + HBRUSH brBackground = CreateSolidBrush( GrayOutColor( COLOR_CHOREO_DARKBACKGROUND ) ); + + if ( !ramponly ) + { + FillRect( dc, &rcEventLine, brBackground ); + } + + // Only draw wav form here if selected + if ( IsSelected() ) + { + sound->RenderWavToDC( dc, rcEventLine, GrayOutColor( IsSelected() ? COLOR_CHOREO_EVENT_SELECTED : COLOR_CHOREO_EVENT ), 0.0, m_pWaveFile->GetRunningLength(), m_pWaveFile ); + } + + //FrameRect( dc, &rcEventLine, brEvent ); + drawHelper.DrawColoredLine( GrayOutColor( COLOR_CHOREO_EVENT ), PS_SOLID, 3, + rcEventLine.left, rcEventLine.top, rcEventLine.left, rcEventLine.bottom ); + drawHelper.DrawColoredLine( GrayOutColor( COLOR_CHOREO_EVENT ), PS_SOLID, 3, + rcEventLine.right, rcEventLine.top, rcEventLine.right, rcEventLine.bottom ); + + DeleteObject( brBackground ); + DeleteObject( brEvent ); + + //rcEventLine.top -= 3; + DrawRelativeTags( drawHelper, rcEventLine, m_pWaveFile->GetRunningLength(), event ); +} + +void CChoreoEventWidget::DrawGestureEvent( CChoreoWidgetDrawHelper& drawHelper, RECT& rcEventLine ) +{ + CChoreoEvent *event = GetEvent(); + Assert( event ); + + RECT rcEventLine2 = rcEventLine; + /* + float duration = event->GetDuration(); + // Crop eventline2 + if ( duration > 0.0f ) + { + float attack_frac = 0.3; // ( event->GetAttackTime() ) / duration; + float decay_frac = 0.7; // ( event->GetDecayTime() ) / duration; + + float event_line_width = rcEventLine.right - rcEventLine.left; + + rcEventLine2.left = rcEventLine.left + attack_frac * event_line_width; + rcEventLine2.right = rcEventLine.left + decay_frac * event_line_width; + } + */ + + bool ramponly = m_pView->IsRampOnly(); + + + HDC dc = drawHelper.GrabDC(); + + bool nullevent = false; + + COLORREF clrEvent = GrayOutColor( IsSelected() ? COLOR_CHOREO_EVENT_SELECTED : COLOR_CHOREO_EVENT ); + if ( !Q_stricmp( event->GetName(), "NULL" ) ) + { + clrEvent = GrayOutColor( RGB( 50, 50, 120 ) ); + nullevent = true; + } + + HBRUSH brEvent = CreateSolidBrush( clrEvent ); + + if ( !ramponly ) + { + FillRect( dc, &rcEventLine2, brEvent ); + } + + DeleteObject( brEvent ); + + if ( ramponly && IsSelected() ) + { + drawHelper.DrawOutlinedRect( GrayOutColor( RGB( 150, 180, 250 ) ), PS_SOLID, 1, + rcEventLine2 ); + } + else + { + drawHelper.DrawColoredLine( GrayOutColor( RGB( 127, 127, 127 ) ), PS_SOLID, 1, rcEventLine2.left, rcEventLine2.bottom, + rcEventLine2.left, rcEventLine2.top ); + drawHelper.DrawColoredLine( GrayOutColor( RGB( 127, 127, 127 ) ), PS_SOLID, 1, rcEventLine2.left, rcEventLine2.top, + rcEventLine2.right, rcEventLine2.top ); + drawHelper.DrawColoredLine( GrayOutColor( RGB( 31, 31, 31 ) ), PS_SOLID, 1, rcEventLine2.right, rcEventLine2.top, + rcEventLine2.right, rcEventLine2.bottom ); + drawHelper.DrawColoredLine( GrayOutColor( RGB( 0, 0, 0 ) ), PS_SOLID, 1, rcEventLine2.right, rcEventLine2.bottom, + rcEventLine2.left, rcEventLine2.bottom ); + } + + int rampstart = m_pView->GetPixelForTimeValue( event->GetStartTime( ) ); + int rampend = m_pView->GetPixelForTimeValue( event->GetEndTime( ) ); + +// COLORREF clrBottom = RGB( 180, 180, 180 ); + +// drawHelper.DrawColoredLine( clrBottom, PS_SOLID, 1, rampstart, rcEventLine2.bottom, +// rcEventLine2.left, rcEventLine2.bottom ); +// drawHelper.DrawColoredLine( clrBottom, PS_SOLID, 1, rcEventLine2.right, rcEventLine2.bottom, +// rampend, rcEventLine2.bottom ); + + if ( !nullevent ) + { + drawHelper.DrawColoredRamp( clrEvent, PS_SOLID, 1, + rampstart, + rcEventLine2.bottom, + rcEventLine2.left, + rcEventLine2.top, + 0.0f, + 1.0f ); + drawHelper.DrawColoredRamp( clrEvent, PS_SOLID, 1, + rcEventLine2.right, + rcEventLine2.top, + rampend, + rcEventLine2.bottom, + 0.0f, + 1.0f ); + } + + g_pRampTool->DrawSamplesSimple( drawHelper, event, false, GrayOutColor( RGB( 63, 63, 63 ) ), rcEventLine ); + + DrawRelativeTags( drawHelper, rcEventLine, event->GetDuration(), event ); + DrawAbsoluteTags( drawHelper, rcEventLine, event->GetDuration(), event ); +} + +void CChoreoEventWidget::DrawGenericEvent( CChoreoWidgetDrawHelper& drawHelper, RECT& rcEventLine ) +{ + bool ramponly = m_pView->IsRampOnly(); + + CChoreoEvent *event = GetEvent(); + Assert( event ); + + HDC dc = drawHelper.GrabDC(); + + COLORREF clrEvent = GrayOutColor( IsSelected() ? COLOR_CHOREO_EVENT_SELECTED : COLOR_CHOREO_EVENT ); + if ( event->GetType() == CChoreoEvent::SUBSCENE ) + { + clrEvent = GrayOutColor( RGB( 200, 180, 200 ) ); + } + + HBRUSH brEvent = CreateSolidBrush( clrEvent ); + + if ( !ramponly ) + { + FillRect( dc, &rcEventLine, brEvent ); + } + + DeleteObject( brEvent ); + + if ( ramponly && IsSelected() ) + { + drawHelper.DrawOutlinedRect( GrayOutColor( RGB( 150, 180, 250 ) ), PS_SOLID, 1, + rcEventLine ); + } + else + { + drawHelper.DrawColoredLine( GrayOutColor( RGB( 127, 127, 127 ) ), PS_SOLID, 1, rcEventLine.left, rcEventLine.bottom, + rcEventLine.left, rcEventLine.top ); + drawHelper.DrawColoredLine( GrayOutColor( RGB( 127, 127, 127 ) ), PS_SOLID, 1, rcEventLine.left, rcEventLine.top, + rcEventLine.right, rcEventLine.top ); + drawHelper.DrawColoredLine( GrayOutColor( RGB( 31, 31, 31 ) ), PS_SOLID, 1, rcEventLine.right, rcEventLine.top, + rcEventLine.right, rcEventLine.bottom ); + drawHelper.DrawColoredLine( GrayOutColor( RGB( 0, 0, 0 ) ), PS_SOLID, 1, rcEventLine.right, rcEventLine.bottom, + rcEventLine.left, rcEventLine.bottom ); + } + + g_pRampTool->DrawSamplesSimple( drawHelper, event, false, GrayOutColor( RGB( 63, 63, 63 ) ), rcEventLine ); + + DrawRelativeTags( drawHelper, rcEventLine, event->GetDuration(), event ); + DrawAbsoluteTags( drawHelper, rcEventLine, event->GetDuration(), event ); + +} + +//----------------------------------------------------------------------------- +// Purpose: FIXME: This should either be embedded or we should draw the caption +// here +//----------------------------------------------------------------------------- +void CChoreoEventWidget::redraw( CChoreoWidgetDrawHelper& drawHelper ) +{ + if ( !getVisible() ) + return; + + CChoreoEvent *event = GetEvent(); + if ( !event ) + return; + + int deflateborder = 1; + int fontsize = 9; + + HDC dc = drawHelper.GrabDC(); + RECT rcClient = getBounds(); + + RECT rcDC; + drawHelper.GetClientRect( rcDC ); + + RECT dummy; + if ( !IntersectRect( &dummy, &rcDC, &rcClient ) ) + return; + + bool ramponly = m_pView->IsRampOnly(); + + if ( IsSelected() && !ramponly ) + { + InflateRect( &rcClient, 3, 1 ); + //rcClient.bottom -= 1; + rcClient.right += 1; + + RECT rcFrame = rcClient; + RECT rcBorder = rcClient; + + rcFrame.bottom = rcFrame.top + 17; + rcBorder.bottom = rcFrame.top + 17; + + COLORREF clrSelection = GrayOutColor( RGB( 0, 63, 63 ) ); + COLORREF clrBorder = GrayOutColor( RGB( 100, 200, 255 ) ); + + HBRUSH brBorder = CreateSolidBrush( clrBorder ); + HBRUSH brSelected = CreateHatchBrush( HS_FDIAGONAL, clrSelection ); + for ( int i = 0; i < 2; i++ ) + { + FrameRect( dc, &rcFrame, brSelected ); + InflateRect( &rcFrame, -1, -1 ); + } + FrameRect( dc, &rcBorder, brBorder ); + FrameRect( dc, &rcFrame, brBorder ); + + DeleteObject( brSelected ); + DeleteObject( brBorder ); + rcClient.right -= 1; + //rcClient.bottom += 1; + InflateRect( &rcClient, -3, -1 ); + } + + RECT rcEvent; + rcEvent = rcClient; + + InflateRect( &rcEvent, 0, -deflateborder ); + + rcEvent.bottom = rcEvent.top + 10; + + if ( event->GetType() == CChoreoEvent::SPEAK && m_pWaveFile && !event->HasEndTime() ) + { + event->SetEndTime( event->GetStartTime() + m_pWaveFile->GetRunningLength() ); + rcEvent.right = ( int )( m_pWaveFile->GetRunningLength() * m_pView->GetPixelsPerSecond() ); + } + + if ( event->HasEndTime() ) + { + RECT rcEventLine = rcEvent; + OffsetRect( &rcEventLine, 0, 1 ); + + switch ( event->GetType() ) + { + case CChoreoEvent::SPEAK: + { + DrawSpeakEvent( drawHelper, rcEventLine ); + } + break; + case CChoreoEvent::GESTURE: + { + DrawGestureEvent( drawHelper, rcEventLine ); + } + break; + default: + { + DrawGenericEvent( drawHelper, rcEventLine ); + } + break; + } + } + else + { + RECT rcEventLine = rcEvent; + OffsetRect( &rcEventLine, 0, 1 ); + + drawHelper.DrawColoredLine( GrayOutColor( COLOR_CHOREO_EVENT ), PS_SOLID, 3, + rcEventLine.left - 1, rcEventLine.top, rcEventLine.left - 1, rcEventLine.bottom ); + } + + if ( event->IsUsingRelativeTag() ) + { + RECT rcTagName; + rcTagName = rcClient; + + int length = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, event->GetRelativeTagName() ); + + rcTagName.right = rcTagName.left; + rcTagName.left = rcTagName.right - length - 4; + rcTagName.top += 3; + rcTagName.bottom = rcTagName.top + 10; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, GrayOutColor( RGB( 0, 100, 200 ) ), rcTagName, event->GetRelativeTagName() ); + + drawHelper.DrawFilledRect( GrayOutColor( RGB( 0, 100, 250 ) ), rcTagName.right-1, rcTagName.top-2, + rcTagName.right+2, rcTagName.bottom + 2 ); + + } + + // Now draw the label + RECT rcEventLabel; + rcEventLabel = rcClient; + + InflateRect( &rcEventLabel, 0, -deflateborder ); + + rcEventLabel.top += 15; // rcEventLabel.bottom - 2 * ( fontsize + 2 ) - 1; + rcEventLabel.bottom = rcEventLabel.top + fontsize + 2; + rcEventLabel.left += 1; + + //rcEventLabel.left -= 8; + + int leftAdd = 16; + + if ( CChoreoEventWidget::GetImage( event->GetType() ) ) + { + mxbitmapdata_t *image = CChoreoEventWidget::GetImage( event->GetType() ); + if ( image ) + { + DrawBitmapToDC( dc, rcEventLabel.left, rcEventLabel.top, leftAdd, leftAdd, + *image ); + } + } + + OffsetRect( &rcEventLabel, leftAdd, 1 ); + + if ( event->IsResumeCondition() ) + { + RECT rc = rcEventLabel; + OffsetRect( &rcEventLabel, leftAdd, 0 ); + rc.right = rc.left + leftAdd; + rc.bottom = rc.top + leftAdd; + + DrawBitmapToDC( dc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, + *CChoreoEventWidget::GetPauseImage() ); + } + + if ( event->IsLockBodyFacing() ) + { + RECT rc = rcEventLabel; + OffsetRect( &rcEventLabel, 16, 0 ); + rc.right = rc.left + leftAdd; + rc.bottom = rc.top + leftAdd; + + RECT rcFixed = rc; + drawHelper.OffsetSubRect( rcFixed ); + DrawBitmapToDC( dc, rcFixed.left, rcFixed.top, + rcFixed.right - rcFixed.left, rcFixed.bottom - rcFixed.top, + *CChoreoEventWidget::GetLockImage() ); + } + + OffsetRect( &rcEventLabel, 2, 1 ); + + int len = drawHelper.CalcTextWidth( "Arial", fontsize, FW_NORMAL, event->GetName() ); + + rcEventLabel.right = rcEventLabel.left + len + 2; + drawHelper.DrawColoredText( "Arial", fontsize, FW_NORMAL, GrayOutColor( RGB( 0, 0, 120 ) ), + rcEventLabel, event->GetName() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CChoreoEvent +//----------------------------------------------------------------------------- +CChoreoEvent *CChoreoEventWidget::GetEvent( void ) +{ + return m_pEvent; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void CChoreoEventWidget::SetEvent( CChoreoEvent *event ) +{ + sound->StopAll(); + + delete m_pWaveFile; + m_pWaveFile = NULL; + + m_pEvent = event; + + if ( event->GetType() == CChoreoEvent::SPEAK ) + { + m_pWaveFile = sound->LoadSound( va( "sound/%s", FacePoser_TranslateSoundName( event ) ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: If the user changes the association of .mdls to actors, then the gender could change and we could need to access a different .wav file +// Input : - +//----------------------------------------------------------------------------- +void CChoreoEventWidget::RecomputeWave() +{ + if ( m_pEvent->GetType() == CChoreoEvent::SPEAK ) + { + delete m_pWaveFile; + m_pWaveFile = sound->LoadSound( va( "sound/%s", FacePoser_TranslateSoundName( m_pEvent ) ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoEventWidget::LoadImages( void ) +{ + for ( int i = 0; i < FP_NUM_BITMAPS; i++ ) + { + m_Bitmaps[ i ].valid = false; + } + + m_ResumeConditionBitmap.valid = false; + m_LockBodyFacingBitmap.valid = false; + + LoadBitmapFromFile( "gfx/hlfaceposer/ev_expression.bmp", m_Bitmaps[ CChoreoEvent::EXPRESSION ] ); + LoadBitmapFromFile( "gfx/hlfaceposer/ev_lookat.bmp", m_Bitmaps[ CChoreoEvent::LOOKAT ] ); + LoadBitmapFromFile( "gfx/hlfaceposer/ev_moveto.bmp", m_Bitmaps[ CChoreoEvent::MOVETO ] ); + LoadBitmapFromFile( "gfx/hlfaceposer/ev_speak.bmp", m_Bitmaps[ CChoreoEvent::SPEAK ] ); + LoadBitmapFromFile( "gfx/hlfaceposer/ev_gesture.bmp", m_Bitmaps[ CChoreoEvent::GESTURE ] ); + LoadBitmapFromFile( "gfx/hlfaceposer/ev_face.bmp", m_Bitmaps[ CChoreoEvent::FACE ] ); + + LoadBitmapFromFile( "gfx/hlfaceposer/ev_firetrigger.bmp", m_Bitmaps[ CChoreoEvent::FIRETRIGGER ] ); + LoadBitmapFromFile( "gfx/hlfaceposer/ev_sequence.bmp", m_Bitmaps[ CChoreoEvent::SEQUENCE ] ); + LoadBitmapFromFile( "gfx/hlfaceposer/ev_flexanimation.bmp", m_Bitmaps[ CChoreoEvent::FLEXANIMATION ] ); + LoadBitmapFromFile( "gfx/hlfaceposer/ev_subscene.bmp", m_Bitmaps[ CChoreoEvent::SUBSCENE ] ); + LoadBitmapFromFile( "gfx/hlfaceposer/ev_loop.bmp", m_Bitmaps[ CChoreoEvent::LOOP ] ); + + LoadBitmapFromFile( "gfx/hlfaceposer/pause.bmp", m_ResumeConditionBitmap ); + + LoadBitmapFromFile( "gfx/hlfaceposer/ev_interrupt.bmp", m_Bitmaps[ CChoreoEvent::INTERRUPT ] ); + LoadBitmapFromFile( "gfx/hlfaceposer/ev_stoppoint.bmp", m_Bitmaps[ CChoreoEvent::STOPPOINT ] ); + LoadBitmapFromFile( "gfx/hlfaceposer/ev_permit_response.bmp", m_Bitmaps[ CChoreoEvent::PERMIT_RESPONSES ] ); + LoadBitmapFromFile( "gfx/hlfaceposer/ev_generic.bmp", m_Bitmaps[ CChoreoEvent::GENERIC ] ); + + LoadBitmapFromFile( "gfx/hlfaceposer/lock.bmp", m_LockBodyFacingBitmap ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoEventWidget::DestroyImages( void ) +{ + for ( int i = 0; i < FP_NUM_BITMAPS; i++ ) + { + if ( m_Bitmaps[ i ].valid ) + { + m_Bitmaps[ i ].valid = false; + DeleteObject( m_Bitmaps[ i ].image ); + m_Bitmaps[ i ].image = NULL; + } + } + + if ( m_ResumeConditionBitmap.valid ) + { + m_ResumeConditionBitmap.valid = false; + DeleteObject( m_ResumeConditionBitmap.image ); + m_ResumeConditionBitmap.image = NULL; + } + + if ( m_LockBodyFacingBitmap.valid ) + { + m_LockBodyFacingBitmap.valid = false; + DeleteObject( m_LockBodyFacingBitmap.image ); + m_LockBodyFacingBitmap.image = NULL; + } + +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - +// Output : mxbitmapdata_t +//----------------------------------------------------------------------------- +mxbitmapdata_t *CChoreoEventWidget::GetImage( int type ) +{ + return &m_Bitmaps[ type ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : mxbitmapdata_t +//----------------------------------------------------------------------------- +mxbitmapdata_t *CChoreoEventWidget::GetPauseImage( void ) +{ + return &m_ResumeConditionBitmap; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : mxbitmapdata_t +//----------------------------------------------------------------------------- +mxbitmapdata_t *CChoreoEventWidget::GetLockImage( void ) +{ + return &m_LockBodyFacingBitmap; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/choreoeventwidget.h b/utils/hlfaceposer/choreoeventwidget.h new file mode 100644 index 0000000..3f7774a --- /dev/null +++ b/utils/hlfaceposer/choreoeventwidget.h @@ -0,0 +1,88 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#ifndef CHOREOEVENTWIDGET_H +#define CHOREOEVENTWIDGET_H +#ifdef _WIN32 +#pragma once +#endif + +#include "mxbitmaptools.h" +#include "choreowidget.h" + +class CChoreoEvent; +class CChoreoChannelWidget; +class CAudioSource; + +#define FP_NUM_BITMAPS 20 + +//----------------------------------------------------------------------------- +// Purpose: Draw event UI element and handle mouse interactions +//----------------------------------------------------------------------------- +class CChoreoEventWidget : public CChoreoWidget +{ +public: + typedef CChoreoWidget BaseClass; + + // Construction/destruction + CChoreoEventWidget( CChoreoWidget *parent ); + virtual ~CChoreoEventWidget( void ); + + // Create children + virtual void Create( void ); + // Redo layout + virtual void Layout( RECT& rc ); + // Screen refresh + virtual void redraw(CChoreoWidgetDrawHelper& drawHelper); + virtual void redrawStatus( CChoreoWidgetDrawHelper& drawHelper, RECT& rcClient ); + + virtual int GetDurationRightEdge( void ); + + // Access underlying object + CChoreoEvent *GetEvent( void ); + void SetEvent( CChoreoEvent *event ); + + // If the user changes the association of .mdls to actors, then the gender could change and we could need to access a different .wav file + // Call this to reconcile things + void RecomputeWave(); + + // System wide icons for various event types ( indexed by CChoreEvent::m_fType ) + static void LoadImages( void ); + static void DestroyImages( void ); + static mxbitmapdata_t *GetImage( int type ); + static mxbitmapdata_t *GetPauseImage( void ); + static mxbitmapdata_t *GetLockImage( void ); + +private: + + COLORREF GrayOutColor( COLORREF clr ); + + void DrawRelativeTags( CChoreoWidgetDrawHelper& drawHelper, RECT &rcWAV, float length, CChoreoEvent *event ); + void DrawAbsoluteTags( CChoreoWidgetDrawHelper& drawHelper, RECT &rcWAV, float length, CChoreoEvent *event ); + + const char *GetLabelText( void ); + + void DrawSpeakEvent( CChoreoWidgetDrawHelper& drawHelper, RECT& rcEventLine ); + void DrawGestureEvent( CChoreoWidgetDrawHelper& drawHelper, RECT& rcEventLine ); + void DrawGenericEvent( CChoreoWidgetDrawHelper& drawHelper, RECT& rcEventLine ); + // Parent widget + CChoreoWidget *m_pParent; + + // Underlying event + CChoreoEvent *m_pEvent; + + int m_nDurationRightEdge; + + // For speak events + CAudioSource *m_pWaveFile; + + // Bitmaps for drawing event widgets + static mxbitmapdata_t m_Bitmaps[ FP_NUM_BITMAPS ]; + static mxbitmapdata_t m_ResumeConditionBitmap; + static mxbitmapdata_t m_LockBodyFacingBitmap; +}; + +#endif // CHOREOEVENTWIDGET_H diff --git a/utils/hlfaceposer/choreoglobaleventwidget.cpp b/utils/hlfaceposer/choreoglobaleventwidget.cpp new file mode 100644 index 0000000..d9af31b --- /dev/null +++ b/utils/hlfaceposer/choreoglobaleventwidget.cpp @@ -0,0 +1,216 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include <stdio.h> +#include "hlfaceposer.h" +#include "choreoviewcolors.h" +#include "choreoglobaleventwidget.h" +#include "choreowidgetdrawhelper.h" +#include "ChoreoView.h" +#include "choreoevent.h" +#include "choreoeventwidget.h" + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *parent - +//----------------------------------------------------------------------------- +CChoreoGlobalEventWidget::CChoreoGlobalEventWidget( CChoreoWidget *parent ) +: CChoreoWidget( parent ) +{ + m_pEvent = NULL; + + m_bDragging = false; + m_xStart = 0; + m_hPrevCursor = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CChoreoGlobalEventWidget::~CChoreoGlobalEventWidget( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoGlobalEventWidget::Create( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rc - +//----------------------------------------------------------------------------- +void CChoreoGlobalEventWidget::Layout( RECT& rc ) +{ + setBounds( rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top ); +} + +//----------------------------------------------------------------------------- +// Redraw to screen +//----------------------------------------------------------------------------- +void CChoreoGlobalEventWidget::redraw( CChoreoWidgetDrawHelper& drawHelper ) +{ + if ( !getVisible() ) + return; + + CChoreoEvent *event = GetEvent(); + if ( !event ) + return; + + RECT rcTab; + rcTab = getBounds(); + + bool isLoop = false; + COLORREF pointColor = COLOR_CHOREO_SEGMENTDIVIDER; + COLORREF clr = COLOR_CHOREO_SEGMENTDIVIDER_BG; + switch ( event->GetType() ) + { + default: + break; + case CChoreoEvent::LOOP: + { + clr = COLOR_CHOREO_LOOPPOINT_BG; + pointColor = COLOR_CHOREO_LOOPPOINT; + isLoop = true; + } + break; + case CChoreoEvent::STOPPOINT: + { + clr = COLOR_CHOREO_STOPPOINT_BG; + pointColor = COLOR_CHOREO_STOPPOINT; + } + break; + } + + if ( IsSelected() ) + { + InflateRect( &rcTab, 2, 2 ); + + drawHelper.DrawTriangleMarker( rcTab, pointColor ); + + InflateRect( &rcTab, -2, -2 ); + + drawHelper.DrawTriangleMarker( rcTab, RGB( 240, 240, 220 ) ); + + } + else + { + drawHelper.DrawTriangleMarker( rcTab, pointColor ); + } + + RECT rcClient; + drawHelper.GetClientRect( rcClient ); + + RECT rcLine = rcTab; + rcLine.top = rcTab.bottom + 2; + rcLine.bottom = rcClient.bottom; + rcLine.left = ( rcLine.left + rcLine.right ) / 2; + rcLine.right = rcLine.left; + + if ( IsSelected() ) + { + drawHelper.DrawColoredLine( clr, PS_DOT, 2, rcLine.left, rcLine.top, rcLine.right, rcLine.bottom ); + } + else + { + drawHelper.DrawColoredLine( clr, PS_DOT, 1, rcLine.left, rcLine.top, rcLine.right, rcLine.bottom ); + } + + if ( event->GetType() == CChoreoEvent::STOPPOINT ) + { + OffsetRect( &rcTab, -4, 15 ); + + mxbitmapdata_t *image = CChoreoEventWidget::GetImage( event->GetType() ); + if ( image ) + { + drawHelper.OffsetSubRect( rcTab ); + DrawBitmapToDC( drawHelper.GrabDC(), rcTab.left, rcTab.top, 16, 16, *image ); + } + } + + if ( !isLoop ) + return; + + COLORREF labelText = COLOR_INFO_TEXT; + DrawLabel( drawHelper, labelText, rcLine.left, rcLine.top + 2, false ); + + // Figure out loop spot + float looptime = (float)atof( event->GetParameters() ); + + // Find pixel for that + bool clipped = false; + int x = m_pView->GetPixelForTimeValue( looptime, &clipped ); + if ( clipped ) + return; + + rcLine.left = x; + rcLine.right = x; + + clr = COLOR_CHOREO_LOOPPOINT_START_BG; + drawHelper.DrawColoredLine( clr, PS_SOLID, 1, rcLine.left, rcLine.top, rcLine.right, rcLine.top + 28); + + DrawLabel( drawHelper, labelText, rcLine.left, rcLine.top + 2, true ); +} + +void CChoreoGlobalEventWidget::DrawLabel( CChoreoWidgetDrawHelper& drawHelper, COLORREF clr, int x, int y, bool right ) +{ + CChoreoEvent *event = GetEvent(); + if ( !event ) + return; + + int len = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, va( "%s", event->GetName() ) ); + + RECT rcText; + rcText.top = y; + rcText.bottom = y + 10; + rcText.left = x - len / 2; + rcText.right = rcText.left + len; + + if ( !right ) + { + drawHelper.DrawColoredTextCharset( "Marlett", 9, FW_NORMAL, SYMBOL_CHARSET, clr, rcText, "3" ); + OffsetRect( &rcText, 8, 0 ); + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, clr, rcText, va( "%s", event->GetName() ) ); + } + else + { + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, clr, rcText, va( "%s", event->GetName() ) ); + OffsetRect( &rcText, len, 0 ); + drawHelper.DrawColoredTextCharset( "Marlett", 9, FW_NORMAL, SYMBOL_CHARSET, clr, rcText, "4" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoGlobalEventWidget::DrawFocusRect( void ) +{ + HDC dc = GetDC( NULL ); + + ::DrawFocusRect( dc, &m_rcFocus ); + + ReleaseDC( NULL, dc ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CChoreoEvent +//----------------------------------------------------------------------------- +CChoreoEvent *CChoreoGlobalEventWidget::GetEvent( void ) +{ + return m_pEvent; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void CChoreoGlobalEventWidget::SetEvent( CChoreoEvent *event ) +{ + m_pEvent = event; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/choreoglobaleventwidget.h b/utils/hlfaceposer/choreoglobaleventwidget.h new file mode 100644 index 0000000..e1881ca --- /dev/null +++ b/utils/hlfaceposer/choreoglobaleventwidget.h @@ -0,0 +1,59 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#ifndef CHOREOGLOBALEVENTWIDGET_H +#define CHOREOGLOBALEVENTWIDGET_H +#ifdef _WIN32 +#pragma once +#endif + +#include "choreowidget.h" + +class CChoreoEvent; + +//----------------------------------------------------------------------------- +// Purpose: For section start/end +// FIXME: Finish this +//----------------------------------------------------------------------------- +class CChoreoGlobalEventWidget : public CChoreoWidget +{ +public: + typedef CChoreoWidget BaseClass; + + // Construction/destruction + CChoreoGlobalEventWidget( CChoreoWidget *parent ); + virtual ~CChoreoGlobalEventWidget( void ); + + // Create children + virtual void Create( void ); + // Redo layout + virtual void Layout( RECT& rc ); + + // Screen refresh + virtual void redraw(CChoreoWidgetDrawHelper& drawHelper); + + // Access underlying scene object + CChoreoEvent *GetEvent( void ); + void SetEvent( CChoreoEvent *event ); + + // Draw focus rect while mouse dragging is going on + void DrawFocusRect( void ); +private: + + void DrawLabel( CChoreoWidgetDrawHelper& drawHelper, COLORREF clr, int x, int y, bool right ); + + // The underlying scene object + CChoreoEvent *m_pEvent; + + // For updating focus rect + bool m_bDragging; + int m_xStart; + RECT m_rcFocus; + RECT m_rcOrig; + HCURSOR m_hPrevCursor; +}; + +#endif // CHOREOGLOBALEVENTWIDGET_H diff --git a/utils/hlfaceposer/choreoview.cpp b/utils/hlfaceposer/choreoview.cpp new file mode 100644 index 0000000..440e69b --- /dev/null +++ b/utils/hlfaceposer/choreoview.cpp @@ -0,0 +1,11647 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include <stdio.h> +#include <mxtk/mxWindow.h> +#include "mdlviewer.h" +#include "hlfaceposer.h" +#include "StudioModel.h" +#include "expressions.h" +#include "expclass.h" +#include "ChoreoView.h" +#include "choreoevent.h" +#include "choreoactor.h" +#include "choreochannel.h" +#include "choreoscene.h" +#include "choreowidget.h" +#include "choreoactorwidget.h" +#include "choreochannelwidget.h" +#include "choreoglobaleventwidget.h" +#include "choreowidgetdrawhelper.h" +#include "choreoeventwidget.h" +#include "viewerSettings.h" +#include "filesystem.h" +#include "choreoviewcolors.h" +#include "ActorProperties.h" +#include "ChannelProperties.h" +#include "EventProperties.h" +#include "GlobalEventProperties.h" +#include "ifaceposersound.h" +#include "snd_wave_source.h" +#include "ifaceposerworkspace.h" +#include "PhonemeEditor.h" +#include "iscenetokenprocessor.h" +#include "InputProperties.h" +#include "filesystem.h" +#include "ExpressionTool.h" +#include "ControlPanel.h" +#include "faceposer_models.h" +#include "choiceproperties.h" +#include "MatSysWin.h" +#include "tier1/strtools.h" +#include "GestureTool.h" +#include "npcevent.h" +#include "RampTool.h" +#include "SceneRampTool.h" +#include "KeyValues.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "cclookup.h" +#include "iclosecaptionmanager.h" +#include "AddSoundEntry.h" +#include "isoundcombiner.h" +#include <vgui/ILocalize.h> +#include "scriplib.h" +#include "WaveBrowser.h" +#include "filesystem_init.h" +#include "flexpanel.h" +#include "tier3/choreoutils.h" +#include "tier2/p4helpers.h" + + +using namespace vgui; + +extern vgui::ILocalize *g_pLocalize; + +// 10x magnification +#define MAX_TIME_ZOOM 1000 +#define TIME_ZOOM_STEP 4 + +#define PHONEME_FILTER 0.08f +#define PHONEME_DELAY 0.0f + +#define SCRUBBER_HEIGHT 15 +#define TIMELINE_NUMBERS_HEIGHT 11 + +#define COPYPASTE_FILENAME "scenes/copydatavcd.txt" + +extern double realtime; +extern bool NameLessFunc( const char *const& name1, const char *const& name2 ); + +// Try to keep shifted times at same absolute time +static void RescaleExpressionTimes( CChoreoEvent *event, float newstart, float newend ) +{ + if ( !event || event->GetType() != CChoreoEvent::FLEXANIMATION ) + return; + + // Did it actually change + if ( newstart == event->GetStartTime() && + newend == event->GetEndTime() ) + { + return; + } + + float newduration = newend - newstart; + + float dt = 0.0f; + //If the end is moving, leave tags stay where they are (dt == 0.0f) + if ( newstart != event->GetStartTime() ) + { + // Otherwise, if the new start is later, then tags need to be shifted backwards + dt -= ( newstart - event->GetStartTime() ); + } + + int count = event->GetNumFlexAnimationTracks(); + int i; + + for ( i = 0; i < count; i++ ) + { + CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i ); + if ( !track ) + continue; + + for ( int type = 0; type < 2; type++ ) + { + int sampleCount = track->GetNumSamples( type ); + for ( int sample = sampleCount - 1; sample >= 0 ; sample-- ) + { + CExpressionSample *s = track->GetSample( sample, type ); + if ( !s ) + continue; + + s->time += dt; + + if ( s->time > newduration || s->time < 0.0f ) + { + track->RemoveSample( sample, type ); + } + } + } + } +} + +static void RescaleRamp( CChoreoEvent *event, float newduration ) +{ + float oldduration = event->GetDuration(); + + if ( fabs( oldduration - newduration ) < 0.000001f ) + return; + + if ( newduration <= 0.0f ) + return; + + float midpointtime = oldduration * 0.5f; + float newmidpointtime = newduration * 0.5f; + + int count = event->GetRampCount(); + int i; + + for ( i = 0; i < count; i++ ) + { + CExpressionSample *sample = event->GetRamp( i ); + if ( !sample ) + continue; + + float t = sample->time; + if ( t < midpointtime ) + continue; + + float timefromend = oldduration - t; + + // There's room to just shift it + if ( timefromend <= newmidpointtime ) + { + t = newduration - timefromend; + } + else + { + // No room, rescale them instead + float frac = ( t - midpointtime ) / midpointtime; + t = newmidpointtime + frac * newmidpointtime; + } + + sample->time = t; + } +} + +bool DoesAnyActorHaveAssociatedModelLoaded( CChoreoScene *scene ) +{ + if ( !scene ) + return false; + + int c = scene->GetNumActors(); + int i; + for ( i = 0; i < c; i++ ) + { + CChoreoActor *a = scene->GetActor( i ); + if ( !a ) + continue; + + char const *modelname = a->GetFacePoserModelName(); + if ( !modelname ) + continue; + + if ( !modelname[ 0 ] ) + continue; + + char mdlname[ 256 ]; + Q_strncpy( mdlname, modelname, sizeof( mdlname ) ); + Q_FixSlashes( mdlname ); + + int idx = models->FindModelByFilename( mdlname ); + if ( idx >= 0 ) + { + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *a - +// Output : StudioModel +//----------------------------------------------------------------------------- +StudioModel *FindAssociatedModel( CChoreoScene *scene, CChoreoActor *a ) +{ + if ( !a || !scene ) + return NULL; + + Assert( models->GetActiveStudioModel() ); + + StudioModel *model = NULL; + if ( a->GetFacePoserModelName()[ 0 ] ) + { + int idx = models->FindModelByFilename( a->GetFacePoserModelName() ); + if ( idx >= 0 ) + { + model = models->GetStudioModel( idx ); + return model; + } + } + + // Is there any loaded model with the actorname in it? + int c = models->Count(); + for ( int i = 0; i < c; i++ ) + { + char const *modelname = models->GetModelName( i ); + if ( !Q_stricmp( modelname, a->GetName() ) ) + { + return models->GetStudioModel( i ); + } + } + + // Does any actor have an associated model which is loaded + if ( DoesAnyActorHaveAssociatedModelLoaded( scene ) ) + { + // Then return NULL here so we don't override with the default an actor who has a valid model going + return NULL; + } + + // Couldn't find it and nobody else has a loaded associated model, so just use the default model + if ( !model ) + { + model = models->GetActiveStudioModel(); + } + return model; +} + + +CChoreoView *g_pChoreoView = 0; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *parent - +// x - +// y - +// w - +// h - +// id - +//----------------------------------------------------------------------------- +CChoreoView::CChoreoView( mxWindow *parent, int x, int y, int w, int h, int id ) +: IFacePoserToolWindow( "CChoreoView", "Choreography" ), mxWindow( parent, x, y, w, h ) +{ + m_bRampOnly = false; + + m_bForceProcess = false; + + m_bSuppressLayout = true; + + SetAutoProcess( true ); + + m_flLastMouseClickTime = -1.0f; + m_bProcessSequences = true; + + m_flPlaybackRate = 1.0f; + + m_pScene = NULL; + + m_flScrub = 0.0f; + m_flScrubTarget = 0.0f; + + m_bCanDraw = false; + + m_bRedoPending = false; + m_nUndoLevel = 0; + + CChoreoEventWidget::LoadImages(); + + CChoreoWidget::m_pView = this; + + setId( id ); + + m_flLastSpeedScale = 0.0f; + m_bResetSpeedScale = false; + + m_nTopOffset = 0; + m_flLeftOffset = 0.0f; + m_nLastHPixelsNeeded = -1; + m_nLastVPixelsNeeded = -1; + + + m_nStartRow = 45; + m_nLabelWidth = 140; + m_nRowHeight = 35; + + m_bSimulating = false; + m_bPaused = false; + + m_bForward = true; + + m_flStartTime = 0.0f; + m_flEndTime = 0.0f; + m_flFrameTime = 0.0f; + + m_bAutomated = false; + m_nAutomatedAction = SCENE_ACTION_UNKNOWN; + m_flAutomationDelay = 0.0f; + m_flAutomationTime = 0.0f; + + m_pVertScrollBar = new mxScrollbar( this, 0, 0, 18, 100, IDC_CHOREOVSCROLL, mxScrollbar::Vertical ); + m_pHorzScrollBar = new mxScrollbar( this, 0, 0, 18, 100, IDC_CHOREOHSCROLL, mxScrollbar::Horizontal ); + + m_bLayoutIsValid = false; + m_flPixelsPerSecond = 150.0f; + + m_btnPlay = new mxBitmapButton( this, 2, 4, 16, 16, IDC_PLAYSCENE, "gfx/hlfaceposer/play.bmp" ); + m_btnPause = new mxBitmapButton( this, 18, 4, 16, 16, IDC_PAUSESCENE, "gfx/hlfaceposer/pause.bmp" ); + m_btnStop = new mxBitmapButton( this, 34, 4, 16, 16, IDC_STOPSCENE, "gfx/hlfaceposer/stop.bmp" ); + + m_pPlaybackRate = new mxSlider( this, 0, 0, 16, 16, IDC_CHOREO_PLAYBACKRATE ); + m_pPlaybackRate->setRange( 0.0, 2.0, 40 ); + m_pPlaybackRate->setValue( m_flPlaybackRate ); + + ShowButtons( false ); + + m_nFontSize = 12; + + for ( int i = 0; i < MAX_ACTORS; i++ ) + { + m_ActorExpanded[ i ].expanded = true; + } + + SetChoreoFile( "" ); + + //SetFocus( (HWND)getHandle() ); + if ( workspacefiles->GetNumStoredFiles( IWorkspaceFiles::CHOREODATA ) >= 1 ) + { + LoadSceneFromFile( workspacefiles->GetStoredFile( IWorkspaceFiles::CHOREODATA, 0 ) ); + } + + ClearABPoints(); + + m_pClickedActor = NULL; + m_pClickedChannel = NULL; + m_pClickedEvent = NULL; + m_pClickedGlobalEvent = NULL; + m_nClickedX = 0; + m_nClickedY = 0; + m_nSelectedEvents = 0; + m_nClickedTag = -1; + m_nClickedChannelCloseCaptionButton = CChoreoChannelWidget::CLOSECAPTION_NONE; + + // Mouse dragging + m_bDragging = false; + m_xStart = 0; + m_yStart = 0; + m_nDragType = DRAGTYPE_NONE; + m_hPrevCursor = 0; + + m_nMinX = 0; + m_nMaxX = 0; + m_bUseBounds = false; + + m_nScrollbarHeight = 12; + m_nInfoHeight = 30; + + ClearStatusArea(); + + SetDirty( false ); + + m_bCanDraw = true; + m_bSuppressLayout = false; + m_flScrubberTimeOffset = 0.0f; + + m_bShowCloseCaptionData = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : closing - +//----------------------------------------------------------------------------- +bool CChoreoView::Close( void ) +{ + if ( m_pScene && m_bDirty ) + { + int retval = mxMessageBox( NULL, va( "Save changes to scene '%s'?", GetChoreoFile() ), g_appTitle, MX_MB_YESNOCANCEL ); + if ( retval == 2 ) + { + return false; + } + if ( retval == 0 ) + { + Save(); + } + } + + if ( m_pScene ) + { + UnloadScene(); + } + return true; +} + +bool CChoreoView::CanClose() +{ + if ( m_pScene ) + { + workspacefiles->StartStoringFiles( IWorkspaceFiles::CHOREODATA ); + workspacefiles->StoreFile( IWorkspaceFiles::CHOREODATA, GetChoreoFile() ); + workspacefiles->FinishStoringFiles( IWorkspaceFiles::CHOREODATA ); + } + + if ( m_pScene && m_bDirty && !Close() ) + { + return false; + } + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Called just before window is destroyed +//----------------------------------------------------------------------------- +void CChoreoView::OnDelete() +{ + if ( m_pScene ) + { + UnloadScene(); + } + + CChoreoWidget::m_pView = NULL; + + CChoreoEventWidget::DestroyImages(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CChoreoView::~CChoreoView() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::ReportSceneClearToTools( void ) +{ + if ( m_pScene ) + { + m_pScene->ResetSimulation(); + } + + g_pPhonemeEditor->ClearEvent(); + g_pExpressionTool->LayoutItems( true ); + g_pExpressionTool->redraw(); + g_pGestureTool->redraw(); + g_pRampTool->redraw(); + g_pSceneRampTool->redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: Find a time that's less than input on the granularity: +// e.g., 3.01 granularity 0.05 will be 3.00, 3.05 will be 3.05 +// Input : input - +// granularity - +// Output : float +//----------------------------------------------------------------------------- +float SnapTime( float input, float granularity ) +{ + float base = (float)(int)input; + float multiplier = (float)(int)( 1.0f / granularity ); + float fracpart = input - (int)input; + + fracpart *= multiplier; + + fracpart = (float)(int)fracpart; + fracpart *= granularity; + + return base + fracpart; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rc - +// left - +// right - +//----------------------------------------------------------------------------- +void CChoreoView::DrawTimeLine( CChoreoWidgetDrawHelper& drawHelper, RECT& rc, float left, float right ) +{ + RECT rcFill = m_rcTimeLine; + rcFill.bottom -= TIMELINE_NUMBERS_HEIGHT; + drawHelper.DrawFilledRect( COLOR_CHOREO_DARKBACKGROUND, rcFill ); + + RECT rcLabel; + float granularity = 0.5f / ((float)GetTimeZoom( GetToolName() ) / 100.0f); + + drawHelper.DrawColoredLine( COLOR_CHOREO_TIMELINE, PS_SOLID, 1, rc.left, GetStartRow() - 1, rc.right, GetStartRow() - 1 ); + + float f = SnapTime( left, granularity ); + while ( f < right ) + { + float frac = ( f - left ) / ( right - left ); + if ( frac >= 0.0f && frac <= 1.0f ) + { + rcLabel.left = GetLabelWidth() + (int)( frac * ( rc.right - GetLabelWidth() ) ); + rcLabel.bottom = GetStartRow() - 1; + rcLabel.top = rcLabel.bottom - 10; + + if ( f != left ) + { + drawHelper.DrawColoredLine( RGB( 220, 220, 240 ), PS_DOT, 1, + rcLabel.left, GetStartRow(), rcLabel.left, h2() ); + } + + char sz[ 32 ]; + sprintf( sz, "%.2f", f ); + + int textWidth = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, sz ); + + rcLabel.right = rcLabel.left + textWidth; + + OffsetRect( &rcLabel, -textWidth / 2, 0 ); + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, COLOR_CHOREO_TEXT, rcLabel, sz ); + + } + f += granularity; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoView::PaintBackground( void ) +{ + redraw(); + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +//----------------------------------------------------------------------------- +void CChoreoView::DrawSceneABTicks( CChoreoWidgetDrawHelper& drawHelper ) +{ + RECT rcThumb; + + float scenestart = m_rgABPoints[ 0 ].active ? m_rgABPoints[ 0 ].time : 0.0f; + float sceneend = m_rgABPoints[ 1 ].active ? m_rgABPoints[ 1 ].time : 0.0f; + + if ( scenestart ) + { + int markerstart = GetPixelForTimeValue( scenestart ); + + rcThumb.left = markerstart - 4; + rcThumb.right = markerstart + 4; + rcThumb.top = 2 + GetCaptionHeight() + SCRUBBER_HEIGHT; + rcThumb.bottom = rcThumb.top + 8; + + drawHelper.DrawTriangleMarker( rcThumb, COLOR_CHOREO_TICKAB ); + } + + if ( sceneend ) + { + int markerend = GetPixelForTimeValue( sceneend ); + + rcThumb.left = markerend - 4; + rcThumb.right = markerend + 4; + rcThumb.top = 2 + GetCaptionHeight() + SCRUBBER_HEIGHT; + rcThumb.bottom = rcThumb.top + 8; + + drawHelper.DrawTriangleMarker( rcThumb, COLOR_CHOREO_TICKAB ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rc - +//----------------------------------------------------------------------------- +void CChoreoView::DrawRelativeTagLines( CChoreoWidgetDrawHelper& drawHelper, RECT& rc ) +{ + if ( !m_pScene ) + return; + + RECT rcClip; + GetClientRect( (HWND)getHandle(), &rcClip ); + rcClip.top = GetStartRow(); + rcClip.bottom -= ( m_nInfoHeight + m_nScrollbarHeight ); + rcClip.right -= m_nScrollbarHeight; + + drawHelper.StartClipping( rcClip ); + + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *a = m_SceneActors[ i ]; + if ( !a ) + continue; + + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannelWidget *c = a->GetChannel( j ); + if ( !c ) + continue; + + for ( int k = 0; k < c->GetNumEvents(); k++ ) + { + CChoreoEventWidget *e = c->GetEvent( k ); + if ( !e ) + continue; + + CChoreoEvent *event = e->GetEvent(); + if ( !event ) + continue; + + if ( !event->IsUsingRelativeTag() ) + continue; + + // Using it, find the tag and figure out the time for it + CEventRelativeTag *tag = m_pScene->FindTagByName( + event->GetRelativeWavName(), + event->GetRelativeTagName() ); + + if ( !tag ) + continue; + + // Found it, draw a vertical line + // + float tagtime = tag->GetStartTime(); + + // Convert to pixel value + bool clipped = false; + int pixel = GetPixelForTimeValue( tagtime, &clipped ); + if ( clipped ) + continue; + + drawHelper.DrawColoredLine( RGB( 180, 180, 220 ), PS_SOLID, 1, + pixel, rcClip.top, pixel, rcClip.bottom ); + } + } + } + + drawHelper.StopClipping(); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw the background markings (actor names, etc) +// Input : drawHelper - +// rc - +//----------------------------------------------------------------------------- +void CChoreoView::DrawBackground( CChoreoWidgetDrawHelper& drawHelper, RECT& rc ) +{ + RECT rcClip; + GetClientRect( (HWND)getHandle(), &rcClip ); + rcClip.top = GetStartRow(); + rcClip.bottom -= ( m_nInfoHeight + m_nScrollbarHeight ); + rcClip.right -= m_nScrollbarHeight; + + int i; + + for ( i = 0; i < m_SceneGlobalEvents.Size(); i++ ) + { + CChoreoGlobalEventWidget *event = m_SceneGlobalEvents[ i ]; + if ( event ) + { + event->redraw( drawHelper ); + } + } + + drawHelper.StartClipping( rcClip ); + + for ( i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *actorW = m_SceneActors[ i ]; + if ( !actorW ) + continue; + + actorW->redraw( drawHelper ); + } + + drawHelper.StopClipping(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::redraw() +{ + if ( !ToolCanDraw() ) + return; + + if ( m_bSuppressLayout ) + return; + + LayoutScene(); + + CChoreoWidgetDrawHelper drawHelper( this, COLOR_CHOREO_BACKGROUND ); + HandleToolRedraw( drawHelper ); + + if ( !m_bCanDraw ) + return; + + RECT rc; + rc.left = 0; + rc.top = GetCaptionHeight(); + rc.right = drawHelper.GetWidth(); + rc.bottom = drawHelper.GetHeight(); + + RECT rcInfo; + rcInfo.left = rc.left; + rcInfo.right = rc.right - m_nScrollbarHeight; + rcInfo.bottom = rc.bottom - m_nScrollbarHeight; + rcInfo.top = rcInfo.bottom - m_nInfoHeight; + + drawHelper.StartClipping( rcInfo ); + + RedrawStatusArea( drawHelper, rcInfo ); + + drawHelper.StopClipping(); + + RECT rcClip = rc; + rcClip.bottom -= ( m_nInfoHeight + m_nScrollbarHeight ); + + drawHelper.StartClipping( rcClip ); + + if ( !m_pScene ) + { + char sz[ 256 ]; + sprintf( sz, "No choreography scene file (.vcd) loaded" ); + + int pointsize = 18; + int textlen = drawHelper.CalcTextWidth( "Arial", pointsize, FW_NORMAL, sz ); + + RECT rcText; + rcText.top = ( rc.bottom - rc.top ) / 2 - pointsize / 2; + rcText.bottom = rcText.top + pointsize + 10; + rcText.left = rc.right / 2 - textlen / 2; + rcText.right = rcText.left + textlen; + + drawHelper.DrawColoredText( "Arial", pointsize, FW_NORMAL, COLOR_CHOREO_LIGHTTEXT, rcText, sz ); + + drawHelper.StopClipping(); + return; + } + + DrawTimeLine( drawHelper, rc, m_flStartTime, m_flEndTime ); + + bool clipped = false; + int finishx = GetPixelForTimeValue( m_pScene->FindStopTime(), &clipped ); + if ( !clipped ) + { + drawHelper.DrawColoredLine( COLOR_CHOREO_ENDTIME, PS_DOT, 1, finishx, rc.top + GetStartRow(), finishx, rc.bottom ); + } + + DrawRelativeTagLines( drawHelper, rc ); + DrawBackground( drawHelper, rc ); + + DrawSceneABTicks( drawHelper ); + + drawHelper.StopClipping(); + + if ( m_UndoStack.Size() > 0 ) + { + int length = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, + "undo %i/%i", m_nUndoLevel, m_UndoStack.Size() ); + RECT rcText = rc; + rcText.top = rc.top + 48; + rcText.bottom = rcText.top + 10; + rcText.left = GetLabelWidth() - length - 20; + rcText.right = rcText.left + length; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 100, 180, 100 ), rcText, + "undo %i/%i", m_nUndoLevel, m_UndoStack.Size() ); + } + + DrawScrubHandle( drawHelper ); + + char sz[ 48 ]; + sprintf( sz, "Speed: %.2fx", m_flPlaybackRate ); + + int fontsize = 9; + + int length = drawHelper.CalcTextWidth( "Arial", fontsize, FW_NORMAL, sz); + + RECT rcText = rc; + rcText.top = rc.top + 25; + rcText.bottom = rcText.top + 10; + rcText.left = GetLabelWidth() + 20; + rcText.right = rcText.left + length; + + drawHelper.DrawColoredText( "Arial", fontsize, FW_NORMAL, + RGB( 50, 50, 50 ), rcText, sz ); + + sprintf( sz, "Zoom: %.2fx", (float)GetTimeZoom( GetToolName() ) / 100.0f ); + + length = drawHelper.CalcTextWidth( "Arial", fontsize, FW_NORMAL, sz); + + rcText = rc; + rcText.left = 5; + rcText.top = rc.top + 48; + rcText.bottom = rcText.top + 10; + rcText.right = rcText.left + length; + + drawHelper.DrawColoredText( "Arial", fontsize, FW_NORMAL, + RGB( 50, 50, 50 ), rcText, sz ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : current - +// number - +// Output : int +//----------------------------------------------------------------------------- +void CChoreoView::GetUndoLevels( int& current, int& number ) +{ + current = m_nUndoLevel; + number = m_UndoStack.Size(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : time - +// *clipped - +// Output : int +//----------------------------------------------------------------------------- +int CChoreoView::GetPixelForTimeValue( float time, bool *clipped /*=NULL*/ ) +{ + if ( clipped ) + { + *clipped = false; + } + + float frac = ( time - m_flStartTime ) / ( m_flEndTime - m_flStartTime ); + if ( frac < 0.0 || frac > 1.0 ) + { + if ( clipped ) + { + *clipped = true; + } + } + + int pixel = GetLabelWidth() + (int)( frac * ( w2() - GetLabelWidth() ) ); + return pixel; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// clip - +// Output : float +//----------------------------------------------------------------------------- +float CChoreoView::GetTimeValueForMouse( int mx, bool clip /*=false*/) +{ + RECT rc = m_rcTimeLine; + rc.left = GetLabelWidth(); + + if ( clip ) + { + if ( mx < rc.left ) + { + return m_flStartTime; + } + if ( mx > rc.right ) + { + return m_flEndTime; + } + } + + float frac = (float)( mx - rc.left ) / (float)( rc.right - rc.left ); + + return m_flStartTime + frac * ( m_flEndTime - m_flStartTime ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : time - +//----------------------------------------------------------------------------- +void CChoreoView::SetStartTime( float time ) +{ + m_flStartTime = time; + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CChoreoView::GetStartTime( void ) +{ + return m_flStartTime; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CChoreoView::GetEndTime( void ) +{ + return m_flEndTime; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CChoreoView::GetPixelsPerSecond( void ) +{ + return m_flPixelsPerSecond * (float)GetTimeZoom( GetToolName() ) / 100.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// origmx - +// Output : float +//----------------------------------------------------------------------------- +float CChoreoView::GetTimeDeltaForMouseDelta( int mx, int origmx ) +{ + float t1, t2; + + t2 = GetTimeValueForMouse( mx ); + t1 = GetTimeValueForMouse( origmx ); + + return t2 - t1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +//----------------------------------------------------------------------------- +void CChoreoView::PlaceABPoint( int mx ) +{ + m_rgABPoints[ ( m_nCurrentABPoint) & 0x01 ].time = GetTimeValueForMouse( mx ); + m_rgABPoints[ ( m_nCurrentABPoint) & 0x01 ].active = true; + m_nCurrentABPoint++; + + if ( m_rgABPoints[ 0 ].active && m_rgABPoints [ 1 ].active && + m_rgABPoints[ 0 ].time > m_rgABPoints[ 1 ].time ) + { + float temp = m_rgABPoints[ 0 ].time; + m_rgABPoints[ 0 ].time = m_rgABPoints[ 1 ].time; + m_rgABPoints[ 1 ].time = temp; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::ClearABPoints( void ) +{ + memset( m_rgABPoints, 0, sizeof( m_rgABPoints ) ); + m_nCurrentABPoint = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoView::IsMouseOverTimeline( int mx, int my ) +{ + POINT pt; + pt.x = mx; + pt.y = my; + + RECT rcCheck = m_rcTimeLine; + rcCheck.bottom -= TIMELINE_NUMBERS_HEIGHT; + + if ( PtInRect( &rcCheck, pt ) ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +//----------------------------------------------------------------------------- +void CChoreoView::ShowContextMenu( int mx, int my ) +{ + CChoreoActorWidget *a = NULL; + CChoreoChannelWidget *c = NULL; + CChoreoEventWidget *e = NULL; + CChoreoGlobalEventWidget *ge = NULL; + int ct = -1; + CEventAbsoluteTag *at = NULL; + int clickedCloseCaptionButton = CChoreoChannelWidget::CLOSECAPTION_NONE; + + GetObjectsUnderMouse( mx, my, &a, &c, &e, &ge, &ct, &at, &clickedCloseCaptionButton ); + + m_pClickedActor = a; + m_pClickedChannel = c; + m_pClickedEvent = e; + m_pClickedGlobalEvent = ge; + m_nClickedX = mx; + m_nClickedY = my; + m_nClickedTag = ct; + m_pClickedAbsoluteTag = at; + m_nClickedChannelCloseCaptionButton = clickedCloseCaptionButton; + + // Construct main + mxPopupMenu *pop = new mxPopupMenu(); + + if ( a && c ) + { + if (!e) + { + pop->add( "Expression...", IDC_ADDEVENT_EXPRESSION ); + pop->add( "WAV File...", IDC_ADDEVENT_SPEAK ); + pop->add( "Gesture...", IDC_ADDEVENT_GESTURE ); + pop->add( "NULL Gesture...", IDC_ADDEVENT_NULLGESTURE ); + pop->add( "Look at actor...", IDC_ADDEVENT_LOOKAT ); + pop->add( "Move to actor...", IDC_ADDEVENT_MOVETO ); + pop->add( "Face actor...", IDC_ADDEVENT_FACE ); + pop->add( "Fire Trigger...", IDC_ADDEVENT_FIRETRIGGER ); + pop->add( "Generic(AI)...", IDC_ADDEVENT_GENERIC ); + pop->add( "Sequence...", IDC_ADDEVENT_SEQUENCE ); + pop->add( "Flex animation...", IDC_ADDEVENT_FLEXANIMATION ); + pop->add( "Sub-scene...", IDC_ADDEVENT_SUBSCENE ); + pop->add( "Interrupt...", IDC_ADDEVENT_INTERRUPT ); + pop->add( "Permit Responses...", IDC_ADDEVENT_PERMITRESPONSES ); + + pop->addSeparator(); + } + else + { + pop->add( va( "Edit Event '%s'...", e->GetEvent()->GetName() ), IDC_EDITEVENT ); + switch ( e->GetEvent()->GetType() ) + { + default: + break; + case CChoreoEvent::FLEXANIMATION: + { + pop->add( va( "Edit Event '%s' in expression tool", e->GetEvent()->GetName() ), IDC_EXPRESSIONTOOL ); + } + break; + case CChoreoEvent::GESTURE: + { + pop->add( va( "Edit Event '%s' in gesture tool", e->GetEvent()->GetName() ), IDC_GESTURETOOL ); + } + break; + } + + if ( e->GetEvent()->HasEndTime() ) + { + pop->add( "Timing Tag...", IDC_ADDTIMINGTAG ); + } + + pop->addSeparator(); + } + } + + // Construct "New..." + mxPopupMenu *newMenu = new mxPopupMenu(); + { + newMenu->add( "Actor...", IDC_ADDACTOR ); + if ( a ) + { + newMenu->add( "Channel...", IDC_ADDCHANNEL ); + } + newMenu->add( "Section Pause...", IDC_ADDEVENT_PAUSE ); + newMenu->add( "Loop...", IDC_ADDEVENT_LOOP ); + newMenu->add( "Fire Completion...", IDC_ADDEVENT_STOPPOINT ); + } + pop->addMenu( "New", newMenu ); + + // Now construct "Edit..." + if ( a || c || e || ge ) + { + mxPopupMenu *editMenu = new mxPopupMenu(); + { + if ( a ) + { + editMenu->add( va( "Actor '%s'...", a->GetActor()->GetName() ), IDC_EDITACTOR ); + } + if ( c ) + { + editMenu->add( va( "Channel '%s'...", c->GetChannel()->GetName() ), IDC_EDITCHANNEL ); + } + if ( ge ) + { + switch ( ge->GetEvent()->GetType() ) + { + default: + break; + case CChoreoEvent::SECTION: + { + editMenu->add( va( "Section Pause '%s'...", ge->GetEvent()->GetName() ), IDC_EDITGLOBALEVENT ); + } + break; + case CChoreoEvent::LOOP: + { + editMenu->add( va( "Loop Point '%s'...", ge->GetEvent()->GetName() ), IDC_EDITGLOBALEVENT ); + } + break; + case CChoreoEvent::STOPPOINT: + { + editMenu->add( va( "Fire Completion '%s'...", ge->GetEvent()->GetName() ), IDC_EDITGLOBALEVENT ); + } + break; + } + } + } + + pop->addMenu( "Edit", editMenu ); + + } + + // Move up/down + if ( a || c ) + { + mxPopupMenu *moveUpMenu = new mxPopupMenu(); + mxPopupMenu *moveDownMenu = new mxPopupMenu(); + + if ( a ) + { + moveUpMenu->add( va( "Move '%s' up", a->GetActor()->GetName() ), IDC_MOVEACTORUP ); + moveDownMenu->add( va( "Move '%s' down", a->GetActor()->GetName() ), IDC_MOVEACTORDOWN ); + } + if ( c ) + { + moveUpMenu->add( va( "Move '%s' up", c->GetChannel()->GetName() ), IDC_MOVECHANNELUP ); + moveDownMenu->add( va( "Move '%s' down", c->GetChannel()->GetName() ), IDC_MOVECHANNELDOWN ); + } + + pop->addMenu( "Move Up", moveUpMenu ); + pop->addMenu( "Move Down", moveDownMenu ); + } + + // Delete + if ( a || c || e || ge || (ct != -1) ) + { + mxPopupMenu *deleteMenu = new mxPopupMenu(); + if ( a ) + { + deleteMenu->add( va( "Actor '%s'", a->GetActor()->GetName() ), IDC_DELETEACTOR ); + } + if ( c ) + { + deleteMenu->add( va( "Channel '%s'", c->GetChannel()->GetName() ), IDC_DELETECHANNEL ); + } + if ( e ) + { + deleteMenu->add( va( "Event '%s'", e->GetEvent()->GetName() ), IDC_DELETEEVENT ); + } + if ( ge ) + { + switch ( ge->GetEvent()->GetType() ) + { + default: + break; + case CChoreoEvent::SECTION: + { + deleteMenu->add( va( "Section Pause '%s'...", ge->GetEvent()->GetName() ), IDC_DELETEGLOBALEVENT ); + } + break; + case CChoreoEvent::LOOP: + { + deleteMenu->add( va( "Loop Point '%s'...", ge->GetEvent()->GetName() ), IDC_DELETEGLOBALEVENT ); + } + break; + case CChoreoEvent::STOPPOINT: + { + deleteMenu->add( va( "Fire Completion '%s'...", ge->GetEvent()->GetName() ), IDC_DELETEGLOBALEVENT ); + } + break; + } + } + if ( e && ct != -1 ) + { + CEventRelativeTag *tag = e->GetEvent()->GetRelativeTag( ct ); + if ( tag ) + { + deleteMenu->add( va( "Relative Tag '%s'...", tag->GetName() ), IDC_DELETERELATIVETAG ); + } + } + pop->addMenu( "Delete", deleteMenu ); + } + + // Select + { + mxPopupMenu *selectMenu = new mxPopupMenu(); + selectMenu->add( "Select All", IDC_SELECTALL ); + selectMenu->add( "Deselect All", IDC_DESELECTALL ); + selectMenu->addSeparator(); + + selectMenu->add( "All events before", IDC_SELECTEVENTS_ALL_BEFORE ); + selectMenu->add( "All events after", IDC_SELECTEVENTS_ALL_AFTER ); + selectMenu->add( "Active events before", IDC_SELECTEVENTS_ACTIVE_BEFORE ); + selectMenu->add( "Active events after", IDC_SELECTEVENTS_ACTIVE_AFTER ); + selectMenu->add( "Channel events before", IDC_SELECTEVENTS_CHANNEL_BEFORE ); + selectMenu->add( "Channel events after", IDC_SELECTEVENTS_CHANNEL_AFTER ); + + if ( a || c ) + { + selectMenu->addSeparator(); + if ( a ) + { + selectMenu->add( va( "All events in actor '%s'", a->GetActor()->GetName() ), IDC_CV_ALLEVENTS_ACTOR ); + } + if ( c ) + { + selectMenu->add( va( "All events in channel '%s'", c->GetChannel()->GetName() ), IDC_CV_ALLEVENTS_CHANNEL ); + } + } + pop->addMenu( "Select/Deselect", selectMenu ); + } + + // Quick delete for events + if ( e ) + { + pop->addSeparator(); + + switch ( e->GetEvent()->GetType() ) + { + default: + break; + case CChoreoEvent::FLEXANIMATION: + { + pop->add( va( "Edit event '%s' in expression tool", e->GetEvent()->GetName() ), IDC_EXPRESSIONTOOL ); + } + break; + case CChoreoEvent::GESTURE: + { + pop->add( va( "Edit event '%s' in gesture tool", e->GetEvent()->GetName() ), IDC_GESTURETOOL ); + } + break; + } + + pop->add( va( "Move event '%s' to back", e->GetEvent()->GetName() ), IDC_MOVETOBACK ); + if ( CountSelectedEvents() > 1 ) + { + pop->add( va( "Delete events" ), IDC_DELETEEVENT ); + pop->addSeparator(); + pop->add( "Enable events", IDC_CV_ENABLEEVENTS ); + pop->add( "Disable events", IDC_CV_DISABLEEVENTS ); + } + else + { + pop->add( va( "Delete event '%s'", e->GetEvent()->GetName() ), IDC_DELETEEVENT ); + pop->addSeparator(); + if ( e->GetEvent()->GetActive() ) + { + pop->add( va( "Disable event '%s'", e->GetEvent()->GetName() ), IDC_CV_DISABLEEVENTS ); + } + else + { + pop->add( va( "Enable event '%s'", e->GetEvent()->GetName() ), IDC_CV_ENABLEEVENTS ); + } + } + } + + if ( m_rgABPoints[ 0 ].active && m_rgABPoints[ 1 ].active ) + { + pop->addSeparator(); + mxPopupMenu *timeMenu = new mxPopupMenu(); + timeMenu->add( "Insert empty space between marks (shifts events right)", IDC_INSERT_TIME ); + timeMenu->add( "Delete events between marks (shifts remaining events left)", IDC_DELETE_TIME ); + pop->addMenu( "Time Marks", timeMenu ); + } + + + // Copy/paste + if ( CanPaste() || e ) + { + pop->addSeparator(); + + if ( CountSelectedEvents() > 1 ) + { + pop->add( va( "Copy events to clipboard" ), IDC_COPYEVENTS ); + } + else if ( e ) + { + pop->add( va( "Copy event '%s' to clipboard", e->GetEvent()->GetName() ), IDC_COPYEVENTS ); + } + + if ( CanPaste() ) + { + pop->add( va( "Paste events" ), IDC_PASTEEVENTS ); + } + } + + // Export / import + pop->addSeparator(); + + if ( e ) + { + mxPopupMenu *exportMenu = new mxPopupMenu(); + if ( CountSelectedEvents() > 1 ) + { + exportMenu->add( va( "Export events to .vce..." ), IDC_EXPORTEVENTS ); + } + else if ( e ) + { + exportMenu->add( va( "Export event '%s' to .vce...", e->GetEvent()->GetName() ), IDC_EXPORTEVENTS ); + } + exportMenu->add( va( "Export as .vcd..." ), IDC_EXPORT_VCD ); + pop->addMenu( "Export", exportMenu ); + } + + mxPopupMenu *importMenu = new mxPopupMenu(); + importMenu->add( va( "Import events from .vce..." ), IDC_IMPORTEVENTS ); + importMenu->add( va( "Merge from .vcd..." ), IDC_IMPORT_VCD ); + pop->addMenu( "Import", importMenu ); + + bool bShowAlignLeft = ( CountSelectedEvents() + CountSelectedGlobalEvents() ) > 1 ? true : false; + + if ( e && ( ( CountSelectedEvents() > 1 ) || bShowAlignLeft ) ) + { + pop->addSeparator(); + + mxPopupMenu *alignMenu = new mxPopupMenu(); + alignMenu->add( "Align Left", IDC_CV_ALIGN_LEFT ); + if ( CountSelectedEvents() > 1 ) + { + alignMenu->add( "Align Right", IDC_CV_ALIGN_RIGHT ); + alignMenu->add( "Size to Smallest", IDC_CV_SAMESIZE_SMALLEST ); + alignMenu->add( "Size to Largest", IDC_CV_SAMESIZE_LARGEST ); + } + pop->addMenu( "Align", alignMenu ); + } + + // Misc. + pop->addSeparator(); + pop->add( va( "Change scale..." ), IDC_CV_CHANGESCALE ); + pop->add( va( "Check sequences" ), IDC_CV_CHECKSEQLENGTHS ); + pop->add( va( "Process sequences" ), IDC_CV_PROCESSSEQUENCES ); + pop->add( va( m_bRampOnly ? "Ramp normal" : "Ramp only" ), IDC_CV_TOGGLERAMPONLY ); + pop->setChecked( IDC_CV_PROCESSSEQUENCES, m_bProcessSequences ); + + bool onmaster= ( m_pClickedChannel && + m_pClickedChannel->GetCaptionClickedEvent() && + m_pClickedChannel->GetCaptionClickedEvent()->GetCloseCaptionType() == CChoreoEvent::CC_MASTER ) ? true : false; + bool ondisabled = ( m_pClickedChannel && + m_pClickedChannel->GetCaptionClickedEvent() && + m_pClickedChannel->GetCaptionClickedEvent()->GetCloseCaptionType() == CChoreoEvent::CC_DISABLED ) ? true : false; + + // The close captioning menu + if ( m_bShowCloseCaptionData && ( AreSelectedEventsCombinable() || AreSelectedEventsInSpeakGroup() || onmaster || ondisabled ) ) + { + pop->addSeparator(); + if ( AreSelectedEventsCombinable() ) + { + pop->add( "Combine Speak Events", IDC_CV_COMBINESPEAKEVENTS ); + } + if ( AreSelectedEventsInSpeakGroup() ) + { + pop->add( "Uncombine Speak Events", IDC_CV_REMOVESPEAKEVENTFROMGROUP ); + } + if ( onmaster ) + { + // Can only change tokens for "combined" files + if ( m_pClickedChannel->GetCaptionClickedEvent()->GetNumSlaves() >= 1 ) + { + pop->add( "Change Token", IDC_CV_CHANGECLOSECAPTIONTOKEN ); + } + pop->add( "Disable captions", IDC_CV_TOGGLECLOSECAPTIONS ); + } + if ( ondisabled ) + { + pop->add( "Enable captions", IDC_CV_TOGGLECLOSECAPTIONS ); + } + } + + // Undo/redo + if ( CanUndo() || CanRedo() ) + { + + pop->addSeparator(); + + if ( CanUndo() ) + { + pop->add( va( "Undo %s", GetUndoDescription() ), IDC_CVUNDO ); + } + if ( CanRedo() ) + { + pop->add( va( "Redo %s", GetRedoDescription() ), IDC_CVREDO ); + } + } + + if ( m_pScene ) + { + // Associate map file + pop->addSeparator(); + pop->add( va( "Associate .bsp (%s)", m_pScene->GetMapname() ), IDC_ASSOCIATEBSP ); + if ( a ) + { + if ( a->GetActor() && a->GetActor()->GetFacePoserModelName()[0] ) + { + pop->add( va( "Change .mdl for %s", a->GetActor()->GetName() ), IDC_ASSOCIATEMODEL ); + } + else + { + pop->add( va( "Associate .mdl with %s", a->GetActor()->GetName() ), IDC_ASSOCIATEMODEL ); + } + } + } + + pop->popup( this, mx, my ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::AssociateModel( void ) +{ + if ( !m_pScene ) + return; + + CChoreoActorWidget *actor = m_pClickedActor; + if ( !actor ) + return; + + CChoreoActor *a = actor->GetActor(); + if ( !a ) + return; + + CChoiceParams params; + strcpy( params.m_szDialogTitle, "Associate Model" ); + + params.m_bPositionDialog = false; + params.m_nLeft = 0; + params.m_nTop = 0; + strcpy( params.m_szPrompt, "Choose model:" ); + + params.m_Choices.RemoveAll(); + + params.m_nSelected = -1; + int oldsel = -1; + + int c = models->Count(); + ChoiceText text; + for ( int i = 0; i < c; i++ ) + { + char const *modelname = models->GetModelName( i ); + + strcpy( text.choice, modelname ); + + if ( !stricmp( a->GetName(), modelname ) ) + { + params.m_nSelected = i; + oldsel = -1; + } + + params.m_Choices.AddToTail( text ); + } + + // Add an extra entry which is "No association" + strcpy( text.choice, "No Associated Model" ); + params.m_Choices.AddToTail( text ); + + if ( !ChoiceProperties( ¶ms ) ) + return; + + if ( params.m_nSelected == oldsel ) + return; + + // Chose something new... + if ( params.m_nSelected >= 0 && + params.m_nSelected < params.m_Choices.Count() ) + { + AssociateModelToActor( a, params.m_nSelected ); + } + else + { + // Chose "No association" + AssociateModelToActor( a, -1 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +// modelindex - +//----------------------------------------------------------------------------- +void CChoreoView::AssociateModelToActor( CChoreoActor *actor, int modelindex ) +{ + Assert( actor ); + + SetDirty( true ); + + PushUndo( "Associate model" ); + + // Chose something new... + if ( modelindex >= 0 && + modelindex < models->Count() ) + { + actor->SetFacePoserModelName( models->GetModelFileName( modelindex ) ); + } + else + { + // Chose "No Associated Model" + actor->SetFacePoserModelName( "" ); + } + + RecomputeWaves(); + + PushRedo( "Associate model" ); +} + +void CChoreoView::AssociateBSP( void ) +{ + if ( !m_pScene ) + return; + + // Strip game directory and slash + char mapname[ 512 ]; + if ( !FacePoser_ShowOpenFileNameDialog( mapname, sizeof( mapname ), "maps", "*.bsp" ) ) + { + return; + } + + m_pScene->SetMapname( mapname ); + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::DrawFocusRect( void ) +{ + HDC dc = GetDC( NULL ); + + for ( int i = 0; i < m_FocusRects.Size(); i++ ) + { + RECT rc = m_FocusRects[ i ].m_rcFocus; + + ::DrawFocusRect( dc, &rc ); + } + + ReleaseDC( NULL, dc ); +} + +int CChoreoView::GetSelectedEventWidgets( CUtlVector< CChoreoEventWidget * >& events ) +{ + events.RemoveAll(); + + int c = 0; + + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *actor = m_SceneActors[ i ]; + if ( !actor ) + continue; + + for ( int j = 0; j < actor->GetNumChannels(); j++ ) + { + CChoreoChannelWidget *channel = actor->GetChannel( j ); + if ( !channel ) + continue; + + for ( int k = 0; k < channel->GetNumEvents(); k++ ) + { + CChoreoEventWidget *event = channel->GetEvent( k ); + if ( !event ) + continue; + + if ( event->IsSelected() ) + { + events.AddToTail( event ); + c++; + } + } + } + } + + return c; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : events - +// Output : int +//----------------------------------------------------------------------------- +int CChoreoView::GetSelectedEvents( CUtlVector< CChoreoEvent * >& events ) +{ + events.RemoveAll(); + + int c = 0; + + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *actor = m_SceneActors[ i ]; + if ( !actor ) + continue; + + for ( int j = 0; j < actor->GetNumChannels(); j++ ) + { + CChoreoChannelWidget *channel = actor->GetChannel( j ); + if ( !channel ) + continue; + + for ( int k = 0; k < channel->GetNumEvents(); k++ ) + { + CChoreoEventWidget *event = channel->GetEvent( k ); + if ( !event ) + continue; + + if ( event->IsSelected() ) + { + events.AddToTail( event->GetEvent() ); + c++; + } + } + } + } + + return c; +} + +int CChoreoView::CountSelectedGlobalEvents( void ) +{ + int c = 0; + for ( int i = 0; i < m_SceneGlobalEvents.Size(); i++ ) + { + CChoreoGlobalEventWidget *event = m_SceneGlobalEvents[ i ]; + if ( !event || !event->IsSelected() ) + continue; + + ++c; + } + return c; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoView::CountSelectedEvents( void ) +{ + int c = 0; + + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *actor = m_SceneActors[ i ]; + if ( !actor ) + continue; + + for ( int j = 0; j < actor->GetNumChannels(); j++ ) + { + CChoreoChannelWidget *channel = actor->GetChannel( j ); + if ( !channel ) + continue; + + for ( int k = 0; k < channel->GetNumEvents(); k++ ) + { + CChoreoEventWidget *event = channel->GetEvent( k ); + if ( !event ) + continue; + + if ( event->IsSelected() ) + c++; + + } + } + } + + return c; +} + +bool CChoreoView::IsMouseOverEvent( CChoreoEventWidget *ew, int mx, int my ) +{ + int tolerance = DRAG_EVENT_EDGE_TOLERANCE; + + RECT bounds = ew->getBounds(); + mx -= bounds.left; + my -= bounds.top; + + if ( mx <= -tolerance ) + { + return false; + } + + CChoreoEvent *event = ew->GetEvent(); + if ( event ) + { + if ( event->HasEndTime() ) + { + int rightside = ew->GetDurationRightEdge() ? ew->GetDurationRightEdge() : ew->w(); + + if ( mx > rightside + tolerance ) + { + return false; + } + } + } + + return true; +} + + +bool CChoreoView::IsMouseOverEventEdge( CChoreoEventWidget *ew, bool bLeftEdge, int mx, int my ) +{ + int tolerance = DRAG_EVENT_EDGE_TOLERANCE; + + RECT bounds = ew->getBounds(); + mx -= bounds.left; + my -= bounds.top; + + CChoreoEvent *event = ew->GetEvent(); + if ( event && event->HasEndTime() ) + { + if ( mx > -tolerance && mx <= tolerance ) + { + return bLeftEdge; + } + + int rightside = ew->GetDurationRightEdge() ? ew->GetDurationRightEdge() : ew->w(); + + if ( mx >= rightside - tolerance ) + { + if ( mx > rightside + tolerance ) + { + return false; + } + else + { + return !bLeftEdge; + } + } + } + + return false; +} + +int CChoreoView::GetEarliestEventIndex( CUtlVector< CChoreoEventWidget * >& events ) +{ + int best = -1; + float minTime = FLT_MAX; + + int c = events.Count(); + for ( int i = 0; i < c; ++i ) + { + CChoreoEvent *e = events[ i ]->GetEvent(); + float t = e->GetStartTime(); + if ( t < minTime ) + { + minTime = t; + best = i; + } + } + + return best; +} + +int CChoreoView::GetLatestEventIndex( CUtlVector< CChoreoEventWidget * >& events ) +{ + int best = -1; + float maxTime = FLT_MIN; + + int c = events.Count(); + for ( int i = 0; i < c; ++i ) + { + CChoreoEvent *e = events[ i ]->GetEvent(); + float t = e->GetEndTime(); + if ( t > maxTime ) + { + maxTime = t; + best = i; + } + } + + return best; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// Output : int +//----------------------------------------------------------------------------- +int CChoreoView::ComputeEventDragType( int mx, int my ) +{ + int tolerance = DRAG_EVENT_EDGE_TOLERANCE; + + // Iterate the events and see who's closest + CChoreoEventWidget *ew = GetEventUnderCursorPos( mx, my ); + if ( !ew ) + { + return DRAGTYPE_NONE; + } + + // Deal with small windows by lowering tolerance + if ( ew->w() < 4 * tolerance ) + { + tolerance = 2; + } + + int tagnum = GetTagUnderCursorPos( ew, mx, my ); + if ( tagnum != -1 && CountSelectedEvents() <= 1 ) + { + return DRAGTYPE_EVENTTAG_MOVE; + } + + CEventAbsoluteTag *tag = GetAbsoluteTagUnderCursorPos( ew, mx, my ); + if ( tag != NULL && CountSelectedEvents() <= 1 ) + { + return DRAGTYPE_EVENTABSTAG_MOVE; + } + + if ( CountSelectedEvents() > 1 ) + { + CUtlVector< CChoreoEventWidget * > events; + GetSelectedEventWidgets( events ); + + int iStart, iEnd; + iStart = GetEarliestEventIndex( events ); + iEnd = GetLatestEventIndex( events ); + + if ( events.IsValidIndex( iStart ) ) + { + // See if mouse is over left edge of starting event + if ( IsMouseOverEventEdge( events[ iStart ], true, mx, my ) ) + { + return DRAGTYPE_RESCALELEFT; + } + } + if ( events.IsValidIndex( iEnd ) ) + { + if ( IsMouseOverEventEdge( events[ iEnd ], false, mx, my ) ) + { + return DRAGTYPE_RESCALERIGHT; + } + } + + return DRAGTYPE_EVENT_MOVE; + } + + CChoreoEvent *event = ew->GetEvent(); + if ( event ) + { + if ( event->IsFixedLength() || !event->HasEndTime() ) + { + return DRAGTYPE_EVENT_MOVE; + } + } + + if ( IsMouseOverEventEdge( ew, true, mx, my ) ) + { + if ( GetAsyncKeyState( VK_SHIFT ) ) + return DRAGTYPE_EVENT_STARTTIME_RESCALE; + return DRAGTYPE_EVENT_STARTTIME; + } + + if ( IsMouseOverEventEdge( ew, false, mx, my ) ) + { + if ( GetAsyncKeyState( VK_SHIFT ) ) + return DRAGTYPE_EVENT_ENDTIME_RESCALE; + return DRAGTYPE_EVENT_ENDTIME; + } + + if ( IsMouseOverEvent( ew, mx, my ) ) + { + return DRAGTYPE_EVENT_MOVE; + } + + return DRAGTYPE_NONE; +} + +void CChoreoView::StartDraggingSceneEndTime( int mx, int my ) +{ + m_nDragType = DRAGTYPE_SCENE_ENDTIME; + + m_FocusRects.Purge(); + + RECT rcFocus; + rcFocus.left = mx; + rcFocus.top = 0; + rcFocus.bottom = h2(); + rcFocus.right = rcFocus.left + 2; + + POINT offset; + offset.x = 0; + offset.y = 0; + ClientToScreen( (HWND)getHandle(), &offset ); + OffsetRect( &rcFocus, offset.x, offset.y ); + + CFocusRect fr; + fr.m_rcFocus = rcFocus; + fr.m_rcOrig = rcFocus; + + // Relative tag events don't move + m_FocusRects.AddToTail( fr ); + + m_xStart = mx; + m_yStart = my; + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + + DrawFocusRect(); + + m_bDragging = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::StartDraggingEvent( int mx, int my ) +{ + m_nDragType = ComputeEventDragType( mx, my ); + if ( m_nDragType == DRAGTYPE_NONE ) + { + if( m_pClickedGlobalEvent ) + { + m_nDragType = DRAGTYPE_EVENT_MOVE; + } + else + { + return; + } + } + + m_FocusRects.Purge(); + + // Go through all selected events + RECT rcFocus; + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *actor = m_SceneActors[ i ]; + if ( !actor ) + continue; + + for ( int j = 0; j < actor->GetNumChannels(); j++ ) + { + CChoreoChannelWidget *channel = actor->GetChannel( j ); + if ( !channel ) + continue; + + for ( int k = 0; k < channel->GetNumEvents(); k++ ) + { + CChoreoEventWidget *event = channel->GetEvent( k ); + if ( !event ) + continue; + + if ( !event->IsSelected() ) + continue; + + if ( event == m_pClickedEvent && + ( m_nClickedTag != -1 || m_pClickedAbsoluteTag ) ) + { + int leftEdge = 0; + int tagWidth = 1; + if ( !m_pClickedAbsoluteTag ) + { + CEventRelativeTag *tag = event->GetEvent()->GetRelativeTag( m_nClickedTag ); + if ( tag ) + { + // Determine left edcge + RECT bounds; + bounds = event->getBounds(); + if ( bounds.right - bounds.left > 0 ) + { + leftEdge = (int)( tag->GetPercentage() * (float)( bounds.right - bounds.left ) + 0.5f ); + } + } + } + else + { + // Determine left edcge + RECT bounds; + bounds = event->getBounds(); + if ( bounds.right - bounds.left > 0 ) + { + leftEdge = (int)( m_pClickedAbsoluteTag->GetPercentage() * (float)( bounds.right - bounds.left ) + 0.5f ); + } + } + + rcFocus.left = event->x() + leftEdge - tagWidth; + rcFocus.top = event->y() - tagWidth; + rcFocus.right = rcFocus.left + 2 * tagWidth; + rcFocus.bottom = event->y() + event->h(); + } + else + { + rcFocus.left = event->x(); + rcFocus.top = event->y(); + if ( event->GetDurationRightEdge() ) + { + rcFocus.right = event->x() + event->GetDurationRightEdge(); + } + else + { + rcFocus.right = rcFocus.left + event->w(); + } + rcFocus.bottom = rcFocus.top + event->h(); + } + + POINT offset; + offset.x = 0; + offset.y = 0; + ClientToScreen( (HWND)getHandle(), &offset ); + OffsetRect( &rcFocus, offset.x, offset.y ); + + CFocusRect fr; + fr.m_rcFocus = rcFocus; + fr.m_rcOrig = rcFocus; + + // Relative tag events don't move + m_FocusRects.AddToTail( fr ); + } + } + } + + for ( int i = 0; i < m_SceneGlobalEvents.Count(); i++ ) + { + CChoreoGlobalEventWidget *gew = m_SceneGlobalEvents[ i ]; + if ( !gew ) + continue; + + if ( !gew->IsSelected() ) + continue; + + rcFocus.left = gew->x() + gew->w() / 2; + rcFocus.top = 0; + rcFocus.right = rcFocus.left + 2; + rcFocus.bottom = h2(); + + POINT offset; + offset.x = 0; + offset.y = 0; + ClientToScreen( (HWND)getHandle(), &offset ); + OffsetRect( &rcFocus, offset.x, offset.y ); + + CFocusRect fr; + fr.m_rcFocus = rcFocus; + fr.m_rcOrig = rcFocus; + + m_FocusRects.AddToTail( fr ); + } + + m_xStart = mx; + m_yStart = my; + m_hPrevCursor = NULL; + switch ( m_nDragType ) + { + default: + break; + case DRAGTYPE_EVENTTAG_MOVE: + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + break; + case DRAGTYPE_EVENTABSTAG_MOVE: + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_IBEAM ) ); + break; + case DRAGTYPE_EVENT_MOVE: + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEALL ) ); + break; + case DRAGTYPE_EVENT_STARTTIME: + case DRAGTYPE_EVENT_STARTTIME_RESCALE: + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + break; + case DRAGTYPE_EVENT_ENDTIME: + case DRAGTYPE_EVENT_ENDTIME_RESCALE: + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + break; + case DRAGTYPE_RESCALELEFT: + case DRAGTYPE_RESCALERIGHT: + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + break; + } + + DrawFocusRect(); + + m_bDragging = true; +} + +bool CChoreoView::IsMouseOverSceneEndTime( int mx ) +{ + // See if mouse if over scene end time instead + if ( m_pScene ) + { + float endtime = m_pScene->FindStopTime(); + + bool clip = false; + int lastpixel = GetPixelForTimeValue( endtime, &clip ); + if ( !clip ) + { + if ( abs( mx - lastpixel ) < DRAG_EVENT_EDGE_TOLERANCE ) + { + return true; + } + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +//----------------------------------------------------------------------------- +void CChoreoView::MouseStartDrag( mxEvent *event, int mx, int my ) +{ + bool isrightbutton = event->buttons & mxEvent::MouseRightButton ? true : false; + + if ( m_bDragging ) + { + return; + } + + GetObjectsUnderMouse( mx, my, &m_pClickedActor, &m_pClickedChannel, &m_pClickedEvent, &m_pClickedGlobalEvent, &m_nClickedTag, &m_pClickedAbsoluteTag, &m_nClickedChannelCloseCaptionButton ); + + if ( m_pClickedEvent ) + { + CChoreoEvent *e = m_pClickedEvent->GetEvent(); + Assert( e ); + + int dtPreview = ComputeEventDragType( mx, my ); + // Shift clicking on exact edge shouldn't toggle selection state + bool bIsEdgeRescale = ( dtPreview == DRAGTYPE_EVENT_ENDTIME_RESCALE || dtPreview == DRAGTYPE_EVENT_STARTTIME_RESCALE ); + + if ( !( event->modifiers & ( mxEvent::KeyCtrl | mxEvent::KeyShift ) ) ) + { + if ( !m_pClickedEvent->IsSelected() ) + { + DeselectAll(); + } + TraverseWidgets( &CChoreoView::Select, m_pClickedEvent ); + } + else if ( !bIsEdgeRescale ) + { + m_pClickedEvent->SetSelected( !m_pClickedEvent->IsSelected() ); + } + + switch ( m_pClickedEvent->GetEvent()->GetType() ) + { + default: + break; + case CChoreoEvent::FLEXANIMATION: + { + g_pExpressionTool->SetEvent( e ); + g_pFlexPanel->SetEvent( e ); + } + break; + case CChoreoEvent::GESTURE: + { + g_pGestureTool->SetEvent( e ); + } + break; + case CChoreoEvent::SPEAK: + { + g_pWaveBrowser->SetEvent( e ); + } + break; + } + + if ( e->HasEndTime() ) + { + g_pRampTool->SetEvent( e ); + } + + redraw(); + StartDraggingEvent( mx, my ); + } + else if ( m_pClickedGlobalEvent ) + { + if ( !( event->modifiers & ( mxEvent::KeyCtrl | mxEvent::KeyShift ) ) ) + { + if ( !m_pClickedGlobalEvent->IsSelected() ) + { + DeselectAll(); + } + TraverseWidgets( &CChoreoView::Select, m_pClickedGlobalEvent ); + } + else + { + m_pClickedGlobalEvent->SetSelected( !m_pClickedGlobalEvent->IsSelected() ); + } + + redraw(); + StartDraggingEvent( mx, my ); + } + else if ( IsMouseOverScrubArea( event ) ) + { + if ( IsMouseOverScrubHandle( event ) ) + { + m_nDragType = DRAGTYPE_SCRUBBER; + + m_bDragging = true; + + float t = GetTimeValueForMouse( (short)event->x ); + m_flScrubberTimeOffset = m_flScrub - t; + float maxoffset = 0.5f * (float)SCRUBBER_HANDLE_WIDTH / GetPixelsPerSecond(); + m_flScrubberTimeOffset = clamp( m_flScrubberTimeOffset, -maxoffset, maxoffset ); + t += m_flScrubberTimeOffset; + + ClampTimeToSelectionInterval( t ); + + SetScrubTime( t ); + SetScrubTargetTime( t ); + + redraw(); + + RECT rcScrub; + GetScrubHandleRect( rcScrub, true ); + + m_FocusRects.Purge(); + + // Go through all selected events + RECT rcFocus; + + rcFocus.top = GetStartRow(); + rcFocus.bottom = h2() - m_nScrollbarHeight - m_nInfoHeight; + rcFocus.left = ( rcScrub.left + rcScrub.right ) / 2; + rcFocus.right = rcFocus.left; + + POINT pt; + pt.x = pt.y = 0; + ClientToScreen( (HWND)getHandle(), &pt ); + + OffsetRect( &rcFocus, pt.x, pt.y ); + + CFocusRect fr; + fr.m_rcFocus = rcFocus; + fr.m_rcOrig = rcFocus; + + m_FocusRects.AddToTail( fr ); + + m_xStart = mx; + m_yStart = my; + + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + + DrawFocusRect(); + } + else + { + float t = GetTimeValueForMouse( mx ); + + ClampTimeToSelectionInterval( t ); + + SetScrubTargetTime( t ); + + // Unpause the scene + m_bPaused = false; + redraw(); + } + } + else if ( IsMouseOverSceneEndTime( mx ) ) + { + redraw(); + StartDraggingSceneEndTime( mx, my ); + } + else if ( m_pClickedChannel && + m_nClickedChannelCloseCaptionButton != CChoreoChannelWidget::CLOSECAPTION_NONE && + m_nClickedChannelCloseCaptionButton != CChoreoChannelWidget::CLOSECAPTION_CAPTION ) + { + switch ( m_nClickedChannelCloseCaptionButton ) + { + default: + case CChoreoChannelWidget::CLOSECAPTION_EXPANDCOLLAPSE: + { + OnToggleCloseCaptionTags(); + } + break; + case CChoreoChannelWidget::CLOSECAPTION_PREVLANGUAGE: + { + // Change language + int id = GetCloseCaptionLanguageId(); + --id; + if ( id < 0 ) + { + id = CC_NUM_LANGUAGES - 1; + Assert( id >= 0 ); + } + SetCloseCaptionLanguageId( id ); + redraw(); + } + break; + case CChoreoChannelWidget::CLOSECAPTION_NEXTLANGUAGE: + { + int id = GetCloseCaptionLanguageId(); + ++id; + if ( id >= CC_NUM_LANGUAGES ) + { + id = 0; + } + SetCloseCaptionLanguageId( id ); + redraw(); + } + break; + case CChoreoChannelWidget::CLOSECAPTION_SELECTOR: + { + SetDirty( true ); + + PushUndo( "Change selector" ); + + m_pClickedChannel->HandleSelectorClicked(); + + PushRedo( "Change selector" ); + + redraw(); + } + break; + } + } + else + { + if ( !( event->modifiers & ( mxEvent::KeyCtrl | mxEvent::KeyShift ) ) ) + { + DeselectAll(); + + if ( !isrightbutton ) + { + if ( realtime - m_flLastMouseClickTime < 0.3f ) + { + OnDoubleClicked(); + m_flLastMouseClickTime = -1.0f; + } + else + { + m_flLastMouseClickTime = realtime; + } + } + + redraw(); + } + } + + CalcBounds( m_nDragType ); +} + +void CChoreoView::OnDoubleClicked() +{ + if ( m_pClickedChannel ) + { + switch (m_nClickedChannelCloseCaptionButton ) + { + default: + break; + case CChoreoChannelWidget::CLOSECAPTION_NONE: + { + SetDirty( true ); + PushUndo( "Enable/disable Channel" ); + + m_pClickedChannel->GetChannel()->SetActive( !m_pClickedChannel->GetChannel()->GetActive() ); + + PushRedo( "Enable/disable Channel" ); + } + break; + case CChoreoChannelWidget::CLOSECAPTION_CAPTION: + { + CChoreoEvent *e = m_pClickedChannel->GetCaptionClickedEvent(); + if ( e && e->GetNumSlaves() >= 1 ) + { + OnChangeCloseCaptionToken( e ); + } + } + break; + } + + return; + } + + if ( m_pClickedActor ) + { + SetDirty( true ); + PushUndo( "Enable/disable Actor" ); + + m_pClickedActor->GetActor()->SetActive( !m_pClickedActor->GetActor()->GetActive() ); + + PushRedo( "Enable/disable Actor" ); + return; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +//----------------------------------------------------------------------------- +void CChoreoView::MouseContinueDrag( mxEvent *event, int mx, int my ) +{ + if ( !m_bDragging ) + return; + + DrawFocusRect(); + + ApplyBounds( mx, my ); + + for ( int i = 0; i < m_FocusRects.Size(); i++ ) + { + CFocusRect *f = &m_FocusRects[ i ]; + f->m_rcFocus = f->m_rcOrig; + + switch ( m_nDragType ) + { + default: + case DRAGTYPE_SCRUBBER: + { + float t = GetTimeValueForMouse( mx ); + t += m_flScrubberTimeOffset; + + ClampTimeToSelectionInterval( t ); + + float dt = t - m_flScrub; + + SetScrubTargetTime( t ); + + m_bSimulating = true; + ScrubThink( dt, true, this ); + + SetScrubTime( t ); + + OffsetRect( &f->m_rcFocus, ( mx - m_xStart ), 0 ); + } + break; + case DRAGTYPE_EVENT_MOVE: + case DRAGTYPE_EVENTTAG_MOVE: + case DRAGTYPE_EVENTABSTAG_MOVE: + { + int dx = mx - m_xStart; + int dy = my - m_yStart; + if ( m_pClickedEvent ) + { + bool shiftdown = ( event->modifiers & mxEvent::KeyShift ) ? true : false; + + + // Only allow jumping channels if shift is down + if ( !shiftdown ) + { + dy = 0; + } + if ( abs( dy ) < m_pClickedEvent->GetItemHeight() ) + { + dy = 0; + } + if ( m_nSelectedEvents > 1 ) + { + dy = 0; + } + if ( m_nDragType == DRAGTYPE_EVENTTAG_MOVE || m_nDragType == DRAGTYPE_EVENTABSTAG_MOVE ) + { + dy = 0; + } + + if ( m_pClickedEvent->GetEvent()->IsUsingRelativeTag() ) + { + dx = 0; + } + } + else + { + dy = 0; + } + OffsetRect( &f->m_rcFocus, dx, dy ); + } + break; + case DRAGTYPE_EVENT_STARTTIME: + case DRAGTYPE_EVENT_STARTTIME_RESCALE: + f->m_rcFocus.left += ( mx - m_xStart ); + break; + case DRAGTYPE_EVENT_ENDTIME: + case DRAGTYPE_EVENT_ENDTIME_RESCALE: + f->m_rcFocus.right += ( mx - m_xStart ); + break; + case DRAGTYPE_SCENE_ENDTIME: + OffsetRect( &f->m_rcFocus, ( mx - m_xStart ), 0 ); + break; + case DRAGTYPE_RESCALELEFT: + case DRAGTYPE_RESCALERIGHT: + //f->m_rcFocus.right += ( mx - m_xStart ); + break; + } + } + + if ( m_nDragType == DRAGTYPE_RESCALELEFT || + m_nDragType == DRAGTYPE_RESCALERIGHT ) + { + int c = m_FocusRects.Count(); + int m_nStart = INT_MAX; + int m_nEnd = INT_MIN; + + for ( int i = 0; i < c; ++i ) + { + CFocusRect *f = &m_FocusRects[ i ]; + if ( f->m_rcFocus.left < m_nStart ) + { + m_nStart = f->m_rcFocus.left; + } + if ( f->m_rcFocus.right > m_nEnd ) + { + m_nEnd = f->m_rcFocus.right; + } + } + + // Now figure out rescaling logic + int dxPixels = mx - m_xStart; + + int oldSize = m_nEnd - m_nStart; + if ( oldSize > 0 ) + { + float rescale = 1.0f; + if ( m_nDragType == DRAGTYPE_RESCALERIGHT ) + { + rescale = (float)( oldSize + dxPixels )/(float)oldSize; + } + else + { + rescale = (float)( oldSize - dxPixels )/(float)oldSize; + } + + for ( int i = 0; i < c; ++i ) + { + CFocusRect *f = &m_FocusRects[ i ]; + int w = f->m_rcFocus.right - f->m_rcFocus.left; + if ( m_nDragType == DRAGTYPE_RESCALERIGHT ) + { + f->m_rcFocus.left = m_nStart + ( int )( rescale * (float)( f->m_rcFocus.left - m_nStart ) + 0.5f ); + f->m_rcFocus.right = f->m_rcFocus.left + ( int )( rescale * (float)w + 0.5f ); + } + else + { + f->m_rcFocus.right = m_nEnd - ( int )( rescale * (float)( m_nEnd - f->m_rcFocus.right ) + 0.5f ); + f->m_rcFocus.left = f->m_rcFocus.right - ( int )( rescale * (float)w + 0.5f ); + } + } + } + } + DrawFocusRect(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +//----------------------------------------------------------------------------- +void CChoreoView::MouseMove( int mx, int my ) +{ + if ( m_bDragging ) + return; + + int dragtype = ComputeEventDragType( mx, my ); + if ( dragtype == DRAGTYPE_NONE ) + { + CChoreoGlobalEventWidget *ge = NULL; + GetObjectsUnderMouse( mx, my, NULL, NULL, NULL, &ge, NULL, NULL, NULL ); + if ( ge ) + { + dragtype = DRAGTYPE_EVENT_MOVE; + } + + if ( dragtype == DRAGTYPE_NONE ) + { + if ( IsMouseOverSceneEndTime( mx ) ) + { + dragtype = DRAGTYPE_SCENE_ENDTIME; + } + } + } + + if ( m_hPrevCursor ) + { + SetCursor( m_hPrevCursor ); + m_hPrevCursor = NULL; + } + switch ( dragtype ) + { + default: + break; + case DRAGTYPE_EVENTTAG_MOVE: + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + break; + case DRAGTYPE_EVENTABSTAG_MOVE: + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_IBEAM ) ); + break; + case DRAGTYPE_EVENT_MOVE: + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEALL ) ); + break; + case DRAGTYPE_EVENT_STARTTIME: + case DRAGTYPE_EVENT_STARTTIME_RESCALE: + case DRAGTYPE_EVENT_ENDTIME: + case DRAGTYPE_EVENT_ENDTIME_RESCALE: + case DRAGTYPE_SCENE_ENDTIME: + case DRAGTYPE_RESCALELEFT: + case DRAGTYPE_RESCALERIGHT: + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *e - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoView::CheckGestureLength( CChoreoEvent *e, bool bCheckOnly ) +{ + Assert( e ); + if ( !e ) + return false; + + if ( e->GetType() != CChoreoEvent::GESTURE ) + { + Con_Printf( "CheckGestureLength: called on non-GESTURE event %s\n", e->GetName() ); + return false; + } + + StudioModel *model = FindAssociatedModel( e->GetScene(), e->GetActor() ); + if ( !model ) + return false; + + CStudioHdr *pStudioHdr = model->GetStudioHdr(); + if ( !pStudioHdr ) + return false; + + return UpdateGestureLength( e, pStudioHdr, model->GetPoseParameters(), bCheckOnly ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *e - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoView::DefaultGestureLength( CChoreoEvent *e, bool bCheckOnly ) +{ + Assert( e ); + if ( !e ) + return false; + + if ( e->GetType() != CChoreoEvent::GESTURE ) + { + Con_Printf( "DefaultGestureLength: called on non-GESTURE event %s\n", e->GetName() ); + return false; + } + + StudioModel *model = FindAssociatedModel( e->GetScene(), e->GetActor() ); + if ( !model ) + return false; + + if ( !model->GetStudioHdr() ) + return false; + + int iSequence = model->LookupSequence( e->GetParameters() ); + if ( iSequence < 0 ) + return false; + + bool bret = false; + + float seqduration = model->GetDuration( iSequence ); + if ( seqduration != 0.0f ) + { + bret = true; + if ( !bCheckOnly ) + { + e->SetEndTime( e->GetStartTime() + seqduration ); + } + } + + return bret; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *e - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoView::AutoaddGestureKeys( CChoreoEvent *e, bool bCheckOnly ) +{ + if ( !e ) + return false; + + StudioModel *model = FindAssociatedModel( e->GetScene(), e->GetActor() ); + if ( !model ) + return false; + + CStudioHdr *pStudioHdr = model->GetStudioHdr(); + if ( !pStudioHdr ) + return false; + + return AutoAddGestureKeys( e, pStudioHdr, model->GetPoseParameters(), bCheckOnly ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CChoreoView::CheckSequenceLength( CChoreoEvent *e, bool bCheckOnly ) +{ + Assert( e ); + if ( !e ) + return false; + + if ( e->GetType() != CChoreoEvent::SEQUENCE ) + { + Con_Printf( "CheckSequenceLength: called on non-SEQUENCE event %s\n", e->GetName() ); + return false; + } + + StudioModel *model = FindAssociatedModel( e->GetScene(), e->GetActor() ); + if ( !model ) + return false; + + CStudioHdr *pStudioHdr = model->GetStudioHdr(); + if ( !pStudioHdr ) + return false; + + return UpdateSequenceLength( e, pStudioHdr, model->GetPoseParameters(), bCheckOnly, true ); +} + +void CChoreoView::FinishDraggingSceneEndTime( mxEvent *event, int mx, int my ) +{ + DrawFocusRect(); + + m_FocusRects.Purge(); + + m_bDragging = false; + + float mouse_dt = GetTimeDeltaForMouseDelta( mx, m_xStart ); + if ( !mouse_dt ) + { + return; + } + + SetDirty( true ); + + const char *desc = "Change Scene Duration"; + + PushUndo( desc ); + + float newendtime = GetTimeValueForMouse( mx ); + float oldendtime = m_pScene->FindStopTime(); + + float scene_dt = newendtime - oldendtime; + + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *a = m_SceneActors[ i ]; + if ( !a ) + continue; + + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannelWidget *channel = a->GetChannel( j ); + if ( !channel ) + continue; + + int k; + + CChoreoEvent *finalGesture = NULL; + for ( k = channel->GetNumEvents() - 1; k >= 0; k-- ) + { + CChoreoEventWidget *event = channel->GetEvent( k ); + CChoreoEvent *e = event->GetEvent(); + if ( e->GetType() != CChoreoEvent::GESTURE ) + continue; + + if ( !finalGesture ) + { + finalGesture = e; + } + else + { + if ( e->GetStartTime() > finalGesture->GetStartTime() ) + { + finalGesture = e; + } + } + } + + + for ( k = channel->GetNumEvents() - 1; k >= 0; k-- ) + { + CChoreoEventWidget *event = channel->GetEvent( k ); + CChoreoEvent *e = event->GetEvent(); + + // Event starts after new end time, kill it + if ( e->GetStartTime() > newendtime ) + { + channel->GetChannel()->RemoveEvent( e ); + m_pScene->DeleteReferencedObjects( e ); + continue; + } + + // No change to normal events that end earlier than new time (but do change gestures) + if ( e->GetEndTime() < newendtime && + e != finalGesture ) + { + continue; + } + + float dt = scene_dt; + if ( e->GetType() == CChoreoEvent::GESTURE ) + { + if ( e->GetEndTime() < newendtime ) + { + dt = newendtime - e->GetEndTime(); + } + } + + float newduration = e->GetDuration() + dt; + RescaleRamp( e, newduration ); + switch ( e->GetType() ) + { + default: + break; + case CChoreoEvent::GESTURE: + { + e->RescaleGestureTimes( e->GetStartTime(), e->GetEndTime() + dt, true ); + } + break; + case CChoreoEvent::FLEXANIMATION: + { + RescaleExpressionTimes( e, e->GetStartTime(), e->GetEndTime() + dt ); + } + break; + } + e->OffsetEndTime( dt ); + e->SnapTimes(); + e->ResortRamp(); + } + } + } + + // Remove event and move to new object + DeleteSceneWidgets(); + + m_nDragType = DRAGTYPE_NONE; + + if ( m_hPrevCursor ) + { + SetCursor( m_hPrevCursor ); + m_hPrevCursor = 0; + } + + PushRedo( desc ); + + CreateSceneWidgets(); + + InvalidateLayout(); + + g_pExpressionTool->LayoutItems( true ); + g_pExpressionTool->redraw(); + g_pGestureTool->redraw(); + g_pRampTool->redraw(); + g_pSceneRampTool->redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called after association changes to reset .wav file images +// Input : - +//----------------------------------------------------------------------------- +void CChoreoView::RecomputeWaves() +{ + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *a = m_SceneActors[ i ]; + if ( !a ) + continue; + + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannelWidget *c = a->GetChannel( j ); + if ( !c ) + continue; + + for ( int k = 0; k < c->GetNumEvents(); k++ ) + { + CChoreoEventWidget *e = c->GetEvent( k ); + if ( !e ) + continue; + + e->RecomputeWave(); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +//----------------------------------------------------------------------------- +void CChoreoView::FinishDraggingEvent( mxEvent *event, int mx, int my ) +{ + DrawFocusRect(); + + m_FocusRects.Purge(); + + m_bDragging = false; + + float dt = GetTimeDeltaForMouseDelta( mx, m_xStart ); + if ( !dt ) + { + if ( m_pScene && m_pClickedEvent && m_pClickedEvent->GetEvent()->GetType() == CChoreoEvent::SPEAK ) + { + // Show phone wav in wav viewer + char sndname[ 512 ]; + Q_strncpy( sndname, FacePoser_TranslateSoundName( m_pClickedEvent->GetEvent() ), sizeof( sndname ) ); + if ( sndname[ 0 ] ) + { + SetCurrentWaveFile( va( "sound/%s", sndname ), m_pClickedEvent->GetEvent() ); + } + else + { + Warning( "Unable to resolve sound name for '%s', check actor associations\n", m_pClickedEvent->GetEvent()->GetName() ); + } + } + return; + } + + SetDirty( true ); + + char const *desc = ""; + + switch ( m_nDragType ) + { + default: + case DRAGTYPE_EVENT_MOVE: + desc = "Event Move"; + break; + case DRAGTYPE_EVENT_STARTTIME: + case DRAGTYPE_EVENT_STARTTIME_RESCALE: + desc = "Change Start Time"; + break; + case DRAGTYPE_EVENT_ENDTIME: + case DRAGTYPE_EVENT_ENDTIME_RESCALE: + desc = "Change End Time"; + break; + case DRAGTYPE_EVENTTAG_MOVE: + desc = "Move Event Tag"; + break; + case DRAGTYPE_EVENTABSTAG_MOVE: + desc = "Move Abs Event Tag"; + break; + case DRAGTYPE_RESCALELEFT: + case DRAGTYPE_RESCALERIGHT: + desc = "Rescale Time"; + break; + } + PushUndo( desc ); + + CUtlVector< CChoreoEvent * > rescaleHelper; + + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *actor = m_SceneActors[ i ]; + if ( !actor ) + continue; + + for ( int j = 0; j < actor->GetNumChannels(); j++ ) + { + CChoreoChannelWidget *channel = actor->GetChannel( j ); + if ( !channel ) + continue; + + for ( int k = 0; k < channel->GetNumEvents(); k++ ) + { + CChoreoEventWidget *event = channel->GetEvent( k ); + if ( !event ) + continue; + + if ( !event->IsSelected() ) + continue; + + // Figure out true dt + CChoreoEvent *e = event->GetEvent(); + if ( e ) + { + switch ( m_nDragType ) + { + default: + case DRAGTYPE_EVENT_MOVE: + e->OffsetTime( dt ); + e->SnapTimes(); + break; + case DRAGTYPE_EVENT_STARTTIME: + case DRAGTYPE_EVENT_STARTTIME_RESCALE: + { + float newduration = e->GetDuration() - dt; + RescaleRamp( e, newduration ); + switch ( e->GetType() ) + { + default: + break; + case CChoreoEvent::GESTURE: + { + e->RescaleGestureTimes( e->GetStartTime() + dt, e->GetEndTime(), m_nDragType == DRAGTYPE_EVENT_STARTTIME ); + } + break; + case CChoreoEvent::FLEXANIMATION: + { + RescaleExpressionTimes( e, e->GetStartTime() + dt, e->GetEndTime() ); + } + break; + } + e->OffsetStartTime( dt ); + e->SnapTimes(); + e->ResortRamp(); + } + break; + case DRAGTYPE_EVENT_ENDTIME: + case DRAGTYPE_EVENT_ENDTIME_RESCALE: + { + float newduration = e->GetDuration() + dt; + RescaleRamp( e, newduration ); + switch ( e->GetType() ) + { + default: + break; + case CChoreoEvent::GESTURE: + { + e->RescaleGestureTimes( e->GetStartTime(), e->GetEndTime() + dt, m_nDragType == DRAGTYPE_EVENT_ENDTIME ); + } + break; + case CChoreoEvent::FLEXANIMATION: + { + RescaleExpressionTimes( e, e->GetStartTime(), e->GetEndTime() + dt ); + } + break; + } + e->OffsetEndTime( dt ); + e->SnapTimes(); + e->ResortRamp(); + } + break; + case DRAGTYPE_RESCALELEFT: + case DRAGTYPE_RESCALERIGHT: + { + rescaleHelper.AddToTail( e ); + } + break; + case DRAGTYPE_EVENTTAG_MOVE: + { + // Get current x position + if ( m_nClickedTag != -1 ) + { + CEventRelativeTag *tag = e->GetRelativeTag( m_nClickedTag ); + if ( tag ) + { + float dx = mx - m_xStart; + // Determine left edcge + RECT bounds; + bounds = event->getBounds(); + if ( bounds.right - bounds.left > 0 ) + { + int left = bounds.left + (int)( tag->GetPercentage() * (float)( bounds.right - bounds.left ) + 0.5f ); + + left += dx; + + if ( left < bounds.left ) + { + left = bounds.left; + } + else if ( left >= bounds.right ) + { + left = bounds.right - 1; + } + + // Now convert back to a percentage + float frac = (float)( left - bounds.left ) / (float)( bounds.right - bounds.left ); + + tag->SetPercentage( frac ); + } + } + } + } + break; + case DRAGTYPE_EVENTABSTAG_MOVE: + { + // Get current x position + if ( m_pClickedAbsoluteTag != NULL ) + { + CEventAbsoluteTag *tag = m_pClickedAbsoluteTag; + if ( tag ) + { + float dx = mx - m_xStart; + // Determine left edcge + RECT bounds; + bounds = event->getBounds(); + if ( bounds.right - bounds.left > 0 ) + { + int left = bounds.left + (int)( tag->GetPercentage() * (float)( bounds.right - bounds.left ) + 0.5f ); + + left += dx; + + if ( left < bounds.left ) + { + left = bounds.left; + } + else if ( left >= bounds.right ) + { + left = bounds.right - 1; + } + + // Now convert back to a percentage + float frac = (float)( left - bounds.left ) / (float)( bounds.right - bounds.left ); + + tag->SetPercentage( frac ); + } + } + } + } + break; + } + } + + switch ( e->GetType() ) + { + default: + break; + case CChoreoEvent::SPEAK: + { + // Try and load wav to get length + CAudioSource *wave = sound->LoadSound( va( "sound/%s", FacePoser_TranslateSoundName( e ) ) ); + if ( wave ) + { + e->SetEndTime( e->GetStartTime() + wave->GetRunningLength() ); + delete wave; + } + } + break; + case CChoreoEvent::SEQUENCE: + { + CheckSequenceLength( e, false ); + } + break; + case CChoreoEvent::GESTURE: + { + CheckGestureLength( e, false ); + } + break; + } + } + } + } + + if ( rescaleHelper.Count() > 0 ) + { + int i; + // Determine start and end times for existing "selection" + float flStart = FLT_MAX; + float flEnd = FLT_MIN; + for ( i = 0; i < rescaleHelper.Count(); ++i ) + { + CChoreoEvent *e = rescaleHelper[ i ]; + float st = e->GetStartTime(); + float ed = e->GetEndTime(); + + if ( st < flStart ) + { + flStart = st; + } + if ( ed > flEnd ) + { + flEnd = ed; + } + } + + float flSelectionDuration = flEnd - flStart; + if ( flSelectionDuration > 0.0f ) + { + float flNewDuration = 0.0f; + if ( m_nDragType == DRAGTYPE_RESCALELEFT ) + { + flNewDuration = max( 0.1f, flSelectionDuration - dt ); + } + else + { + flNewDuration = max( 0.1f, flSelectionDuration + dt ); + } + float flScale = flNewDuration / flSelectionDuration; + + for ( i = 0; i < rescaleHelper.Count(); ++i ) + { + CChoreoEvent *e = rescaleHelper[ i ]; + float st = e->GetStartTime(); + float et = e->HasEndTime() ? e->GetEndTime() : e->GetStartTime(); + float flTimeFromStart = st - flStart; + float flTimeFromEnd = flEnd - et; + float flDuration = e->GetDuration(); + + float flNewStartTime = 0.0f; + float flNewDuration = 0.0f; + + if ( m_nDragType == DRAGTYPE_RESCALELEFT ) + { + float flNewEndTime = flEnd - flTimeFromEnd * flScale; + if ( !e->HasEndTime() || e->IsFixedLength() ) + { + e->OffsetTime( flNewEndTime - flDuration - st ); + continue; + } + flNewDuration = flDuration * flScale; + flNewStartTime = flNewEndTime - flNewDuration; + } + else + { + flNewStartTime = flTimeFromStart * flScale + flStart; + if ( !e->HasEndTime() || e->IsFixedLength() ) + { + e->OffsetTime( flNewStartTime - st ); + continue; + } + flNewDuration = flDuration * flScale; + } + + RescaleRamp( e, flNewDuration ); + switch ( e->GetType() ) + { + default: + break; + case CChoreoEvent::GESTURE: + { + e->RescaleGestureTimes( flNewStartTime, flNewStartTime + flNewDuration, m_nDragType == DRAGTYPE_EVENT_STARTTIME || m_nDragType == DRAGTYPE_EVENT_ENDTIME ); + } + break; + case CChoreoEvent::FLEXANIMATION: + { + RescaleExpressionTimes( e, flNewStartTime, flNewStartTime + flNewDuration ); + } + break; + } + + e->SetStartTime( flNewStartTime ); + Assert( e->HasEndTime() ); + e->SetEndTime( flNewStartTime + flNewDuration ); + } + } + } + + for ( int i = 0; i < m_SceneGlobalEvents.Count(); i++ ) + { + CChoreoGlobalEventWidget *gew = m_SceneGlobalEvents[ i ]; + if ( !gew || !gew->IsSelected() ) + continue; + + CChoreoEvent *e = gew->GetEvent(); + if ( !e ) + continue; + + e->OffsetTime( dt ); + e->SnapTimes(); + } + + m_nDragType = DRAGTYPE_NONE; + + if ( m_hPrevCursor ) + { + SetCursor( m_hPrevCursor ); + m_hPrevCursor = 0; + } + + CChoreoEvent *e = m_pClickedEvent ? m_pClickedEvent->GetEvent() : NULL; + + if ( e ) + { + // See if event is moving to a new owner + CChoreoChannelWidget *chOrig, *chNew; + + int dy = my - m_yStart; + bool shiftdown = ( event->modifiers & mxEvent::KeyShift ) ? true : false; + if ( !shiftdown ) + { + dy = 0; + } + + if ( abs( dy ) < m_pClickedEvent->GetItemHeight() ) + { + my = m_yStart; + } + + chNew = GetChannelUnderCursorPos( mx, my ); + + InvalidateLayout(); + + mx = m_xStart; + my = m_yStart; + + chOrig = m_pClickedChannel; + + if ( chOrig && chNew && chOrig != chNew ) + { + // Swap underlying objects + CChoreoChannel *pOrigChannel, *pNewChannel; + + pOrigChannel = chOrig->GetChannel(); + pNewChannel = chNew->GetChannel(); + + Assert( pOrigChannel && pNewChannel ); + + // Remove event and move to new object + DeleteSceneWidgets(); + + pOrigChannel->RemoveEvent( e ); + pNewChannel->AddEvent( e ); + + e->SetChannel( pNewChannel ); + e->SetActor( pNewChannel->GetActor() ); + + CreateSceneWidgets(); + } + else + { + if ( e && e->GetType() == CChoreoEvent::SPEAK ) + { + // Show phone wav in wav viewer + SetCurrentWaveFile( va( "sound/%s", FacePoser_TranslateSoundName( e ) ), e ); + } + } + } + + PushRedo( desc ); + InvalidateLayout(); + + if ( e ) + { + switch ( e->GetType() ) + { + default: + break; + case CChoreoEvent::FLEXANIMATION: + { + g_pExpressionTool->SetEvent( e ); + g_pFlexPanel->SetEvent( e ); + } + break; + case CChoreoEvent::GESTURE: + { + g_pGestureTool->SetEvent( e ); + } + break; + } + + if ( e->HasEndTime() ) + { + g_pRampTool->SetEvent( e ); + } + } + g_pExpressionTool->LayoutItems( true ); + g_pExpressionTool->redraw(); + g_pGestureTool->redraw(); + g_pRampTool->redraw(); + g_pSceneRampTool->redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +//----------------------------------------------------------------------------- +void CChoreoView::MouseFinishDrag( mxEvent *event, int mx, int my ) +{ + if ( !m_bDragging ) + return; + + ApplyBounds( mx, my ); + + switch ( m_nDragType ) + { + case DRAGTYPE_SCRUBBER: + { + DrawFocusRect(); + + m_FocusRects.Purge(); + + float t = GetTimeValueForMouse( mx ); + t += m_flScrubberTimeOffset; + m_flScrubberTimeOffset = 0.0f; + + ClampTimeToSelectionInterval( t ); + + SetScrubTime( t ); + SetScrubTargetTime( t ); + + m_bDragging = false; + m_nDragType = DRAGTYPE_NONE; + + redraw(); + } + break; + case DRAGTYPE_EVENT_MOVE: + case DRAGTYPE_EVENT_STARTTIME: + case DRAGTYPE_EVENT_STARTTIME_RESCALE: + case DRAGTYPE_EVENT_ENDTIME: + case DRAGTYPE_EVENT_ENDTIME_RESCALE: + case DRAGTYPE_EVENTTAG_MOVE: + case DRAGTYPE_EVENTABSTAG_MOVE: + case DRAGTYPE_RESCALELEFT: + case DRAGTYPE_RESCALERIGHT: + FinishDraggingEvent( event, mx, my ); + break; + case DRAGTYPE_SCENE_ENDTIME: + FinishDraggingSceneEndTime( event, mx, my ); + break; + default: + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +// Output : int +//----------------------------------------------------------------------------- +int CChoreoView::handleEvent( mxEvent *event ) +{ + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + int iret = 0; + + if ( HandleToolEvent( event ) ) + { + return iret; + } + + switch ( event->event ) + { + case mxEvent::MouseWheeled: + { + CChoreoScene *scene = GetScene(); + if ( scene ) + { + int tz = GetTimeZoom( GetToolName() ); + bool shiftdown = ( event->modifiers & mxEvent::KeyShift ) ? true : false; + int stepMultipiler = shiftdown ? 5 : 1; + + // Zoom time in / out + if ( event->height > 0 ) + { + tz = min( tz + TIME_ZOOM_STEP * stepMultipiler, MAX_TIME_ZOOM ); + } + else + { + tz = max( tz - TIME_ZOOM_STEP * stepMultipiler, TIME_ZOOM_STEP ); + } + + SetTimeZoom( GetToolName(), tz, true ); + + CUtlVector< CChoreoEvent * > selected; + RememberSelectedEvents( selected ); + + DeleteSceneWidgets(); + CreateSceneWidgets(); + + ReselectEvents( selected ); + + InvalidateLayout(); + Con_Printf( "Zoom factor %i %%\n", GetTimeZoom( GetToolName() ) ); + } + iret = 1; + } + break; + case mxEvent::Size: + { + // Force scroll bars to recompute + ForceScrollBarsToRecompute( false ); + + InvalidateLayout(); + PositionControls(); + iret = 1; + } + break; + case mxEvent::MouseDown: + { + if ( !m_bDragging ) + { + if ( event->buttons & mxEvent::MouseRightButton ) + { + if ( IsMouseOverTimeline( (short)event->x, (short)event->y ) ) + { + PlaceABPoint( (short)event->x ); + redraw(); + } + else if ( IsMouseOverScrubArea( event ) ) + { + float t = GetTimeValueForMouse( (short)event->x ); + + ClampTimeToSelectionInterval( t ); + + SetScrubTime( t ); + SetScrubTargetTime( t ); + + sound->Flush(); + + // Unpause the scene + m_bPaused = false; + + redraw(); + } + else + { + // Show right click menu + ShowContextMenu( (short)event->x, (short)event->y ); + } + } + else + { + if ( IsMouseOverTimeline( (short)event->x, (short)event->y ) ) + { + ClearABPoints(); + redraw(); + } + else + { + // Handle mouse dragging here + MouseStartDrag( event, (short)event->x, (short)event->y ); + } + } + } + iret = 1; + } + break; + case mxEvent::MouseDrag: + { + MouseContinueDrag( event, (short)event->x, (short)event->y ); + iret = 1; + } + break; + case mxEvent::MouseUp: + { + MouseFinishDrag( event, (short)event->x, (short)event->y ); + iret = 1; + } + break; + case mxEvent::MouseMove: + { + MouseMove( (short)event->x, (short)event->y ); + UpdateStatusArea( (short)event->x, (short)event->y ); + iret = 1; + } + break; + case mxEvent::KeyDown: + { + iret = 1; + + switch ( event->key ) + { + default: + iret = 0; + break; + case 'E': + if ( GetAsyncKeyState( VK_CONTROL ) ) + { + OnPlaceNextSpeakEvent(); + } + break; + case VK_ESCAPE: + DeselectAll(); + break; + case 'C': + CopyEvents(); + iret = 1; + break; + case 'V': + PasteEvents(); + redraw(); + break; + case VK_DELETE: + { + if ( IsActiveTool() ) + { + DeleteSelectedEvents(); + } + } + break; + case VK_RETURN: + { + CUtlVector< CChoreoEvent * > events; + GetSelectedEvents( events ); + if ( events.Count() == 1 ) + { + if ( GetAsyncKeyState( VK_MENU ) ) + { + EditEvent( events[ 0 ] ); + redraw(); + iret = 1; + } + } + } + break; + case 'Z': // Undo/Redo + { + if ( GetAsyncKeyState( VK_CONTROL ) ) + { + if ( GetAsyncKeyState( VK_SHIFT ) ) + { + if ( CanRedo() ) + { + Con_Printf( "Redo %s\n", GetRedoDescription() ); + Redo(); + iret = 1; + } + } + else + { + if ( CanUndo() ) + { + Con_Printf( "Undo %s\n", GetUndoDescription() ); + Undo(); + iret = 1; + } + } + } + } + break; + + case VK_SPACE: + { + if ( IsPlayingScene() ) + { + StopScene(); + } + } + break; + case 188: // VK_OEM_COMMA: + { + SetScrubTargetTime( 0.0f ); + } + break; + case 190: // VK_OEM_PERIOD: + { + CChoreoScene *scene = GetScene(); + if ( scene ) + { + SetScrubTargetTime( scene->FindStopTime() ); + } + } + break; + case VK_LEFT: + { + CChoreoScene *scene = GetScene(); + if ( scene && scene->GetSceneFPS() > 0 ) + { + float curscrub = m_flScrub; + curscrub -= ( 1.0f / (float)scene->GetSceneFPS() ); + curscrub = max( curscrub, 0.0f ); + SetScrubTargetTime( curscrub ); + } + } + break; + case VK_RIGHT: + { + CChoreoScene *scene = GetScene(); + if ( scene && scene->GetSceneFPS() > 0 ) + { + float curscrub = m_flScrub; + curscrub += ( 1.0f / (float)scene->GetSceneFPS() ); + curscrub = min( curscrub, scene->FindStopTime() ); + SetScrubTargetTime( curscrub ); + } + } + break; + case VK_HOME: + { + MoveTimeSliderToPos( 0 ); + } + break; + case VK_END: + { + float maxtime = m_pScene->FindStopTime() - 1.0f; + int pixels = (int)( maxtime * GetPixelsPerSecond() ); + MoveTimeSliderToPos( pixels - 1 ); + } + break; + case VK_PRIOR: // PgUp + { + int window = w2() - GetLabelWidth(); + m_flLeftOffset = max( m_flLeftOffset - (float)window, 0.0f ); + MoveTimeSliderToPos( (int)m_flLeftOffset ); + } + break; + case VK_NEXT: // PgDown + { + int window = w2() - GetLabelWidth(); + int pixels = ComputeHPixelsNeeded(); + m_flLeftOffset = min( m_flLeftOffset + (float)window, (float)pixels ); + MoveTimeSliderToPos( (int)m_flLeftOffset ); + } + break; + } + } + break; + case mxEvent::Action: + { + iret = 1; + switch ( event->action ) + { + default: + { + iret = 0; + int lang_index = event->action - IDC_CV_CC_LANGUAGESTART; + if ( lang_index >= 0 && lang_index < CC_NUM_LANGUAGES ) + { + iret = 1; + SetCloseCaptionLanguageId( lang_index ); + } + } + break; + case IDC_CV_TOGGLECLOSECAPTIONS: + { + OnToggleCloseCaptionsForEvent(); + } + break; + case IDC_CV_CHANGECLOSECAPTIONTOKEN: + { + if ( m_pClickedChannel ) + { + CChoreoEvent *e = m_pClickedChannel->GetCaptionClickedEvent(); + if ( e && e->GetNumSlaves() >= 1 ) + { + OnChangeCloseCaptionToken( e ); + } + } + } + break; + case IDC_CV_REMOVESPEAKEVENTFROMGROUP: + { + OnRemoveSpeakEventFromGroup(); + } + break; + case IDC_CV_COMBINESPEAKEVENTS: + { + OnCombineSpeakEvents(); + } + break; + case IDC_CV_CC_SHOW: + { + OnToggleCloseCaptionTags(); + } + break; + case IDC_CV_TOGGLERAMPONLY: + { + m_bRampOnly = !m_bRampOnly; + redraw(); + } + break; + case IDC_CV_PROCESSSEQUENCES: + { + m_bProcessSequences = !m_bProcessSequences; + } + break; + case IDC_CV_CHECKSEQLENGTHS: + { + OnCheckSequenceLengths(); + } + break; + case IDC_CV_CHANGESCALE: + { + OnChangeScale(); + } + break; + case IDC_CHOREO_PLAYBACKRATE: + { + m_flPlaybackRate = m_pPlaybackRate->getValue(); + redraw(); + } + break; + case IDC_COPYEVENTS: + CopyEvents(); + break; + case IDC_PASTEEVENTS: + PasteEvents(); + redraw(); + break; + case IDC_IMPORTEVENTS: + ImportEvents(); + redraw(); + break; + case IDC_EXPORTEVENTS: + ExportEvents(); + redraw(); + break; + case IDC_EXPORT_VCD: + ExportVCD(); + redraw(); + break; + case IDC_IMPORT_VCD: + ImportVCD(); + redraw(); + break; + case IDC_EXPRESSIONTOOL: + OnExpressionTool(); + break; + case IDC_GESTURETOOL: + OnGestureTool(); + break; + case IDC_ASSOCIATEBSP: + AssociateBSP(); + break; + case IDC_ASSOCIATEMODEL: + AssociateModel(); + break; + case IDC_CVUNDO: + Undo(); + break; + case IDC_CVREDO: + Redo(); + break; + case IDC_SELECTALL: + SelectAll(); + break; + case IDC_DESELECTALL: + DeselectAll(); + break; + case IDC_PLAYSCENE: + Con_Printf( "Commencing playback\n" ); + PlayScene( true ); + break; + case IDC_PAUSESCENE: + Con_Printf( "Pausing playback\n" ); + PauseScene(); + break; + case IDC_STOPSCENE: + Con_Printf( "Canceling playback\n" ); + StopScene(); + break; + case IDC_CHOREOVSCROLL: + { + int offset = 0; + bool processed = true; + + switch ( event->modifiers ) + { + case SB_THUMBTRACK: + offset = event->height; + break; + case SB_PAGEUP: + offset = m_pVertScrollBar->getValue(); + offset -= 20; + offset = max( offset, m_pVertScrollBar->getMinValue() ); + break; + case SB_PAGEDOWN: + offset = m_pVertScrollBar->getValue(); + offset += 20; + offset = min( offset, m_pVertScrollBar->getMaxValue() ); + break; + case SB_LINEDOWN: + offset = m_pVertScrollBar->getValue(); + offset += 10; + offset = min( offset, m_pVertScrollBar->getMaxValue() ); + break; + case SB_LINEUP: + offset = m_pVertScrollBar->getValue(); + offset -= 10; + offset = max( offset, m_pVertScrollBar->getMinValue() ); + break; + default: + processed = false; + break; + } + + if ( processed ) + { + m_pVertScrollBar->setValue( offset ); + InvalidateRect( (HWND)m_pVertScrollBar->getHandle(), NULL, TRUE ); + m_nTopOffset = offset; + InvalidateLayout(); + } + } + break; + case IDC_CHOREOHSCROLL: + { + int offset = 0; + bool processed = true; + + switch ( event->modifiers ) + { + case SB_THUMBTRACK: + offset = event->height; + break; + case SB_PAGEUP: + offset = m_pHorzScrollBar->getValue(); + offset -= 20; + offset = max( offset, m_pHorzScrollBar->getMinValue() ); + break; + case SB_PAGEDOWN: + offset = m_pHorzScrollBar->getValue(); + offset += 20; + offset = min( offset, m_pHorzScrollBar->getMaxValue() ); + break; + case SB_LINEUP: + offset = m_pHorzScrollBar->getValue(); + offset -= 10; + offset = max( offset, m_pHorzScrollBar->getMinValue() ); + break; + case SB_LINEDOWN: + offset = m_pHorzScrollBar->getValue(); + offset += 10; + offset = min( offset, m_pHorzScrollBar->getMaxValue() ); + break; + default: + processed = false; + break; + } + + if ( processed ) + { + MoveTimeSliderToPos( offset ); + } + } + break; + case IDC_ADDACTOR: + { + NewActor(); + } + break; + case IDC_EDITACTOR: + { + CChoreoActorWidget *actor = m_pClickedActor; + if ( actor ) + { + EditActor( actor->GetActor() ); + } + } + break; + case IDC_DELETEACTOR: + { + CChoreoActorWidget *actor = m_pClickedActor; + if ( actor ) + { + DeleteActor( actor->GetActor() ); + } + } + break; + case IDC_MOVEACTORUP: + { + CChoreoActorWidget *actor = m_pClickedActor; + if ( actor ) + { + MoveActorUp( actor->GetActor() ); + } + } + break; + case IDC_MOVEACTORDOWN: + { + CChoreoActorWidget *actor = m_pClickedActor; + if ( actor ) + { + MoveActorDown( actor->GetActor() ); + } + } + break; + case IDC_CHANNELOPEN: + { + CActorBitmapButton *btn = static_cast< CActorBitmapButton * >( event->widget ); + if ( btn ) + { + CChoreoActorWidget *a = btn->GetActor(); + if ( a ) + { + a->ShowChannels( true ); + } + } + } + break; + case IDC_CHANNELCLOSE: + { + CActorBitmapButton *btn = static_cast< CActorBitmapButton * >( event->widget ); + if ( btn ) + { + CChoreoActorWidget *a = btn->GetActor(); + if ( a ) + { + a->ShowChannels( false ); + } + } + } + break; + case IDC_ADDEVENT_INTERRUPT: + { + AddEvent( CChoreoEvent::INTERRUPT ); + } + break; + case IDC_ADDEVENT_PERMITRESPONSES: + { + AddEvent( CChoreoEvent::PERMIT_RESPONSES ); + } + break; + case IDC_ADDEVENT_EXPRESSION: + { + AddEvent( CChoreoEvent::EXPRESSION ); + } + break; + case IDC_ADDEVENT_FLEXANIMATION: + { + AddEvent( CChoreoEvent::FLEXANIMATION ); + } + break; + case IDC_ADDEVENT_GESTURE: + { + AddEvent( CChoreoEvent::GESTURE ); + } + break; + case IDC_ADDEVENT_NULLGESTURE: + { + AddEvent( CChoreoEvent::GESTURE, 1 ); + } + break; + case IDC_ADDEVENT_LOOKAT: + { + AddEvent( CChoreoEvent::LOOKAT ); + } + break; + case IDC_ADDEVENT_MOVETO: + { + AddEvent( CChoreoEvent::MOVETO ); + } + break; + case IDC_ADDEVENT_FACE: + { + AddEvent( CChoreoEvent::FACE ); + } + break; + case IDC_ADDEVENT_SPEAK: + { + AddEvent( CChoreoEvent::SPEAK ); + } + break; + case IDC_ADDEVENT_FIRETRIGGER: + { + AddEvent( CChoreoEvent::FIRETRIGGER ); + } + break; + case IDC_ADDEVENT_GENERIC: + { + AddEvent( CChoreoEvent::GENERIC ); + } + break; + case IDC_ADDEVENT_SUBSCENE: + { + AddEvent( CChoreoEvent::SUBSCENE ); + } + break; + case IDC_ADDEVENT_SEQUENCE: + { + AddEvent( CChoreoEvent::SEQUENCE ); + } + break; + case IDC_EDITEVENT: + { + CChoreoEventWidget *event = m_pClickedEvent; + if ( event ) + { + EditEvent( event->GetEvent() ); + redraw(); + } + } + break; + case IDC_DELETEEVENT: + { + DeleteSelectedEvents(); + } + break; + case IDC_CV_ENABLEEVENTS: + { + EnableSelectedEvents( true ); + } + break; + case IDC_CV_DISABLEEVENTS: + { + EnableSelectedEvents( false ); + } + break; + case IDC_MOVETOBACK: + { + CChoreoEventWidget *event = m_pClickedEvent; + if ( event ) + { + MoveEventToBack( event->GetEvent() ); + } + } + break; + case IDC_DELETERELATIVETAG: + { + CChoreoEventWidget *event = m_pClickedEvent; + if ( event && m_nClickedTag >= 0 ) + { + DeleteEventRelativeTag( event->GetEvent(), m_nClickedTag ); + } + } + break; + case IDC_ADDTIMINGTAG: + { + AddEventRelativeTag(); + } + break; + case IDC_ADDEVENT_PAUSE: + { + AddGlobalEvent( CChoreoEvent::SECTION ); + } + break; + case IDC_ADDEVENT_LOOP: + { + AddGlobalEvent( CChoreoEvent::LOOP ); + } + break; + case IDC_ADDEVENT_STOPPOINT: + { + AddGlobalEvent( CChoreoEvent::STOPPOINT ); + } + break; + case IDC_EDITGLOBALEVENT: + { + CChoreoGlobalEventWidget *event = m_pClickedGlobalEvent; + if ( event ) + { + EditGlobalEvent( event->GetEvent() ); + redraw(); + } + } + break; + case IDC_DELETEGLOBALEVENT: + { + CChoreoGlobalEventWidget *event = m_pClickedGlobalEvent; + if ( event ) + { + DeleteGlobalEvent( event->GetEvent() ); + } + } + break; + case IDC_ADDCHANNEL: + { + NewChannel(); + } + break; + case IDC_EDITCHANNEL: + { + CChoreoChannelWidget *channel = m_pClickedChannel; + if ( channel ) + { + EditChannel( channel->GetChannel() ); + } + } + break; + case IDC_DELETECHANNEL: + { + CChoreoChannelWidget *channel = m_pClickedChannel; + if ( channel ) + { + DeleteChannel( channel->GetChannel() ); + } + } + break; + case IDC_MOVECHANNELUP: + { + CChoreoChannelWidget *channel = m_pClickedChannel; + if ( channel ) + { + MoveChannelUp( channel->GetChannel() ); + } + } + break; + case IDC_MOVECHANNELDOWN: + { + CChoreoChannelWidget *channel = m_pClickedChannel; + if ( channel ) + { + MoveChannelDown( channel->GetChannel() ); + } + } + break; + case IDC_CV_ALLEVENTS_CHANNEL: + { + CChoreoChannelWidget *channel = m_pClickedChannel; + if ( channel ) + { + SelectAllEventsInChannel( channel ); + } + } + break; + case IDC_CV_ALLEVENTS_ACTOR: + { + CChoreoActorWidget *actor = m_pClickedActor; + if ( actor ) + { + SelectAllEventsInActor( actor ); + } + } + break; + case IDC_SELECTEVENTS_ALL_BEFORE: + { + SelectionParams_t params; + Q_memset( ¶ms, 0, sizeof( params ) ); + params.forward = false; + params.time = GetTimeValueForMouse( m_nClickedX ); + params.type = SelectionParams_t::SP_ALL; + + SelectEvents( params ); + } + break; + case IDC_SELECTEVENTS_ALL_AFTER: + { + SelectionParams_t params; + Q_memset( ¶ms, 0, sizeof( params ) ); + params.forward = true; + params.time = GetTimeValueForMouse( m_nClickedX ); + params.type = SelectionParams_t::SP_ALL; + + SelectEvents( params ); + } + break; + case IDC_SELECTEVENTS_ACTIVE_BEFORE: + { + SelectionParams_t params; + Q_memset( ¶ms, 0, sizeof( params ) ); + params.forward = false; + params.time = GetTimeValueForMouse( m_nClickedX ); + params.type = SelectionParams_t::SP_ACTIVE; + + SelectEvents( params ); + } + break; + case IDC_SELECTEVENTS_ACTIVE_AFTER: + { + SelectionParams_t params; + Q_memset( ¶ms, 0, sizeof( params ) ); + params.forward = true; + params.time = GetTimeValueForMouse( m_nClickedX ); + params.type = SelectionParams_t::SP_ACTIVE; + + SelectEvents( params ); + } + break; + case IDC_SELECTEVENTS_CHANNEL_BEFORE: + { + SelectionParams_t params; + Q_memset( ¶ms, 0, sizeof( params ) ); + params.forward = false; + params.time = GetTimeValueForMouse( m_nClickedX ); + params.type = SelectionParams_t::SP_CHANNEL; + + SelectEvents( params ); + } + break; + case IDC_SELECTEVENTS_CHANNEL_AFTER: + { + SelectionParams_t params; + Q_memset( ¶ms, 0, sizeof( params ) ); + params.forward = true; + params.time = GetTimeValueForMouse( m_nClickedX ); + params.type = SelectionParams_t::SP_CHANNEL; + + SelectEvents( params ); + } + break; + case IDC_INSERT_TIME: + { + OnInsertTime(); + } + break; + case IDC_DELETE_TIME: + { + OnDeleteTime(); + } + break; + case IDC_CV_ALIGN_LEFT: + { + OnAlign( true ); + } + break; + case IDC_CV_ALIGN_RIGHT: + { + OnAlign( false ); + } + break; + case IDC_CV_SAMESIZE_SMALLEST: + { + OnMakeSameSize( true ); + } + break; + case IDC_CV_SAMESIZE_LARGEST: + { + OnMakeSameSize( false ); + } + break; + } + + if ( iret == 1 ) + { + SetActiveTool( this ); + } + } + break; + } + return iret; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::PlayScene( bool forward ) +{ + m_bForward = forward; + if ( !m_pScene ) + return; + + sound->Flush(); + + // Make sure phonemes are loaded + FacePoser_EnsurePhonemesLoaded(); + + // Unpause + if ( m_bSimulating && m_bPaused ) + { + m_bPaused = false; + return; + } + + m_bSimulating = true; + m_bPaused = false; + +// float soundlatency = max( sound->GetAmountofTimeAhead(), 0.0f ); +// soundlatency = min( 0.5f, soundlatency ); + + float soundlatency = 0.0f; + + float sceneendtime = m_pScene->FindStopTime(); + + m_pScene->SetSoundFileStartupLatency( soundlatency ); + + if ( m_rgABPoints[ 0 ].active || + m_rgABPoints[ 1 ].active ) + { + if ( m_rgABPoints[ 0 ].active && + m_rgABPoints[ 1 ].active ) + { + float st = m_rgABPoints[ 0 ].time; + float ed = m_rgABPoints[ 1 ].time; + + m_pScene->ResetSimulation( m_bForward, st, ed ); + + SetScrubTime( m_bForward ? st : ed ); + SetScrubTargetTime( m_bForward ? ed : st ); + } + else + { + float startonly = m_rgABPoints[ 0 ].active ? m_rgABPoints[ 0 ].time : m_rgABPoints[ 1 ].time; + + m_pScene->ResetSimulation( m_bForward, startonly ); + + SetScrubTime( m_bForward ? startonly : sceneendtime ); + SetScrubTargetTime( m_bForward ? sceneendtime : startonly ); + } + } + else + { + // NO start end/loop + m_pScene->ResetSimulation( m_bForward ); + + SetScrubTime( m_bForward ? 0 : sceneendtime ); + SetScrubTargetTime( m_bForward ? sceneendtime : 0 ); + } + + if ( g_viewerSettings.speedScale == 0.0f ) + { + m_flLastSpeedScale = g_viewerSettings.speedScale; + m_bResetSpeedScale = true; + + g_viewerSettings.speedScale = 1.0f; + + Con_Printf( "Resetting speed scale to 1.0\n" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : x - +//----------------------------------------------------------------------------- +void CChoreoView::MoveTimeSliderToPos( int x ) +{ + m_flLeftOffset = (float)x; + m_pHorzScrollBar->setValue( (int)m_flLeftOffset ); + InvalidateRect( (HWND)m_pHorzScrollBar->getHandle(), NULL, TRUE ); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::PauseScene( void ) +{ + if ( !m_bSimulating ) + return; + + m_bPaused = true; + sound->StopAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: Apply expression to actor's face +// Input : *event - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +void CChoreoView::ProcessExpression( CChoreoScene *scene, CChoreoEvent *event ) +{ + Assert( event->GetType() == CChoreoEvent::EXPRESSION ); + + StudioModel *model = FindAssociatedModel( scene, event->GetActor() ); + if ( !model ) + return; + + CStudioHdr *hdr = model->GetStudioHdr(); + if ( !hdr ) + { + return; + } + + CExpClass *p = expressions->FindClass( event->GetParameters(), true ); + if ( !p ) + { + return; + } + + CExpression *exp = p->FindExpression( event->GetParameters2() ); + if ( !exp ) + { + return; + } + + CChoreoActor *a = event->GetActor(); + if ( !a ) + return; + + CChoreoActorWidget *actor = NULL; + + int i; + for ( i = 0; i < m_SceneActors.Size(); i++ ) + { + actor = m_SceneActors[ i ]; + if ( !actor ) + continue; + + if ( actor->GetActor() == a ) + break; + } + + if ( !actor || i >= m_SceneActors.Size() ) + return; + + float *settings = exp->GetSettings(); + Assert( settings ); + float *weights = exp->GetWeights(); + Assert( weights ); + float *current = actor->GetSettings(); + Assert( current ); + + float flIntensity = event->GetIntensity( scene->GetTime() ); + + // blend in target values for correct actor + for ( LocalFlexController_t i = (LocalFlexController_t)0; i < hdr->numflexcontrollers(); i++ ) + { + mstudioflexcontroller_t *pFlex = hdr->pFlexcontroller( i ); + int j = pFlex->localToGlobal; + if ( j < 0 ) + continue; + float s = clamp( weights[j] * flIntensity, 0.0, 1.0 ); + current[ j ] = current[j] * (1.0f - s) + settings[ j ] * s; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *hdr - +// *event - +//----------------------------------------------------------------------------- +void SetupFlexControllerTracks( CStudioHdr *hdr, CChoreoEvent *event ) +{ + Assert( hdr ); + Assert( event ); + + if ( !hdr ) + return; + + if ( !event ) + return; + + // Already done + if ( event->GetTrackLookupSet() ) + return; + + /* + // FIXME: Brian hooked this stuff up for some took work, but at this point the .mdl files don't look like they've been updated to include the remapping data yet... + int c = hdr->numflexcontrollerremaps(); + for ( i = 0; i < c; ++i ) + { + mstudioflexcontrollerremap_t *remap = hdr->pFlexcontrollerRemap( i ); + Msg( "remap %s\n", remap->pszName() ); + Msg( " type %d\n", remap->remaptype ); + Msg( " num remaps %d (stereo %s)\n", remap->numremaps, remap->stereo ? "true" : "false" ); + for ( int j = 0 ; j < remap->numremaps; ++j ) + { + int index = remap->pRemapControlIndex( j ); + Msg( " %d: maps to %d (%s) with %s\n", j, index, hdr->pFlexcontroller( index )->pszName(), remap->pRemapControl( j ) ); + } + } + */ + + // Unlink stuff in case it doesn't exist + int nTrackCount = event->GetNumFlexAnimationTracks(); + for ( int i = 0; i < nTrackCount; ++i ) + { + CFlexAnimationTrack *pTrack = event->GetFlexAnimationTrack( i ); + pTrack->SetFlexControllerIndex( LocalFlexController_t(-1), -1, 0 ); + pTrack->SetFlexControllerIndex( LocalFlexController_t(-1), -1, 1 ); + } + + for ( LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); ++i ) + { + int j = hdr->pFlexcontroller( i )->localToGlobal; + + char const *name = hdr->pFlexcontroller( i )->pszName(); + if ( !name ) + continue; + + bool combo = false; + // Look up or create all necessary tracks + if ( strncmp( "right_", name, 6 ) == 0 ) + { + combo = true; + name = &name[6]; + } + + CFlexAnimationTrack *track = event->FindTrack( name ); + if ( !track ) + { + track = event->AddTrack( name ); + Assert( track ); + } + + track->SetFlexControllerIndex( i, j, 0 ); + if ( combo ) + { + track->SetFlexControllerIndex( LocalFlexController_t(i + 1), hdr->pFlexcontroller( LocalFlexController_t(i + 1) )->localToGlobal, 1 ); + track->SetComboType( true ); + } + + float orig_min = track->GetMin( ); + float orig_max = track->GetMax( ); + + // set range + if (hdr->pFlexcontroller( i )->min == 0.0f || hdr->pFlexcontroller( i )->max == 1.0f) + { + track->SetInverted( false ); + track->SetMin( hdr->pFlexcontroller( i )->min ); + track->SetMax( hdr->pFlexcontroller( i )->max ); + } + else + { + // invert ranges for wide ranged, makes sense considering flexcontroller names... + track->SetInverted( true ); + track->SetMin( hdr->pFlexcontroller( i )->max ); + track->SetMax( hdr->pFlexcontroller( i )->min ); + } + + // resample track based on this models dynamic range + if (track->GetNumSamples( 0 ) > 0) + { + float range = track->GetMax( ) - track->GetMin( ); + + for (int i = 0; i < track->GetNumSamples( 0 ); i++) + { + CExpressionSample *sample = track->GetSample( i, 0 ); + float rangedValue = orig_min * (1 - sample->value) + orig_max * sample->value; + sample->value = clamp( (rangedValue - track->GetMin( )) / range, 0.0, 1.0 ); + } + } + + // skip next flex since we've already assigned it + if ( combo ) + { + i++; + } + } + + event->SetTrackLookupSet( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: Apply flexanimation to actor's face +// Input : *event - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +void CChoreoView::ProcessFlexAnimation( CChoreoScene *scene, CChoreoEvent *event ) +{ + Assert( event->GetType() == CChoreoEvent::FLEXANIMATION ); + + StudioModel *model = FindAssociatedModel( scene, event->GetActor() ); + if ( !model ) + return; + + CStudioHdr *hdr = model->GetStudioHdr(); + if ( !hdr ) + { + return; + } + + CChoreoActor *a = event->GetActor(); + + CChoreoActorWidget *actor = NULL; + + int i; + for ( i = 0; i < m_SceneActors.Size(); i++ ) + { + actor = m_SceneActors[ i ]; + if ( !actor ) + continue; + + if ( !stricmp( actor->GetActor()->GetName(), a->GetName() ) ) + break; + } + + if ( !actor || i >= m_SceneActors.Size() ) + return; + + float *current = actor->GetSettings(); + Assert( current ); + + if ( !event->GetTrackLookupSet() ) + { + SetupFlexControllerTracks( hdr, event ); + } + + float weight = event->GetIntensity( scene->GetTime() ); + + CChoreoEventWidget *eventwidget = FindWidgetForEvent( event ); + bool bUpdateSliders = (eventwidget && eventwidget->IsSelected() && model == models->GetActiveStudioModel() ); + + // Iterate animation tracks + for ( i = 0; i < event->GetNumFlexAnimationTracks(); i++ ) + { + CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i ); + if ( !track ) + continue; + + // Disabled + if ( !track->IsTrackActive() ) + { + if ( bUpdateSliders ) + { + for ( int side = 0; side < 1 + track->IsComboType(); side++ ) + { + int controller = track->GetFlexControllerIndex( side ); + if ( controller != -1 && !g_pFlexPanel->IsEdited( controller )) + { + g_pFlexPanel->SetSlider( controller, 0.0 ); + g_pFlexPanel->SetInfluence( controller, 0.0f ); + } + } + } + continue; + } + + // Map track flex controller to global name + if ( track->IsComboType() ) + { + for ( int side = 0; side < 2; side++ ) + { + int controller = track->GetFlexControllerIndex( side ); + if ( controller != -1 ) + { + // Get spline intensity for controller + float flIntensity = track->GetIntensity( scene->GetTime(), side ); + + if (bUpdateSliders && !g_pFlexPanel->IsEdited( controller ) ) + { + g_pFlexPanel->SetSlider( controller, flIntensity ); + g_pFlexPanel->SetInfluence( controller, 1.0f ); + } + + flIntensity = current[ controller ] * (1 - weight) + flIntensity * weight; + current[ controller ] = flIntensity; + } + } + } + else + { + int controller = track->GetFlexControllerIndex( 0 ); + if ( controller != -1 ) + { + // Get spline intensity for controller + float flIntensity = track->GetIntensity( scene->GetTime(), 0 ); + + if (bUpdateSliders && !g_pFlexPanel->IsEdited( controller ) ) + { + g_pFlexPanel->SetSlider( controller, flIntensity ); + g_pFlexPanel->SetInfluence( controller, 1.0f ); + } + + flIntensity = current[ controller ] * (1 - weight) + flIntensity * weight; + current[ controller ] = flIntensity; + } + } + } +} + + +#include "mapentities.h" + +//----------------------------------------------------------------------------- +// Purpose: Apply lookat target +// Input : *event - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +void CChoreoView::ProcessLookat( CChoreoScene *scene, CChoreoEvent *event ) +{ + Assert( event->GetType() == CChoreoEvent::LOOKAT ); + + if ( !event->GetActor() ) + return; + + CChoreoActor *a = event->GetActor(); + + Assert( a ); + + StudioModel *model = FindAssociatedModel( scene, a ); + if ( !model ) + { + return; + } + + float flIntensity = event->GetIntensity( scene->GetTime() ); + + // clamp in-ramp to 0.3 seconds + float flDuration = scene->GetTime() - event->GetStartTime(); + float flMaxIntensity = flDuration < 0.3f ? SimpleSpline( flDuration / 0.3f ) : 1.0f; + flDuration = event->GetEndTime() - scene->GetTime(); + flMaxIntensity = min( flMaxIntensity, flDuration < 0.3f ? SimpleSpline( flDuration / 0.3f ) : 1.0f ); + flIntensity = clamp( flIntensity, 0.0f, flMaxIntensity ); + + if (!stricmp( event->GetParameters(), a->GetName() ) || !stricmp( event->GetParameters(), "!self" )) + { + model->AddLookTargetSelf( flIntensity ); + } + else if ( !stricmp( event->GetParameters(), "player" ) || + !stricmp( event->GetParameters(), "!player" ) ) + { + Vector vecTarget = model->m_origin; + vecTarget.z = 0; + + model->AddLookTarget( vecTarget, flIntensity ); + } + else + { + mapentities->CheckUpdateMap( scene->GetMapname() ); + + Vector orgActor; + Vector orgTarget; + QAngle anglesActor; + QAngle anglesDummy; + + if ( event->GetPitch() != 0 || + event->GetYaw() != 0 ) + { + QAngle angles( -(float)event->GetPitch(), + (float)event->GetYaw(), + 0 ); + + matrix3x4_t matrix; + + AngleMatrix( model->m_angles, matrix ); + + Vector vecForward; + AngleVectors( angles, &vecForward ); + + Vector eyeTarget; + VectorRotate( vecForward, matrix, eyeTarget ); + VectorScale( eyeTarget, 75, eyeTarget ); + + model->AddLookTarget( eyeTarget, flIntensity ); + } + else + { + if ( mapentities->LookupOrigin( a->GetName(), orgActor, anglesActor ) ) + { + if ( mapentities->LookupOrigin( event->GetParameters(), orgTarget, anglesDummy ) ) + { + Vector delta = orgTarget - orgActor; + + matrix3x4_t matrix; + Vector lookTarget; + + // Rotate around actor's placed forward direction since we look straight down x in faceposer/hlmv + AngleMatrix( anglesActor, matrix ); + VectorIRotate( delta, matrix, lookTarget ); + + model->AddLookTarget( lookTarget, flIntensity ); + return; + } + } + // hack up something based on the name. + { + const char *cp = event->GetParameters(); + float value = 0.0; + while (*cp) + { + value += *cp++; + } + value = cos( value ); + value = acos( value ); + QAngle angles( 0.0, value * 45 / M_PI, 0.0 ); + + matrix3x4_t matrix; + AngleMatrix( model->m_angles, matrix ); + + Vector vecForward; + AngleVectors( angles, &vecForward ); + + Vector eyeTarget; + VectorRotate( vecForward, matrix, eyeTarget ); + VectorScale( eyeTarget, 75, eyeTarget ); + + model->AddLookTarget( eyeTarget, flIntensity ); + } + + } + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: Returns a target for Faceing +// Input : *event - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoView::GetTarget( CChoreoScene *scene, CChoreoEvent *event, Vector &vecTarget, QAngle &vecAngle ) +{ + if ( !event->GetActor() ) + return false; + + CChoreoActor *a = event->GetActor(); + + Assert( a ); + + StudioModel *model = FindAssociatedModel( scene, a ); + if ( !model ) + { + return false; + } + + if (!stricmp( event->GetParameters(), a->GetName() )) + { + vecTarget = vec3_origin; + return true; + } + else if ( !stricmp( event->GetParameters(), "player" ) || + !stricmp( event->GetParameters(), "!player" ) ) + { + vecTarget = model->m_origin; + vecTarget.z = 0; + vecAngle = model->m_angles; + + return true; + } + else + { + mapentities->CheckUpdateMap( scene->GetMapname() ); + + Vector orgActor; + Vector orgTarget; + QAngle anglesActor; + QAngle anglesDummy; + + if ( event->GetPitch() != 0 || + event->GetYaw() != 0 ) + { + QAngle angles( -(float)event->GetPitch(), + (float)event->GetYaw(), + 0 ); + + matrix3x4_t matrix; + + AngleMatrix( model->m_angles, matrix ); + + QAngle angles2 = angles; + angles2.x *= 0.6f; + angles2.y *= 0.8f; + + Vector vecForward, vecForward2; + AngleVectors( angles, &vecForward ); + AngleVectors( angles2, &vecForward2 ); + + VectorNormalize( vecForward ); + VectorNormalize( vecForward2 ); + + Vector eyeTarget, headTarget; + + VectorRotate( vecForward, matrix, eyeTarget ); + VectorRotate( vecForward2, matrix, headTarget ); + + VectorScale( eyeTarget, 150, eyeTarget ); + + VectorScale( headTarget, 150, vecTarget ); + return true; + + } + else + { + if ( mapentities->LookupOrigin( a->GetName(), orgActor, anglesActor ) ) + { + if ( mapentities->LookupOrigin( event->GetParameters(), orgTarget, anglesDummy ) ) + { + Vector delta = orgTarget - orgActor; + + matrix3x4_t matrix; + Vector lookTarget; + + // Rotate around actor's placed forward direction since we look straight down x in faceposer/hlmv + AngleMatrix( anglesActor, matrix ); + VectorIRotate( delta, matrix, vecTarget ); + + return true; + } + } + } + } + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Apply lookat target +// Input : *event - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +void CChoreoView::ProcessFace( CChoreoScene *scene, CChoreoEvent *event ) +{ + Assert( event->GetType() == CChoreoEvent::FACE ); + + if ( !event->GetActor() ) + return; + + CChoreoActor *a = event->GetActor(); + + Assert( a ); + + StudioModel *model = FindAssociatedModel( scene, a ); + if ( !model ) + { + return; + } + + Vector vecTarget; + QAngle vecAngle; + + if (!GetTarget( scene, event, vecTarget, vecAngle )) + { + return; + } + + /* + // FIXME: this is broke + float goalYaw = -(vecAngle.y > 180 ? 360 - vecAngle.y : vecAngle.y ); + + float intensity = event->GetIntensity( scene->GetTime() ); + + float diff = goalYaw * intensity; + float dir = 1.0; + + if (diff < 0) + { + diff = -diff; + dir = -1; + } + + float spineintensity = 0 * max( 0.0, (intensity - 0.5) / 0.5 ); + float goalSpineYaw = min( diff * (1.0 - spineintensity), 30 ); + //float idealYaw = info->m_flInitialYaw + (diff - m_goalBodyYaw * dir - m_goalSpineYaw * dir) * dir; + // float idealYaw = UTIL_AngleMod( info->m_flInitialYaw + diff * intensity ); + + // FIXME: this is broke + // model->SetSpineYaw( goalSpineYaw * dir); + // model->SetBodyYaw( goalBodyYaw * dir ); + + // Msg("yaw %.1f : %.1f (%.1f)\n", info->m_flInitialYaw, idealYaw, intensity ); + */ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *scene - +// *event - +//----------------------------------------------------------------------------- +void CChoreoView::ProcessLoop( CChoreoScene *scene, CChoreoEvent *event ) +{ + Assert( event->GetType() == CChoreoEvent::LOOP ); + + // Don't loop when dragging scrubber! + if ( IsScrubbing() ) + return; + + float backtime = (float)atof( event->GetParameters() ); + + bool process = true; + int counter = event->GetLoopCount(); + if ( counter != -1 ) + { + int remaining = event->GetNumLoopsRemaining(); + if ( remaining <= 0 ) + { + process = false; + } + else + { + event->SetNumLoopsRemaining( --remaining ); + } + } + + if ( !process ) + return; + + scene->LoopToTime( backtime ); + SetScrubTime( backtime ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a gesture layer +// Input : *event - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +void CChoreoView::ProcessGesture( CChoreoScene *scene, CChoreoEvent *event ) +{ + Assert( event->GetType() == CChoreoEvent::GESTURE ); + + // NULL event is just a placeholder + if ( !Q_stricmp( event->GetName(), "NULL" ) ) + { + return; + } + + StudioModel *model = FindAssociatedModel( scene, event->GetActor() ); + if ( !model ) + return; + + if ( !event->GetActor() ) + return; + + CChoreoActor *a = event->GetActor(); + + Assert( a ); + + int iSequence = model->LookupSequence( event->GetParameters() ); + if (iSequence < 0) + return; + + // Get spline intensity for controller + float eventlocaltime = scene->GetTime() - event->GetStartTime(); + + float referencetime = event->GetOriginalPercentageFromPlaybackPercentage( eventlocaltime / event->GetDuration() ) * event->GetDuration(); + + float resampledtime = event->GetStartTime() + referencetime; + + float cycle = event->GetCompletion( resampledtime ); + + int iLayer = model->GetNewAnimationLayer( a->FindChannelIndex( event->GetChannel() ) ); + + model->SetOverlaySequence( iLayer, iSequence, event->GetIntensity( scene->GetTime() ) ); + model->SetOverlayRate( iLayer, cycle, 0.0 ); +} + + + +//----------------------------------------------------------------------------- +// Purpose: Apply a sequence +// Input : *event - +//----------------------------------------------------------------------------- +void CChoreoView::ProcessSequence( CChoreoScene *scene, CChoreoEvent *event ) +{ + Assert( event->GetType() == CChoreoEvent::SEQUENCE ); + + if ( !m_bProcessSequences ) + { + return; + } + + StudioModel *model = FindAssociatedModel( scene, event->GetActor() ); + if ( !model ) + return; + + if ( !event->GetActor() ) + return; + + CChoreoActor *a = event->GetActor(); + + Assert( a ); + + int iSequence = model->LookupSequence( event->GetParameters() ); + if (iSequence < 0) + return; + + float flFrameRate; + float flGroundSpeed; + model->GetSequenceInfo( iSequence, &flFrameRate, &flGroundSpeed ); + + float cycle; + bool looping = model->GetSequenceLoops( iSequence ); + if (looping) + { + float dt = scene->GetTime() - event->m_flPrevTime; + event->m_flPrevTime = scene->GetTime(); + dt = clamp( dt, 0.0, 0.1 ); + cycle = event->m_flPrevCycle + flFrameRate * dt; + cycle = cycle - (int)cycle; + event->m_flPrevCycle = cycle; + } + else + { + float dt = scene->GetTime() - event->GetStartTime(); + cycle = flFrameRate * dt; + cycle = cycle - (int)(cycle); + } + + // FIXME: shouldn't sequences always be lower priority than gestures? + int iLayer = model->GetNewAnimationLayer( a->FindChannelIndex( event->GetChannel() ) ); + model->SetOverlaySequence( iLayer, iSequence, event->GetIntensity( scene->GetTime() ) ); + model->SetOverlayRate( iLayer, cycle, 0.0 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Apply a walking animation +// Input : *event - +//----------------------------------------------------------------------------- +void CChoreoView::ProcessMoveto( CChoreoScene *scene, CChoreoEvent *event ) +{ + Assert( event->GetType() == CChoreoEvent::MOVETO ); + + if ( !m_bProcessSequences ) + { + return; + } + + StudioModel *model = FindAssociatedModel( scene, event->GetActor() ); + if ( !model ) + return; + + if ( !event->GetActor() ) + return; + + int iSequence = GetMovetoSequence( scene, event, model ); + if (iSequence < 0) + return; + + float flFrameRate; + float flGroundSpeed; + model->GetSequenceInfo( iSequence, &flFrameRate, &flGroundSpeed ); + + float dt = scene->GetTime() - event->GetStartTime(); + float cycle = flFrameRate * dt; + cycle = cycle - (int)(cycle); + + float idealAccel = 100; + + // accel to ideal + float t1 = flGroundSpeed / idealAccel; + + float intensity = 1.0; + + if (dt < t1) + { + intensity = dt / t1; + } + else if (event->GetDuration() - dt < t1) + { + intensity = (event->GetDuration() - dt) / t1; + } + + // movement should always be higher priority than postures, but not gestures....grrr, any way to tell them apart? + int iLayer = model->GetNewAnimationLayer( 0 /* a->FindChannelIndex( event->GetChannel() ) */ ); + model->SetOverlaySequence( iLayer, iSequence, intensity ); + model->SetOverlayRate( iLayer, cycle, 0.0 ); +} + + + +int CChoreoView::GetMovetoSequence( CChoreoScene *scene, CChoreoEvent *event, StudioModel *model ) +{ + // FIXME: needs to pull from event (activity or sequence?) + if ( !event->GetParameters2() || !event->GetParameters2()[0] ) + return model->LookupSequence( "walk_all" ); + + // Custom distance styles are appended to param2 with a space as a separator + const char *pszAct = Q_strstr( event->GetParameters2(), " " ); + if ( pszAct ) + { + char szActName[256]; + Q_strncpy( szActName, event->GetParameters2(), sizeof(szActName) ); + szActName[ (pszAct-event->GetParameters2()) ] = '\0'; + pszAct = szActName; + } + else + { + pszAct = event->GetParameters2(); + } + + if ( !Q_strcmp( pszAct, "Walk" ) ) + { + pszAct = "ACT_WALK"; + } + else if ( !Q_strcmp( pszAct, "Run" ) ) + { + pszAct = "ACT_RUN"; + } + else if ( !Q_strcmp( pszAct, "CrouchWalk" ) ) + { + pszAct = "ACT_WALK_CROUCH"; + } + + int iSequence = model->LookupActivity( pszAct ); + + if (iSequence == -1) + { + return model->LookupSequence( "walk_all" ); + } + return iSequence; +} + + +//----------------------------------------------------------------------------- +// Purpose: Process a pause event +// Input : *event - +//----------------------------------------------------------------------------- +void CChoreoView::ProcessPause( CChoreoScene *scene, CChoreoEvent *event ) +{ + Assert( event->GetType() == CChoreoEvent::SECTION ); + + // Don't pause if scrubbing + bool scrubbing = ( m_nDragType == DRAGTYPE_SCRUBBER ) ? true : false; + if ( scrubbing ) + return; + + PauseScene(); + + m_bAutomated = false; + m_nAutomatedAction = SCENE_ACTION_UNKNOWN; + m_flAutomationDelay = 0.0f; + m_flAutomationTime = 0.0f; + + // Check for auto resume/cancel + ParseFromMemory( (char *)event->GetParameters(), strlen( event->GetParameters() ) ); + if ( tokenprocessor->TokenAvailable() ) + { + tokenprocessor->GetToken( false ); + if ( !stricmp( tokenprocessor->CurrentToken(), "automate" ) ) + { + if ( tokenprocessor->TokenAvailable() ) + { + tokenprocessor->GetToken( false ); + if ( !stricmp( tokenprocessor->CurrentToken(), "Cancel" ) ) + { + m_nAutomatedAction = SCENE_ACTION_CANCEL; + } + else if ( !stricmp( tokenprocessor->CurrentToken(), "Resume" ) ) + { + m_nAutomatedAction = SCENE_ACTION_RESUME; + } + + if ( tokenprocessor->TokenAvailable() && + m_nAutomatedAction != SCENE_ACTION_UNKNOWN ) + { + tokenprocessor->GetToken( false ); + m_flAutomationDelay = (float)atof( tokenprocessor->CurrentToken() ); + + if ( m_flAutomationDelay > 0.0f ) + { + // Success + m_bAutomated = true; + m_flAutomationTime = 0.0f; + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Main event processor +// Input : *event - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +void CChoreoView::ProcessEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) +{ + if ( !event || !event->GetActive() ) + return; + + CChoreoActor *actor = event->GetActor(); + if ( actor && !actor->GetActive() ) + { + return; + } + + CChoreoChannel *channel = event->GetChannel(); + if ( channel && !channel->GetActive() ) + { + return; + } + + switch( event->GetType() ) + { + case CChoreoEvent::EXPRESSION: + ProcessExpression( scene, event ); + break; + case CChoreoEvent::FLEXANIMATION: + ProcessFlexAnimation( scene, event ); + break; + case CChoreoEvent::LOOKAT: + ProcessLookat( scene, event ); + break; + case CChoreoEvent::FACE: + ProcessFace( scene, event ); + break; + case CChoreoEvent::GESTURE: + ProcessGesture( scene, event ); + break; + case CChoreoEvent::SEQUENCE: + ProcessSequence( scene, event ); + break; + case CChoreoEvent::SUBSCENE: + ProcessSubscene( scene, event ); + break; + case CChoreoEvent::SPEAK: + ProcessSpeak( scene, event ); + break; + case CChoreoEvent::MOVETO: + ProcessMoveto( scene, event ); + break; + case CChoreoEvent::STOPPOINT: + // Nothing + break; + case CChoreoEvent::INTERRUPT: + ProcessInterrupt( scene, event ); + break; + case CChoreoEvent::PERMIT_RESPONSES: + ProcessPermitResponses( scene, event ); + break; + default: + break; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Main event completion checker +// Input : *event - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoView::CheckEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) +{ + if ( !event || !event->GetActive() ) + return true; + + CChoreoActor *actor = event->GetActor(); + if ( actor && !actor->GetActive() ) + { + return true; + } + + CChoreoChannel *channel = event->GetChannel(); + if ( channel && !channel->GetActive() ) + { + return true; + } + + switch( event->GetType() ) + { + case CChoreoEvent::EXPRESSION: + break; + case CChoreoEvent::FLEXANIMATION: + break; + case CChoreoEvent::LOOKAT: + break; + case CChoreoEvent::GESTURE: + break; + case CChoreoEvent::SEQUENCE: + break; + case CChoreoEvent::SUBSCENE: + break; + case CChoreoEvent::SPEAK: + break; + case CChoreoEvent::MOVETO: + break; + case CChoreoEvent::INTERRUPT: + break; + case CChoreoEvent::PERMIT_RESPONSES: + break; + default: + break; + } + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::PauseThink( void ) +{ + // FIXME: Game code would check for conditions being met + + if ( !m_bAutomated ) + return; + + m_flAutomationTime += fabs( m_flFrameTime ); + + RECT rcPauseRect; + rcPauseRect.left = 0; + rcPauseRect.right = w2(); + rcPauseRect.top = GetCaptionHeight() + SCRUBBER_HEIGHT; + rcPauseRect.bottom = rcPauseRect.top + 10; + + CChoreoWidgetDrawHelper drawHelper( this, + rcPauseRect, + COLOR_CHOREO_BACKGROUND ); + + DrawSceneABTicks( drawHelper ); + + if ( m_flAutomationDelay > 0.0f && + m_flAutomationTime < m_flAutomationDelay ) + { + char sz[ 256 ]; + sprintf( sz, "Pause %.2f/%.2f", m_flAutomationTime, m_flAutomationDelay ); + + int textlen = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, sz ); + + RECT rcText; + GetScrubHandleRect( rcText, true ); + + rcText.left = ( rcText.left + rcText.right ) / 2; + rcText.left -= ( textlen * 0.5f ); + rcText.right = rcText.left + textlen + 1; + + rcText.top = rcPauseRect.top; + rcText.bottom = rcPauseRect.bottom; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, COLOR_CHOREO_PLAYBACKTICKTEXT, rcText, sz ); + + return; + } + + // Time to act + m_bAutomated = false; + + switch ( m_nAutomatedAction ) + { + case SCENE_ACTION_RESUME: + m_bPaused = false; + sound->StopAll(); + break; + case SCENE_ACTION_CANCEL: + FinishSimulation(); + break; + default: + break; + } + + m_nAutomatedAction = SCENE_ACTION_UNKNOWN; + m_flAutomationTime = 0.0f; + m_flAutomationDelay = 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: Conclude simulation +//----------------------------------------------------------------------------- +void CChoreoView::FinishSimulation( void ) +{ + if ( !m_bSimulating ) + return; + +// m_pScene->ResetSimulation(); + + m_bSimulating = false; + m_bPaused = false; + + sound->StopAll(); + + if ( m_bResetSpeedScale ) + { + m_bResetSpeedScale = false; + g_viewerSettings.speedScale = m_flLastSpeedScale; + m_flLastSpeedScale = 0.0f; + + Con_Printf( "Resetting speed scale to %f\n", m_flLastSpeedScale ); + } + + models->ClearOverlaysSequences(); + + // redraw(); +} + +void CChoreoView::SceneThink( float time ) +{ + if ( !m_pScene ) + return; + + if ( m_bSimulating ) + { + if ( m_bPaused ) + { + PauseThink(); + } + else + { + m_pScene->SetSoundFileStartupLatency( 0.0f ); + + models->CheckResetFlexes(); + + ResetTargetSettings(); + + models->ClearOverlaysSequences(); + + // Tell scene to go + m_pScene->Think( time ); + + // Move flexes toward their targets + UpdateCurrentSettings(); + } + } + else + { + FinishSimulation(); + } + + if ( !ShouldProcessSpeak() ) + { + bool autoprocess = ShouldAutoProcess(); + bool anyscrub = IsAnyToolScrubbing() ; + bool anyprocessing = IsAnyToolProcessing(); + + //Con_Printf( "autoprocess %i anyscrub %i anyprocessing %i\n", + // autoprocess ? 1 : 0, + // anyscrub ? 1 : 0, + // anyprocessing ? 1 : 0 ); + + if ( !anyscrub && + !anyprocessing && + autoprocess && + !m_bForceProcess ) + { + sound->StopAll(); + + // why clear lookat? + //models->ClearModelTargets( false ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::LayoutScene( void ) +{ + if ( !m_pScene ) + return; + + if ( m_bLayoutIsValid ) + return; + + m_pScene->ReconcileTags(); + + RECT rc; + GetClientRect( (HWND)getHandle(), &rc ); + + RECT rcClient = rc; + rcClient.top += GetStartRow(); + OffsetRect( &rcClient, 0, -m_nTopOffset ); + + m_flStartTime = m_flLeftOffset / GetPixelsPerSecond(); + + m_flEndTime = m_flStartTime + (float)( rcClient.right - GetLabelWidth() ) / GetPixelsPerSecond(); + + m_rcTimeLine = rcClient; + m_rcTimeLine.top = GetCaptionHeight() + SCRUBBER_HEIGHT; + m_rcTimeLine.bottom = m_rcTimeLine.top + 44; + + int currentRow = rcClient.top + 2; + int itemHeight; + + // Draw actors + int i; + for ( i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *a = m_SceneActors[ i ]; + Assert( a ); + if ( !a ) + { + continue; + } + + // Figure out rectangle + itemHeight = a->GetItemHeight(); + + RECT rcActor = rcClient; + rcActor.top = currentRow; + rcActor.bottom = currentRow + itemHeight; + + a->Layout( rcActor ); + + currentRow += itemHeight; + } + + // Draw section tabs + for ( i = 0; i < m_SceneGlobalEvents.Size(); i++ ) + { + CChoreoGlobalEventWidget *e = m_SceneGlobalEvents[ i ]; + if ( !e ) + continue; + + RECT rcEvent; + rcEvent = m_rcTimeLine; + + float frac = ( e->GetEvent()->GetStartTime() - m_flStartTime ) / ( m_flEndTime - m_flStartTime ); + + rcEvent.left = GetLabelWidth() + rcEvent.left + (int)( frac * ( m_rcTimeLine.right - m_rcTimeLine.left - GetLabelWidth() ) ); + rcEvent.left -= 4; + rcEvent.right = rcEvent.left + 8; + rcEvent.bottom += 0; + rcEvent.top = rcEvent.bottom - 8; + + if ( rcEvent.left + 10 < GetLabelWidth() ) + { + e->setVisible( false ); + } + else + { + e->setVisible( true ); + } + + // OffsetRect( &rcEvent, GetLabelWidth(), 0 ); + + e->Layout( rcEvent ); + } + + m_bLayoutIsValid = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::DeleteSceneWidgets( void ) +{ + bool oldcandraw = m_bCanDraw; + + m_bCanDraw = false; + + int i; + CChoreoWidget *w; + + ClearStatusArea(); + + for( i = 0 ; i < m_SceneActors.Size(); i++ ) + { + w = m_SceneActors[ i ]; + m_ActorExpanded[ i ].expanded = ((CChoreoActorWidget *)w)->GetShowChannels(); + delete w; + } + + m_SceneActors.RemoveAll(); + + for( i = 0 ; i < m_SceneGlobalEvents.Size(); i++ ) + { + w = m_SceneGlobalEvents[ i ]; + delete w; + } + + m_SceneGlobalEvents.RemoveAll(); + + m_bCanDraw = oldcandraw; + + // Make sure nobody is still pointing at us + m_pClickedActor = NULL; + m_pClickedChannel = NULL; + m_pClickedEvent = NULL; + m_pClickedGlobalEvent = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::InvalidateLayout( void ) +{ + if ( m_bSuppressLayout ) + return; + + if ( ComputeHPixelsNeeded() != m_nLastHPixelsNeeded ) + { + RepositionHSlider(); + } + + if ( ComputeVPixelsNeeded() != m_nLastVPixelsNeeded ) + { + RepositionVSlider(); + } + + // Recheck gesture start/end times + if ( m_pScene ) + { + m_pScene->ReconcileGestureTimes(); + m_pScene->ReconcileCloseCaption(); + } + + m_bLayoutIsValid = false; + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::CreateSceneWidgets( void ) +{ + DeleteSceneWidgets(); + + m_bSuppressLayout = true; + + int i; + for ( i = 0; i < m_pScene->GetNumActors(); i++ ) + { + CChoreoActor *a = m_pScene->GetActor( i ); + Assert( a ); + if ( !a ) + continue; + + CChoreoActorWidget *actorWidget = new CChoreoActorWidget( NULL ); + Assert( actorWidget ); + + actorWidget->SetActor( a ); + actorWidget->Create(); + + m_SceneActors.AddToTail( actorWidget ); + + actorWidget->ShowChannels( m_ActorExpanded[ i ].expanded ); + } + + // Find global events + for ( i = 0; i < m_pScene->GetNumEvents(); i++ ) + { + CChoreoEvent *e = m_pScene->GetEvent( i ); + if ( !e || e->GetActor() ) + continue; + + CChoreoGlobalEventWidget *eventWidget = new CChoreoGlobalEventWidget( NULL ); + Assert( eventWidget ); + + eventWidget->SetEvent( e ); + eventWidget->Create(); + + m_SceneGlobalEvents.AddToTail( eventWidget ); + } + + m_bSuppressLayout = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoView::GetLabelWidth( void ) +{ + return m_nLabelWidth; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoView::GetStartRow( void ) +{ + return m_nStartRow + GetCaptionHeight() + SCRUBBER_HEIGHT; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoView::GetRowHeight( void ) +{ + return m_nRowHeight; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoView::GetFontSize( void ) +{ + return m_nFontSize; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoView::ComputeVPixelsNeeded( void ) +{ + int pixels = 0; + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *actor = m_SceneActors[ i ]; + if ( !actor ) + continue; + + pixels += actor->GetItemHeight() + 2; + } + + pixels += GetStartRow() + 15; + +// pixels += m_nInfoHeight; + + //pixels += 30; + return pixels; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoView::ComputeHPixelsNeeded( void ) +{ + if ( !m_pScene ) + { + return 0; + } + + int pixels = 0; + float maxtime = m_pScene->FindStopTime(); + if ( maxtime < 5.0 ) + { + maxtime = 5.0f; + } + pixels = (int)( ( maxtime + 5.0 ) * GetPixelsPerSecond() ); + + return pixels; + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::RepositionVSlider( void ) +{ + int pixelsneeded = ComputeVPixelsNeeded(); + + if ( pixelsneeded <= ( h2() - GetStartRow() )) + { + m_pVertScrollBar->setVisible( false ); + m_nTopOffset = 0; + } + else + { + m_pVertScrollBar->setVisible( true ); + } + + m_pVertScrollBar->setBounds( w2() - m_nScrollbarHeight, GetStartRow(), m_nScrollbarHeight, h2() - m_nScrollbarHeight - GetStartRow() ); + + //int visiblepixels = h2() - m_nScrollbarHeight - GetStartRow(); + //m_nTopOffset = min( pixelsneeded - visiblepixels, m_nTopOffset ); + m_nTopOffset = max( 0, m_nTopOffset ); + m_nTopOffset = min( pixelsneeded, m_nTopOffset ); + + m_pVertScrollBar->setRange( 0, pixelsneeded ); + m_pVertScrollBar->setValue( m_nTopOffset ); + m_pVertScrollBar->setPagesize( h2() - GetStartRow() ); + + m_nLastVPixelsNeeded = pixelsneeded; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::RepositionHSlider( void ) +{ + int pixelsneeded = ComputeHPixelsNeeded(); + + int w = w2(); + int lw = GetLabelWidth(); + + if ( pixelsneeded <= ( w - lw ) ) + { + m_pHorzScrollBar->setVisible( false ); + } + else + { + m_pHorzScrollBar->setVisible( true ); + } + m_pHorzScrollBar->setBounds( 0, h2() - m_nScrollbarHeight, w - m_nScrollbarHeight, m_nScrollbarHeight ); + + m_flLeftOffset = max( 0.f, m_flLeftOffset ); + m_flLeftOffset = min( (float)pixelsneeded, m_flLeftOffset ); + + m_pHorzScrollBar->setRange( 0, pixelsneeded ); + m_pHorzScrollBar->setValue( (int)m_flLeftOffset ); + m_pHorzScrollBar->setPagesize(w - lw ); + + m_nLastHPixelsNeeded = pixelsneeded; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dirty - +//----------------------------------------------------------------------------- +void CChoreoView::SetDirty( bool dirty, bool clearundo /*=true*/ ) +{ + bool changed = dirty != m_bDirty; + + m_bDirty = dirty; + + if ( !dirty && clearundo ) + { + WipeUndo(); + redraw(); + } + + if ( changed ) + { + SetPrefix( m_bDirty ? "* " : "" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::New( void ) +{ + if ( m_pScene ) + { + Close( ); + if ( m_pScene ) + { + return; + } + } + + char scenefile[ 512 ]; + if ( FacePoser_ShowSaveFileNameDialog( scenefile, sizeof( scenefile ), "scenes", "*.vcd" ) ) + { + Q_DefaultExtension( scenefile, ".vcd", sizeof( scenefile ) ); + + m_pScene = new CChoreoScene( this ); + g_MDLViewer->InitGridSettings(); + SetChoreoFile( scenefile ); + m_pScene->SetPrintFunc( Con_Printf ); + + ShowButtons( true ); + + SetDirty( false ); + } + + if ( !m_pScene ) + return; + + // Get first actor name + CActorParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Create Actor" ); + strcpy( params.m_szName, "" ); + + if ( !ActorProperties( ¶ms ) ) + return; + + if ( strlen( params.m_szName ) <= 0 ) + return; + + SetDirty( true ); + + PushUndo( "Create Actor" ); + + Con_Printf( "Creating scene %s with actor '%s'\n", GetChoreoFile(), params.m_szName ); + + CChoreoActor *actor = m_pScene->AllocActor(); + if ( actor ) + { + actor->SetName( params.m_szName ); + } + + PushRedo( "Create Actor" ); + + CreateSceneWidgets(); + // Redraw + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::Save( void ) +{ + if ( !m_pScene ) + return; + + if ( !MakeFileWriteablePrompt( GetChoreoFile(), "VCD File" ) ) + { + Con_Printf( "Not saving changes to %s\n", GetChoreoFile() ); + return; + } + + Con_Printf( "Saving changes to %s\n", GetChoreoFile() ); + + CP4AutoEditAddFile checkout( GetChoreoFile() ); + if ( !m_pScene->SaveToFile( GetChoreoFile() ) ) + { + mxMessageBox( this, va( "Unable to write \"%s\"", GetChoreoFile() ), + "SaveToFile", MX_MB_OK | MX_MB_ERROR ); + } + + g_MDLViewer->OnVCDSaved(); + + // Refresh the suffix + SetChoreoFile( GetChoreoFile() ); + + SetDirty( false, false ); + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::SaveAs( void ) +{ + if ( !m_pScene ) + return; + + char scenefile[ 512 ]; + if ( !FacePoser_ShowSaveFileNameDialog( scenefile, sizeof( scenefile ), "scenes", "*.vcd" ) ) + return; + + Q_DefaultExtension( scenefile, ".vcd", sizeof( scenefile ) ); + + Con_Printf( "Saving %s\n", scenefile ); + + MakeFileWriteable( scenefile ); + + // Change filename + SetChoreoFile( scenefile ); + + // Write it out baby + CP4AutoEditAddFile checkout( scenefile ); + if (!m_pScene->SaveToFile( GetChoreoFile() )) + { + mxMessageBox( this, va( "Unable to write \"%s\"", GetChoreoFile() ), + "SaveToFile", MX_MB_OK | MX_MB_ERROR ); + } + + g_MDLViewer->OnVCDSaved(); + + SetDirty( false, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::Load( void ) +{ + char scenefile[ 512 ]; + if ( !FacePoser_ShowOpenFileNameDialog( scenefile, sizeof( scenefile ), "scenes", "*.vcd" ) ) + { + return; + } + + Q_DefaultExtension( scenefile, ".vcd", sizeof( scenefile ) ); + + LoadSceneFromFile( scenefile ); + + m_nextFileList.RemoveAll(); +} + +void CChoreoView::LoadNext( void ) +{ + if (GetChoreoFile() == NULL) + return; + + char fixedupFile[ 512 ]; + V_FixupPathName( fixedupFile, sizeof( fixedupFile ), GetChoreoFile() ); + + char relativeFile[ 512 ]; + filesystem->FullPathToRelativePath( fixedupFile, relativeFile, sizeof( relativeFile ) ); + + char relativePath[ 512 ]; + Q_ExtractFilePath( relativeFile, relativePath, sizeof( relativePath ) ); + + if (m_nextFileList.Count() == 0) + { + // iterate files in the local directory + char path[ 512 ]; + strcpy( path, relativePath ); + strcat( path, "/*.vcd" ); + + FileFindHandle_t hFindFile; + char const *fn = filesystem->FindFirstEx( path, "MOD", &hFindFile ); + if ( fn ) + { + while ( fn ) + { + // Don't do anything with directories + if ( !filesystem->FindIsDirectory( hFindFile ) ) + { + CUtlString s = fn; + m_nextFileList.AddToTail( s ); + } + + fn = filesystem->FindNext( hFindFile ); + } + + filesystem->FindClose( hFindFile ); + } + } + + // look for a match, then pick the next in the list + const char *fileBase; + fileBase = V_UnqualifiedFileName( fixedupFile ); + + for (int i = 0; i < m_nextFileList.Count(); i++) + { + if (!stricmp( fileBase, m_nextFileList[i] )) + { + char fileName[512]; + strcpy( fileName, relativePath ); + if (i < m_nextFileList.Count() - 1) + { + strcat( fileName, m_nextFileList[i+1] ); + } + else + { + strcat( fileName, m_nextFileList[0] ); + } + + LoadSceneFromFile( fileName ); + break; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *filename - +//----------------------------------------------------------------------------- +void CChoreoView::LoadSceneFromFile( const char *filename ) +{ + if ( filename[ 0 ] == '/' || + filename[ 0 ] == '\\' ) + { + ++filename; + } + + char fn[ 512 ]; + Q_strncpy( fn, filename, sizeof( fn ) ); + if ( m_pScene ) + { + Close(); + if ( m_pScene ) + { + return; + } + } + + m_pScene = LoadScene( fn ); + g_MDLViewer->InitGridSettings(); + if ( !m_pScene ) + return; + + g_MDLViewer->OnFileLoaded( fn ); + + ShowButtons( true ); + + CChoreoWidget::m_pScene = m_pScene; + SetChoreoFile( fn ); + + bool cleaned = FixupSequenceDurations( m_pScene, false ); + + SetDirty( cleaned ); + + DeleteSceneWidgets(); + CreateSceneWidgets(); + + // Force scroll bars to recompute + ForceScrollBarsToRecompute( false ); + + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : closing - +//----------------------------------------------------------------------------- +void CChoreoView::UnloadScene( void ) +{ + InvalidateLayout(); + ReportSceneClearToTools(); + + ClearStatusArea(); + + delete m_pScene; + m_pScene = NULL; + SetDirty( false ); + SetChoreoFile( "" ); + g_MDLViewer->InitGridSettings(); + CChoreoWidget::m_pScene = NULL; + + DeleteSceneWidgets(); + + m_pVertScrollBar->setVisible( false ); + m_pHorzScrollBar->setVisible( false ); + + ShowButtons( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *channel - +//----------------------------------------------------------------------------- +void CChoreoView::DeleteChannel( CChoreoChannel *channel ) +{ + if ( !channel || !m_pScene ) + return; + + SetDirty( true ); + + PushUndo( "Delete Channel" ); + + DeleteSceneWidgets(); + + // Delete channel and it's children + // Find the appropriate actor + for ( int i = 0; i < m_pScene->GetNumActors(); i++ ) + { + CChoreoActor *a = m_pScene->GetActor( i ); + if ( !a ) + continue; + + if ( a->FindChannelIndex( channel ) == -1 ) + continue; + + Con_Printf( "Deleting %s\n", channel->GetName() ); + a->RemoveChannel( channel ); + + m_pScene->DeleteReferencedObjects( channel ); + break; + } + + ReportSceneClearToTools(); + + CreateSceneWidgets(); + + PushRedo( "Delete Channel" ); + + // Redraw + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::NewChannel( void ) +{ + if ( !m_pScene ) + return; + + if ( !m_pScene->GetNumActors() ) + { + Con_Printf( "You must create an actor before you can add a channel\n" ); + return; + } + + CChannelParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Create Channel" ); + strcpy( params.m_szName, "" ); + params.m_bShowActors = true; + strcpy( params.m_szSelectedActor, "" ); + params.m_pScene = m_pScene; + + if ( !ChannelProperties( ¶ms ) ) + { + return; + } + + if ( strlen( params.m_szName ) <= 0 ) + { + return; + } + + CChoreoActor *actor = m_pScene->FindActor( params.m_szSelectedActor ); + if ( !actor ) + { + Con_Printf( "Can't add channel %s, actor %s doesn't exist\n", params.m_szName, params.m_szSelectedActor ); + return; + } + + SetDirty( true ); + + PushUndo( "Add Channel" ); + + DeleteSceneWidgets(); + + CChoreoChannel *channel = m_pScene->AllocChannel(); + if ( !channel ) + { + Con_Printf( "Unable to allocate channel %s!\n", params.m_szName ); + } + else + { + channel->SetName( params.m_szName ); + channel->SetActor( actor ); + actor->AddChannel( channel ); + } + + CreateSceneWidgets(); + + PushRedo( "Add Channel" ); + + // Redraw + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *channel - +//----------------------------------------------------------------------------- +void CChoreoView::MoveChannelUp( CChoreoChannel *channel ) +{ + SetDirty( true ); + + PushUndo( "Move Channel Up" ); + + DeleteSceneWidgets(); + + // Find the appropriate actor + for ( int i = 0; i < m_pScene->GetNumActors(); i++ ) + { + CChoreoActor *a = m_pScene->GetActor( i ); + if ( !a ) + continue; + + int index = a->FindChannelIndex( channel ); + if ( index == -1 ) + continue; + + if ( index != 0 ) + { + Con_Printf( "Moving %s up\n", channel->GetName() ); + a->SwapChannels( index, index - 1 ); + } + break; + } + + CreateSceneWidgets(); + + PushRedo( "Move Channel Up" ); + + // Redraw + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *channel - +//----------------------------------------------------------------------------- +void CChoreoView::MoveChannelDown( CChoreoChannel *channel ) +{ + SetDirty( true ); + + PushUndo( "Move Channel Down" ); + + DeleteSceneWidgets(); + + // Find the appropriate actor + for ( int i = 0; i < m_pScene->GetNumActors(); i++ ) + { + CChoreoActor *a = m_pScene->GetActor( i ); + if ( !a ) + continue; + + int index = a->FindChannelIndex( channel ); + if ( index == -1 ) + continue; + + if ( index < a->GetNumChannels() - 1 ) + { + Con_Printf( "Moving %s down\n", channel->GetName() ); + a->SwapChannels( index, index + 1 ); + } + break; + } + + CreateSceneWidgets(); + + PushRedo( "Move Channel Down" ); + + // Redraw + InvalidateLayout(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *channel - +//----------------------------------------------------------------------------- +void CChoreoView::EditChannel( CChoreoChannel *channel ) +{ + if ( !channel ) + return; + + CChannelParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Edit Channel" ); + V_strcpy_safe( params.m_szName, channel->GetName() ); + + if ( !ChannelProperties( ¶ms ) ) + return; + + if ( strlen( params.m_szName ) <= 0 ) + return; + + SetDirty( true ); + + PushUndo( "Edit Channel" ); + + channel->SetName( params.m_szName ); + + PushRedo( "Edit Channel" ); + + // Redraw + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +//----------------------------------------------------------------------------- +void CChoreoView::DeleteActor( CChoreoActor *actor ) +{ + if ( !actor || !m_pScene ) + return; + + SetDirty( true ); + + PushUndo( "Delete Actor" ); + + DeleteSceneWidgets(); + + // Delete channel and it's children + // Find the appropriate actor + Con_Printf( "Deleting %s\n", actor->GetName() ); + m_pScene->RemoveActor( actor ); + + m_pScene->DeleteReferencedObjects( actor ); + + ReportSceneClearToTools(); + + CreateSceneWidgets(); + + PushRedo( "Delete Actor" ); + + // Redraw + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::NewActor( void ) +{ + if ( !m_pScene ) + { + Con_ErrorPrintf( "You must load or create a scene file first\n" ); + return; + } + + CActorParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Create Actor" ); + strcpy( params.m_szName, "" ); + + if ( !ActorProperties( ¶ms ) ) + return; + + if ( strlen( params.m_szName ) <= 0 ) + return; + + SetDirty( true ); + + PushUndo( "Add Actor" ); + + DeleteSceneWidgets(); + + Con_Printf( "Adding new actor '%s'\n", params.m_szName ); + + CChoreoActor *actor = m_pScene->AllocActor(); + if ( actor ) + { + actor->SetName( params.m_szName ); + } + + CreateSceneWidgets(); + + PushRedo( "Add Actor" ); + + // Redraw + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +//----------------------------------------------------------------------------- +void CChoreoView::MoveActorUp( CChoreoActor *actor ) +{ + DeleteSceneWidgets(); + + int index = m_pScene->FindActorIndex( actor ); + // found it and it's not first + if ( index != -1 && index != 0 ) + { + Con_Printf( "Moving %s up\n", actor->GetName() ); + + SetDirty( true ); + + PushUndo( "Move Actor Up" ); + + m_pScene->SwapActors( index, index - 1 ); + + PushRedo( "Move Actor Up" ); + } + + CreateSceneWidgets(); + // Redraw + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +//----------------------------------------------------------------------------- +void CChoreoView::MoveActorDown( CChoreoActor *actor ) +{ + DeleteSceneWidgets(); + + int index = m_pScene->FindActorIndex( actor ); + // found it and it's not first + if ( index != -1 && ( index < m_pScene->GetNumActors() - 1 ) ) + { + Con_Printf( "Moving %s down\n", actor->GetName() ); + + SetDirty( true ); + + PushUndo( "Move Actor Down" ); + + m_pScene->SwapActors( index, index + 1 ); + + PushRedo( "Move Actor Down" ); + } + + CreateSceneWidgets(); + // Redraw + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *actor - +//----------------------------------------------------------------------------- +void CChoreoView::EditActor( CChoreoActor *actor ) +{ + if ( !actor ) + return; + + CActorParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Edit Actor" ); + V_strcpy_safe( params.m_szName, actor->GetName() ); + + if ( !ActorProperties( ¶ms ) ) + return; + + if ( strlen( params.m_szName ) <= 0 ) + return; + + SetDirty( true ); + + PushUndo( "Edit Actor" ); + + actor->SetName( params.m_szName ); + + PushRedo( "Edit Actor" ); + + // Redraw + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - +//----------------------------------------------------------------------------- +void CChoreoView::AddEvent( int type, int subtype /*= 0*/, char const *defaultparameters /*= NULL*/ ) +{ + int mx, my; + mx = m_nClickedX; + my = m_nClickedY; + CChoreoChannelWidget *channel = m_pClickedChannel; + if ( !channel || !channel->GetChannel() ) + { + CChoreoActorWidget *actor = m_pClickedActor; + if ( actor ) + { + if ( actor->GetNumChannels() <= 0 ) + return; + + channel = actor->GetChannel( 0 ); + if ( !channel || !channel->GetChannel() ) + return; + } + else + { + return; + } + } + + // Convert click position local to this window + POINT pt; + pt.x = mx; + pt.y = my; + + CEventParams params; + memset( ¶ms, 0, sizeof( params ) ); + + if ( defaultparameters ) + { + Q_strncpy( params.m_szParameters, defaultparameters, sizeof( params.m_szParameters ) ); + } + + strcpy( params.m_szDialogTitle, "Create Event" ); + + params.m_nType = type; + params.m_pScene = m_pScene; + + params.m_bFixedLength = false; + params.m_bResumeCondition = false; + params.m_flStartTime = GetTimeValueForMouse( pt.x ); + params.m_bCloseCaptionNoAttenuate = false; + params.m_bForceShortMovement = false; + params.m_bSyncToFollowingGesture = false; + params.m_bDisabled = false; + params.m_bPlayOverScript = false; + + switch ( type ) + { + case CChoreoEvent::EXPRESSION: + case CChoreoEvent::FLEXANIMATION: + case CChoreoEvent::GESTURE: + case CChoreoEvent::SEQUENCE: + case CChoreoEvent::LOOKAT: + case CChoreoEvent::MOVETO: + case CChoreoEvent::FACE: + case CChoreoEvent::SUBSCENE: + case CChoreoEvent::INTERRUPT: + case CChoreoEvent::GENERIC: + case CChoreoEvent::PERMIT_RESPONSES: + params.m_bHasEndTime = true; + params.m_flEndTime = params.m_flStartTime + 0.5f; + if ( type == CChoreoEvent::GESTURE && subtype == 1 ) + { + strcpy( params.m_szDialogTitle, "Create <NULL> Gesture" ); + strcpy( params.m_szName, "NULL" ); + } + break; + case CChoreoEvent::SPEAK: + params.m_bFixedLength = true; + params.m_bHasEndTime = false; + params.m_flEndTime = -1.0f; + break; + default: + params.m_bHasEndTime = false; + params.m_flEndTime = -1.0f; + break; + } + + params.m_bUsesTag = false; + + while (1) + { + SetScrubTargetTime( m_flScrub ); + FinishSimulation(); + sound->Flush(); + + m_bForceProcess = true; + if (!EventProperties( ¶ms )) + { + m_bForceProcess = false; + return; + } + m_bForceProcess = false; + + if ( Q_strlen( params.m_szName ) <= 0 ) + { + mxMessageBox( this, va( "Event must have a valid name" ), + "Edit Event", MX_MB_OK | MX_MB_ERROR ); + continue; + } + + if ( Q_strlen( params.m_szParameters ) <= 0 ) + { + bool shouldBreak = false; + + switch ( params.m_nType ) + { + case CChoreoEvent::FLEXANIMATION: + case CChoreoEvent::INTERRUPT: + case CChoreoEvent::PERMIT_RESPONSES: + shouldBreak = true; + break; + case CChoreoEvent::GESTURE: + if ( subtype == 1 ) + { + shouldBreak = true; + } + break; + default: + // Have to have a non-null parameters block + break; + } + + if ( !shouldBreak ) + { + mxMessageBox( this, va( "No parameters specified for %s\n", params.m_szName ), + "Edit Event", MX_MB_OK | MX_MB_ERROR ); + continue; + } + } + + break; + } + + SetDirty( true ); + + PushUndo( "Add Event" ); + + CChoreoEvent *event = m_pScene->AllocEvent(); + if ( event ) + { + event->SetType( (CChoreoEvent::EVENTTYPE)type ); + event->SetName( params.m_szName ); + event->SetParameters( params.m_szParameters ); + event->SetParameters2( params.m_szParameters2 ); + event->SetParameters3( params.m_szParameters3 ); + event->SetStartTime( params.m_flStartTime ); + + event->SetResumeCondition( params.m_bResumeCondition ); + event->SetLockBodyFacing( params.m_bLockBodyFacing ); + event->SetDistanceToTarget( params.m_flDistanceToTarget ); + event->SetForceShortMovement( params.m_bForceShortMovement ); + event->SetSyncToFollowingGesture( params.m_bSyncToFollowingGesture ); + event->SetActive( !params.m_bDisabled ); + event->SetPlayOverScript( params.m_bPlayOverScript ); + + if ( params.m_bUsesTag ) + { + event->SetUsingRelativeTag( true, params.m_szTagName, params.m_szTagWav ); + } + else + { + event->SetUsingRelativeTag( false ); + } + CChoreoChannel *pchannel = channel->GetChannel(); + + event->SetChannel( pchannel ); + event->SetActor( pchannel->GetActor() ); + + if ( params.m_bHasEndTime && + params.m_flEndTime != -1.0 && + params.m_flEndTime > params.m_flStartTime ) + { + event->SetEndTime( params.m_flEndTime ); + } + else + { + event->SetEndTime( -1.0f ); + } + + switch ( event->GetType() ) + { + default: + break; + case CChoreoEvent::SUBSCENE: + { + // Just grab end time + CChoreoScene *scene = LoadScene( event->GetParameters() ); + if ( scene ) + { + event->SetEndTime( params.m_flStartTime + scene->FindStopTime() ); + } + delete scene; + } + break; + case CChoreoEvent::SEQUENCE: + { + CheckSequenceLength( event, false ); + // AutoaddSequenceKeys( event); + } + break; + case CChoreoEvent::GESTURE: + { + DefaultGestureLength( event, false ); + AutoaddGestureKeys( event, false ); + } + break; + case CChoreoEvent::LOOKAT: + case CChoreoEvent::FACE: + { + if ( params.usepitchyaw ) + { + event->SetPitch( params.pitch ); + event->SetYaw( params.yaw ); + } + else + { + event->SetPitch( 0 ); + event->SetYaw( 0 ); + } + } + break; + case CChoreoEvent::SPEAK: + { + // Try and load wav to get length + CAudioSource *wave = sound->LoadSound( va( "sound/%s", FacePoser_TranslateSoundName( event ) ) ); + if ( wave ) + { + event->SetEndTime( params.m_flStartTime + wave->GetRunningLength() ); + delete wave; + } + + event->SetSuppressingCaptionAttenuation( params.m_bCloseCaptionNoAttenuate ); + } + break; + } + event->SnapTimes(); + + DeleteSceneWidgets(); + + // Add to appropriate channel + pchannel->AddEvent( event ); + + CreateSceneWidgets(); + + // Redraw + InvalidateLayout(); + } + + PushRedo( "Add Event" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a scene "pause" event +//----------------------------------------------------------------------------- +void CChoreoView::AddGlobalEvent( CChoreoEvent::EVENTTYPE type ) +{ + int mx, my; + mx = m_nClickedX; + my = m_nClickedY; + + // Convert click position local to this window + POINT pt; + pt.x = mx; + pt.y = my; + + CGlobalEventParams params; + memset( ¶ms, 0, sizeof( params ) ); + + params.m_nType = type; + + switch ( type ) + { + default: + Assert( 0 ); + strcpy( params.m_szDialogTitle, "???" ); + break; + case CChoreoEvent::SECTION: + { + strcpy( params.m_szDialogTitle, "Add Pause Point" ); + } + break; + case CChoreoEvent::LOOP: + { + strcpy( params.m_szDialogTitle, "Add Loop Point" ); + } + break; + case CChoreoEvent::STOPPOINT: + { + strcpy( params.m_szDialogTitle, "Add Fire Completion" ); + } + break; + } + strcpy( params.m_szName, "" ); + strcpy( params.m_szAction, "" ); + + params.m_flStartTime = GetTimeValueForMouse( pt.x ); + + if ( !GlobalEventProperties( ¶ms ) ) + return; + + if ( strlen( params.m_szName ) <= 0 ) + { + Con_Printf( "Pause section event must have a valid name\n" ); + return; + } + + if ( strlen( params.m_szAction ) <= 0 ) + { + Con_Printf( "No action specified for section pause\n" ); + return; + } + + char undotext[ 256 ]; + undotext[0]=0; + switch( type ) + { + default: + Assert( 0 ); + break; + case CChoreoEvent::SECTION: + { + Q_strcpy( undotext, "Add Section Pause" ); + } + break; + case CChoreoEvent::LOOP: + { + Q_strcpy( undotext, "Add Loop Point" ); + } + break; + case CChoreoEvent::STOPPOINT: + { + Q_strcpy( undotext, "Add Fire Completion" ); + } + break; + } + + SetDirty( true ); + + PushUndo( undotext ); + + CChoreoEvent *event = m_pScene->AllocEvent(); + if ( event ) + { + event->SetType( type ); + event->SetName( params.m_szName ); + event->SetParameters( params.m_szAction ); + event->SetStartTime( params.m_flStartTime ); + event->SetEndTime( -1.0f ); + + switch ( type ) + { + default: + break; + case CChoreoEvent::LOOP: + { + event->SetLoopCount( params.m_nLoopCount ); + event->SetParameters( va( "%f", params.m_flLoopTime ) ); + } + break; + } + + event->SnapTimes(); + + DeleteSceneWidgets(); + + CreateSceneWidgets(); + + // Redraw + InvalidateLayout(); + } + + PushRedo( undotext ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void CChoreoView::EditGlobalEvent( CChoreoEvent *event ) +{ + if ( !event ) + return; + + CGlobalEventParams params; + memset( ¶ms, 0, sizeof( params ) ); + + params.m_nType = event->GetType(); + + switch ( event->GetType() ) + { + default: + Assert( 0 ); + strcpy( params.m_szDialogTitle, "???" ); + break; + case CChoreoEvent::SECTION: + { + strcpy( params.m_szDialogTitle, "Edit Pause Point" ); + V_strcpy_safe( params.m_szAction, event->GetParameters() ); + } + break; + case CChoreoEvent::LOOP: + { + strcpy( params.m_szDialogTitle, "Edit Loop Point" ); + strcpy( params.m_szAction, "" ); + params.m_flLoopTime = (float)atof( event->GetParameters() ); + params.m_nLoopCount = event->GetLoopCount(); + } + break; + case CChoreoEvent::STOPPOINT: + { + strcpy( params.m_szDialogTitle, "Edit Fire Completion" ); + strcpy( params.m_szAction, "" ); + } + break; + } + + strcpy( params.m_szName, event->GetName() ); + + params.m_flStartTime = event->GetStartTime(); + + if ( !GlobalEventProperties( ¶ms ) ) + return; + + if ( strlen( params.m_szName ) <= 0 ) + { + Con_Printf( "Event %s must have a valid name\n", event->GetName() ); + return; + } + + if ( strlen( params.m_szAction ) <= 0 ) + { + Con_Printf( "No action specified for %s\n", event->GetName() ); + return; + } + + SetDirty( true ); + + char undotext[ 256 ]; + undotext[0]=0; + switch( event->GetType() ) + { + default: + Assert( 0 ); + break; + case CChoreoEvent::SECTION: + { + Q_strcpy( undotext, "Edit Section Pause" ); + } + break; + case CChoreoEvent::LOOP: + { + Q_strcpy( undotext, "Edit Loop Point" ); + } + break; + case CChoreoEvent::STOPPOINT: + { + Q_strcpy( undotext, "Edit Fire Completion" ); + } + break; + } + + PushUndo( undotext ); + + event->SetName( params.m_szName ); + event->SetStartTime( params.m_flStartTime ); + event->SetEndTime( -1.0f ); + + switch ( event->GetType() ) + { + default: + { + event->SetParameters( params.m_szAction ); + } + break; + case CChoreoEvent::LOOP: + { + event->SetLoopCount( params.m_nLoopCount ); + event->SetParameters( va( "%f", params.m_flLoopTime ) ); + } + break; + } + + event->SnapTimes(); + + PushRedo( undotext ); + + // Redraw + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void CChoreoView::DeleteGlobalEvent( CChoreoEvent *event ) +{ + if ( !event || !m_pScene ) + return; + + SetDirty( true ); + + + char undotext[ 256 ]; + undotext[0]=0; + switch( event->GetType() ) + { + default: + Assert( 0 ); + break; + case CChoreoEvent::SECTION: + { + Q_strcpy( undotext, "Delete Section Pause" ); + } + break; + case CChoreoEvent::LOOP: + { + Q_strcpy( undotext, "Delete Loop Point" ); + } + break; + case CChoreoEvent::STOPPOINT: + { + Q_strcpy( undotext, "Delete Fire Completion" ); + } + break; + } + + PushUndo( undotext ); + + DeleteSceneWidgets(); + + Con_Printf( "Deleting %s\n", event->GetName() ); + + m_pScene->DeleteReferencedObjects( event ); + + CreateSceneWidgets(); + + PushRedo( undotext ); + + // Redraw + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void CChoreoView::EditEvent( CChoreoEvent *event ) +{ + if ( !event ) + return; + + CEventParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Edit Event" ); + + // Copy in current even properties + params.m_nType = event->GetType(); + params.m_bDisabled = !event->GetActive(); + + switch ( params.m_nType ) + { + case CChoreoEvent::EXPRESSION: + case CChoreoEvent::SEQUENCE: + case CChoreoEvent::MOVETO: + case CChoreoEvent::SPEAK: + case CChoreoEvent::GESTURE: + case CChoreoEvent::INTERRUPT: + case CChoreoEvent::PERMIT_RESPONSES: + case CChoreoEvent::GENERIC: + V_strcpy_safe( params.m_szParameters3, event->GetParameters3() ); + V_strcpy_safe( params.m_szParameters2, event->GetParameters2() ); + V_strcpy_safe( params.m_szParameters, event->GetParameters() ); + V_strcpy_safe( params.m_szName, event->GetName() ); + break; + case CChoreoEvent::FACE: + case CChoreoEvent::LOOKAT: + case CChoreoEvent::FIRETRIGGER: + case CChoreoEvent::FLEXANIMATION: + case CChoreoEvent::SUBSCENE: + V_strcpy_safe( params.m_szParameters, event->GetParameters() ); + V_strcpy_safe( params.m_szName, event->GetName() ); + + if ( params.m_nType == CChoreoEvent::LOOKAT || params.m_nType == CChoreoEvent::FACE ) + { + if ( event->GetPitch() != 0 || + event->GetYaw() != 0 ) + { + params.usepitchyaw = true; + params.pitch = event->GetPitch(); + params.yaw = event->GetYaw(); + } + } + break; + default: + Con_Printf( "Don't know how to edit event type %s\n", + CChoreoEvent::NameForType( (CChoreoEvent::EVENTTYPE)params.m_nType ) ); + return; + } + + params.m_pScene = m_pScene; + params.m_pEvent = event; + params.m_flStartTime = event->GetStartTime(); + params.m_flEndTime = event->GetEndTime(); + params.m_bHasEndTime = event->HasEndTime(); + + params.m_bFixedLength = event->IsFixedLength(); + params.m_bResumeCondition = event->IsResumeCondition(); + params.m_bLockBodyFacing = event->IsLockBodyFacing(); + params.m_flDistanceToTarget = event->GetDistanceToTarget(); + params.m_bForceShortMovement = event->GetForceShortMovement(); + params.m_bSyncToFollowingGesture = event->GetSyncToFollowingGesture(); + params.m_bPlayOverScript = event->GetPlayOverScript(); + params.m_bUsesTag = event->IsUsingRelativeTag(); + params.m_bCloseCaptionNoAttenuate = event->IsSuppressingCaptionAttenuation(); + + if ( params.m_bUsesTag ) + { + V_strcpy_safe( params.m_szTagName, event->GetRelativeTagName() ); + V_strcpy_safe( params.m_szTagWav, event->GetRelativeWavName() ); + } + + while (1) + { + SetScrubTargetTime( m_flScrub ); + FinishSimulation(); + sound->Flush(); + + m_bForceProcess = true; + if (!EventProperties( ¶ms )) + { + m_bForceProcess = false; + return; + } + m_bForceProcess = false; + + if ( Q_strlen( params.m_szName ) <= 0 ) + { + mxMessageBox( this, va( "Event %s must have a valid name", event->GetName() ), + "Edit Event", MX_MB_OK | MX_MB_ERROR ); + continue; + } + + if ( Q_strlen( params.m_szParameters ) <= 0 ) + { + bool shouldBreak = false; + + switch ( params.m_nType ) + { + case CChoreoEvent::FLEXANIMATION: + case CChoreoEvent::INTERRUPT: + case CChoreoEvent::PERMIT_RESPONSES: + shouldBreak = true; + break; + case CChoreoEvent::GESTURE: + if ( !Q_stricmp( params.m_szName, "NULL" ) ) + { + shouldBreak = true; + } + break; + default: + // Have to have a non-null parameters block + break; + } + + if ( !shouldBreak ) + { + mxMessageBox( this, va( "No parameters specified for %s\n", params.m_szName ), + "Edit Event", MX_MB_OK | MX_MB_ERROR ); + continue; + } + } + + break; + } + + + SetDirty( true ); + + PushUndo( "Edit Event" ); + + event->SetName( params.m_szName ); + event->SetParameters( params.m_szParameters ); + event->SetParameters2( params.m_szParameters2 ); + event->SetParameters3( params.m_szParameters3 ); + event->SetStartTime( params.m_flStartTime ); + event->SetResumeCondition( params.m_bResumeCondition ); + event->SetLockBodyFacing( params.m_bLockBodyFacing ); + event->SetDistanceToTarget( params.m_flDistanceToTarget ); + event->SetForceShortMovement( params.m_bForceShortMovement ); + event->SetSyncToFollowingGesture( params.m_bSyncToFollowingGesture ); + event->SetActive( !params.m_bDisabled ); + event->SetPlayOverScript( params.m_bPlayOverScript ); + if ( params.m_bUsesTag ) + { + event->SetUsingRelativeTag( true, params.m_szTagName, params.m_szTagWav ); + } + else + { + event->SetUsingRelativeTag( false ); + } + + if ( params.m_bHasEndTime && + params.m_flEndTime != -1.0 && + params.m_flEndTime > params.m_flStartTime ) + { + float dt = params.m_flEndTime - event->GetEndTime(); + float newduration = event->GetDuration() + dt; + RescaleRamp( event, newduration ); + switch ( event->GetType() ) + { + default: + break; + case CChoreoEvent::GESTURE: + { + event->RescaleGestureTimes( event->GetStartTime(), event->GetEndTime() + dt, true ); + } + break; + case CChoreoEvent::FLEXANIMATION: + { + RescaleExpressionTimes( event, event->GetStartTime(), event->GetEndTime() + dt ); + } + break; + } + event->SetEndTime( params.m_flEndTime ); + event->SnapTimes(); + event->ResortRamp(); + } + else + { + event->SetEndTime( -1.0f ); + } + + switch ( event->GetType() ) + { + default: + break; + case CChoreoEvent::SPEAK: + { + // Try and load wav to get length + CAudioSource *wave = sound->LoadSound( va( "sound/%s", FacePoser_TranslateSoundName( event ) ) ); + if ( wave ) + { + event->SetEndTime( params.m_flStartTime + wave->GetRunningLength() ); + delete wave; + } + + event->SetSuppressingCaptionAttenuation( params.m_bCloseCaptionNoAttenuate ); + } + break; + case CChoreoEvent::SUBSCENE: + { + // Just grab end time + CChoreoScene *scene = LoadScene( event->GetParameters() ); + if ( scene ) + { + event->SetEndTime( params.m_flStartTime + scene->FindStopTime() ); + } + delete scene; + } + break; + case CChoreoEvent::SEQUENCE: + { + CheckSequenceLength( event, false ); + } + break; + case CChoreoEvent::GESTURE: + { + CheckGestureLength( event, false ); + AutoaddGestureKeys( event, false ); + g_pGestureTool->redraw(); + } + break; + case CChoreoEvent::LOOKAT: + case CChoreoEvent::FACE: + { + if ( params.usepitchyaw ) + { + event->SetPitch( params.pitch ); + event->SetYaw( params.yaw ); + } + else + { + event->SetPitch( 0 ); + event->SetYaw( 0 ); + } + } + break; + } + + event->SnapTimes(); + + PushRedo( "Edit Event" ); + + // Redraw + InvalidateLayout(); +} + +void CChoreoView::EnableSelectedEvents( bool state ) +{ + if ( !m_pScene ) + return; + + SetDirty( true ); + + // If we right clicked on an unseleced event, then select it for the user. + if ( CountSelectedEvents() == 0 ) + { + CChoreoEventWidget *event = m_pClickedEvent; + if ( event ) + { + event->SetSelected( true ); + } + } + + char const *desc = state ? "Enable Events" : "Disable Events"; + + PushUndo( desc ); + + // Find the appropriate event by iterating across all actors and channels + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *a = m_SceneActors[ i ]; + if ( !a ) + continue; + + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannelWidget *channel = a->GetChannel( j ); + if ( !channel ) + continue; + + for ( int k = channel->GetNumEvents() - 1; k >= 0; k-- ) + { + CChoreoEventWidget *event = channel->GetEvent( k ); + if ( !event->IsSelected() ) + continue; + + event->GetEvent()->SetActive( state ); + } + } + } + + for ( int i = 0; i < m_SceneGlobalEvents.Size(); i++ ) + { + CChoreoGlobalEventWidget *event = m_SceneGlobalEvents[ i ]; + if ( !event || !event->IsSelected() ) + continue; + + event->GetEvent()->SetActive( state ); + } + + PushRedo( desc ); + + // Redraw + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void CChoreoView::DeleteSelectedEvents( void ) +{ + if ( !m_pScene ) + return; + + SetDirty( true ); + + PushUndo( "Delete Events" ); + + int deleteCount = 0; + + float oldstoptime = m_pScene->FindStopTime(); + + // Find the appropriate event by iterating across all actors and channels + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *a = m_SceneActors[ i ]; + if ( !a ) + continue; + + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannelWidget *channel = a->GetChannel( j ); + if ( !channel ) + continue; + + for ( int k = channel->GetNumEvents() - 1; k >= 0; k-- ) + { + CChoreoEventWidget *event = channel->GetEvent( k ); + if ( !event->IsSelected() ) + continue; + + channel->GetChannel()->RemoveEvent( event->GetEvent() ); + m_pScene->DeleteReferencedObjects( event->GetEvent() ); + + deleteCount++; + } + } + } + + for ( int i = 0; i < m_SceneGlobalEvents.Size(); i++ ) + { + CChoreoGlobalEventWidget *event = m_SceneGlobalEvents[ i ]; + if ( !event || !event->IsSelected() ) + continue; + + m_pScene->DeleteReferencedObjects( event->GetEvent() ); + + deleteCount++; + } + + DeleteSceneWidgets(); + + ReportSceneClearToTools(); + + CreateSceneWidgets(); + + PushRedo( "Delete Events" ); + + Con_Printf( "Deleted <%i> events\n", deleteCount ); + + if ( m_pScene->FindStopTime() != oldstoptime ) + { + // Force scroll bars to recompute + ForceScrollBarsToRecompute( false ); + + } + // Redraw + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : resetthumb - +//----------------------------------------------------------------------------- +void CChoreoView::ForceScrollBarsToRecompute( bool resetthumb ) +{ + if ( resetthumb ) + { + m_flLeftOffset = 0.0f; + } + m_nLastHPixelsNeeded = -1; + m_nLastVPixelsNeeded = -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *filename - +// Output : CChoreoScene +//----------------------------------------------------------------------------- +CChoreoScene *CChoreoView::LoadScene( char const *filename ) +{ + // If relative path, then make a full path + char pFullPathBuf[ MAX_PATH ]; + if ( !Q_IsAbsolutePath( filename ) ) + { + filesystem->RelativePathToFullPath( filename, "GAME", pFullPathBuf, sizeof( pFullPathBuf ) ); + filename = pFullPathBuf; + } + + if ( !filesystem->FileExists( filename ) ) + return NULL; + + LoadScriptFile( const_cast<char*>( filename ) ); + + CChoreoScene *scene = ChoreoLoadScene( filename, this, tokenprocessor, Con_Printf ); + return scene; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CChoreoView::FixupSequenceDurations( CChoreoScene *scene, bool checkonly ) +{ + bool bret = false; + if ( !scene ) + return bret; + + int c = scene->GetNumEvents(); + for ( int i = 0; i < c; i++ ) + { + CChoreoEvent *event = scene->GetEvent( i ); + if ( !event ) + continue; + + switch ( event->GetType() ) + { + default: + break; + case CChoreoEvent::SPEAK: + { + // Try and load wav to get length + CAudioSource *wave = sound->LoadSound( va( "sound/%s", FacePoser_TranslateSoundName( event ) ) ); + if ( wave ) + { + float endtime = event->GetStartTime() + wave->GetRunningLength(); + if ( event->GetEndTime() != endtime ) + { + event->SetEndTime( event->GetStartTime() + wave->GetRunningLength() ); + bret = true; + } + delete wave; + } + } + break; + case CChoreoEvent::SEQUENCE: + { + if ( CheckSequenceLength( event, checkonly ) ) + { + bret = true; + } + } + break; + case CChoreoEvent::GESTURE: + { + if ( CheckGestureLength( event, checkonly ) ) + { + bret = true; + } + if ( AutoaddGestureKeys( event, checkonly ) ) + { + bret = true; + } + } + break; + } + } + + return bret; +} + +//----------------------------------------------------------------------------- +// Purpose: IChoreoEventCallback +// Input : currenttime - +// *event - +//----------------------------------------------------------------------------- +void CChoreoView::StartEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) +{ + if ( !event || !event->GetActive() ) + return; + + CChoreoActor *actor = event->GetActor(); + if ( actor && !actor->GetActive() ) + { + return; + } + + CChoreoChannel *channel = event->GetChannel(); + if ( channel && !channel->GetActive() ) + { + return; + } + + // Con_Printf( "%8.4f: start %s\n", currenttime, event->GetDescription() ); + + switch ( event->GetType() ) + { + case CChoreoEvent::SEQUENCE: + { + ProcessSequence( scene, event ); + } + break; + case CChoreoEvent::SUBSCENE: + { + if ( !scene->IsSubScene() ) + { + CChoreoScene *subscene = event->GetSubScene(); + if ( !subscene ) + { + subscene = LoadScene( event->GetParameters() ); + subscene->SetSubScene( true ); + event->SetSubScene( subscene ); + } + + if ( subscene ) + { + subscene->ResetSimulation( m_bForward ); + } + } + } + break; + case CChoreoEvent::SECTION: + { + ProcessPause( scene, event ); + } + break; + case CChoreoEvent::SPEAK: + { + if ( ShouldProcessSpeak() ) + { + // See if we should trigger CC + char soundname[ 512 ]; + Q_strncpy( soundname, event->GetParameters(), sizeof( soundname ) ); + + float actualEndTime = event->GetEndTime(); + + if ( event->GetCloseCaptionType() == CChoreoEvent::CC_MASTER ) + { + char tok[ CChoreoEvent::MAX_CCTOKEN_STRING ]; + if ( event->GetPlaybackCloseCaptionToken( tok, sizeof( tok ) ) ) + { + float duration = max( event->GetDuration(), event->GetLastSlaveEndTime() - event->GetStartTime() ); + + closecaptionmanager->Process( tok, duration, GetCloseCaptionLanguageId() ); + + // Use the token as the sound name lookup, too. + if ( event->IsUsingCombinedFile() && + ( event->GetNumSlaves() > 0 ) ) + { + Q_strncpy( soundname, tok, sizeof( soundname ) ); + actualEndTime = max( actualEndTime, event->GetLastSlaveEndTime() ); + } + } + } + + StudioModel *model = FindAssociatedModel( scene, event->GetActor() ); + + CAudioMixer *mixer = event->GetMixer(); + if ( !mixer || !sound->IsSoundPlaying( mixer ) ) + { + CSoundParameters params; + + float volume = VOL_NORM; + gender_t gender = GENDER_NONE; + if (model) + { + gender = soundemitter->GetActorGender( model->GetFileName() ); + } + + if ( !Q_stristr( soundname, ".wav" ) && + soundemitter->GetParametersForSound( soundname, params, gender ) ) + { + volume = params.volume; + } + + sound->PlaySound( + model, + volume, + va( "sound/%s", FacePoser_TranslateSoundName( soundname, model ) ), + &mixer ); + event->SetMixer( mixer ); + } + + if ( mixer ) + { + mixer->SetDirection( m_flFrameTime >= 0.0f ); + float starttime, endtime; + starttime = event->GetStartTime(); + endtime = actualEndTime; + + float soundtime = endtime - starttime; + if ( soundtime > 0.0f ) + { + float f = ( currenttime - starttime ) / soundtime; + f = clamp( f, 0.0f, 1.0f ); + + // Compute sample + float numsamples = (float)mixer->GetSource()->SampleCount(); + + int cursample = f * numsamples; + cursample = clamp( cursample, 0, numsamples - 1 ); + mixer->SetSamplePosition( cursample ); + mixer->SetActive( true ); + } + } + } + } + break; + case CChoreoEvent::EXPRESSION: + { + } + break; + case CChoreoEvent::LOOP: + { + ProcessLoop( scene, event ); + } + break; + case CChoreoEvent::STOPPOINT: + { + // Nothing, this is a symbolic event for keeping the vcd alive for ramping out after the last true event + } + break; + default: + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : currenttime - +// *event - +//----------------------------------------------------------------------------- +void CChoreoView::EndEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) +{ + if ( !event || !event->GetActive() ) + return; + + CChoreoActor *actor = event->GetActor(); + if ( actor && !actor->GetActive() ) + { + return; + } + + CChoreoChannel *channel = event->GetChannel(); + if ( channel && !channel->GetActive() ) + { + return; + } + + switch ( event->GetType() ) + { + case CChoreoEvent::SUBSCENE: + { + CChoreoScene *subscene = event->GetSubScene(); + if ( subscene ) + { + subscene->ResetSimulation(); + } + } + break; + case CChoreoEvent::SPEAK: + { + CAudioMixer *mixer = event->GetMixer(); + if ( mixer && sound->IsSoundPlaying( mixer ) ) + { + sound->StopSound( mixer ); + } + event->SetMixer( NULL ); + } + break; + default: + break; + } + +// Con_Printf( "%8.4f: finish %s\n", currenttime, event->GetDescription() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +// mx - +// my - +//----------------------------------------------------------------------------- +int CChoreoView::GetTagUnderCursorPos( CChoreoEventWidget *event, int mx, int my ) +{ + if ( !event ) + { + return -1; + } + + for ( int i = 0; i < event->GetEvent()->GetNumRelativeTags(); i++ ) + { + CEventRelativeTag *tag = event->GetEvent()->GetRelativeTag( i ); + if ( !tag ) + continue; + + // Determine left edcge + RECT bounds; + bounds = event->getBounds(); + int left = bounds.left + (int)( tag->GetPercentage() * (float)( bounds.right - bounds.left ) + 0.5f ); + + int tolerance = 3; + + if ( abs( mx - left ) < tolerance ) + { + if ( abs( my - bounds.top ) < tolerance ) + { + return i; + } + } + } + + return -1; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +// mx - +// my - +//----------------------------------------------------------------------------- +CEventAbsoluteTag *CChoreoView::GetAbsoluteTagUnderCursorPos( CChoreoEventWidget *event, int mx, int my ) +{ + if ( !event ) + { + return NULL; + } + + for ( int i = 0; i < event->GetEvent()->GetNumAbsoluteTags( CChoreoEvent::PLAYBACK ); i++ ) + { + CEventAbsoluteTag *tag = event->GetEvent()->GetAbsoluteTag( CChoreoEvent::PLAYBACK, i ); + if ( !tag ) + continue; + + // Determine left edcge + RECT bounds; + bounds = event->getBounds(); + int left = bounds.left + (int)( tag->GetPercentage() * (float)( bounds.right - bounds.left ) + 0.5f ); + + int tolerance = 3; + + if ( abs( mx - left ) < tolerance ) + { + if ( abs( my - bounds.top ) < tolerance ) + { + return tag; + } + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +// **actor - +// **channel - +// **event - +//----------------------------------------------------------------------------- +void CChoreoView::GetObjectsUnderMouse( int mx, int my, CChoreoActorWidget **actor, + CChoreoChannelWidget **channel, CChoreoEventWidget **event, CChoreoGlobalEventWidget **globalevent, + int *clickedTag, + CEventAbsoluteTag **absolutetag, int *clickedCCArea ) +{ + if ( actor ) + { + *actor = GetActorUnderCursorPos( mx, my ); + } + if ( channel ) + { + *channel = GetChannelUnderCursorPos( mx, my ); + if ( *channel && clickedCCArea ) + { + *clickedCCArea = (*channel)->GetChannelItemUnderMouse( mx, my ); + } + } + if ( event ) + { + *event = GetEventUnderCursorPos( mx, my ); + } + if ( globalevent ) + { + *globalevent = GetGlobalEventUnderCursorPos( mx, my ); + } + if ( clickedTag ) + { + if ( event && *event ) + { + *clickedTag = GetTagUnderCursorPos( *event, mx, my ); + } + else + { + *clickedTag = -1; + } + } + if ( absolutetag ) + { + if ( event && *event ) + { + *absolutetag = GetAbsoluteTagUnderCursorPos( *event, mx, my ); + } + else + { + *absolutetag = NULL; + } + } + + + m_nSelectedEvents = CountSelectedEvents(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +// Output : CChoreoGlobalEventWidget +//----------------------------------------------------------------------------- +CChoreoGlobalEventWidget *CChoreoView::GetGlobalEventUnderCursorPos( int mx, int my ) +{ + POINT check; + check.x = mx; + check.y = my; + + CChoreoGlobalEventWidget *event; + for ( int i = 0; i < m_SceneGlobalEvents.Size(); i++ ) + { + event = m_SceneGlobalEvents[ i ]; + if ( !event ) + continue; + + RECT bounds; + event->getBounds( bounds ); + + if ( PtInRect( &bounds, check ) ) + { + return event; + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Caller must first translate mouse into screen coordinates +// Input : mx - +// my - +//----------------------------------------------------------------------------- +CChoreoActorWidget *CChoreoView::GetActorUnderCursorPos( int mx, int my ) +{ + POINT check; + check.x = mx; + check.y = my; + + CChoreoActorWidget *actor; + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + actor = m_SceneActors[ i ]; + if ( !actor ) + continue; + + RECT bounds; + actor->getBounds( bounds ); + + if ( PtInRect( &bounds, check ) ) + { + return actor; + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Caller must first translate mouse into screen coordinates +// Input : mx - +// my - +// Output : CChoreoChannelWidget +//----------------------------------------------------------------------------- +CChoreoChannelWidget *CChoreoView::GetChannelUnderCursorPos( int mx, int my ) +{ + CChoreoActorWidget *actor = GetActorUnderCursorPos( mx, my ); + if ( !actor ) + { + return NULL; + } + + POINT check; + check.x = mx; + check.y = my; + + CChoreoChannelWidget *channel; + for ( int i = 0; i < actor->GetNumChannels(); i++ ) + { + channel = actor->GetChannel( i ); + if ( !channel ) + continue; + + RECT bounds; + channel->getBounds( bounds ); + + if ( PtInRect( &bounds, check ) ) + { + return channel; + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Caller must first translate mouse into screen coordinates +// Input : mx - +// my - +//----------------------------------------------------------------------------- +CChoreoEventWidget *CChoreoView::GetEventUnderCursorPos( int mx, int my ) +{ + CChoreoChannelWidget *channel = GetChannelUnderCursorPos( mx, my ); + if ( !channel ) + { + return NULL; + } + + POINT check; + check.x = mx; + check.y = my; + + if ( mx < GetLabelWidth() ) + return NULL; + + if ( my < GetStartRow() ) + return NULL; + + if ( my >= h2() - ( m_nInfoHeight + m_nScrollbarHeight ) ) + return NULL; + + CChoreoEventWidget *event; + for ( int i = 0; i < channel->GetNumEvents(); i++ ) + { + event = channel->GetEvent( i ); + if ( !event ) + continue; + + RECT bounds; + event->getBounds( bounds ); + + // Events get an expanded border + InflateRect( &bounds, 8, 4 ); + + if ( PtInRect( &bounds, check ) ) + { + return event; + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Select wave file for phoneme editing +// Input : *filename - +//----------------------------------------------------------------------------- +void CChoreoView::SetCurrentWaveFile( const char *filename, CChoreoEvent *event ) +{ + g_pPhonemeEditor->SetCurrentWaveFile( filename, false, event ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pfn - +// *param1 - +//----------------------------------------------------------------------------- +void CChoreoView::TraverseWidgets( CVMEMBERFUNC pfn, CChoreoWidget *param1 ) +{ + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *actor = m_SceneActors[ i ]; + if ( !actor ) + continue; + + (this->*pfn)( actor, param1 ); + + for ( int j = 0; j < actor->GetNumChannels(); j++ ) + { + CChoreoChannelWidget *channel = actor->GetChannel( j ); + if ( !channel ) + continue; + + (this->*pfn)( channel, param1 ); + + for ( int k = 0; k < channel->GetNumEvents(); k++ ) + { + CChoreoEventWidget *event = channel->GetEvent( k ); + if ( !event ) + continue; + + (this->*pfn)( event, param1 ); + } + } + } + + for ( int i = 0; i < m_SceneGlobalEvents.Size(); i++ ) + { + CChoreoGlobalEventWidget *event = m_SceneGlobalEvents[ i ]; + if ( !event ) + continue; + + (this->*pfn)( event, param1 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *widget - +// *param1 - +//----------------------------------------------------------------------------- +void CChoreoView::Deselect( CChoreoWidget *widget, CChoreoWidget *param1 ) +{ + if ( widget->IsSelected() ) + { + widget->SetSelected( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *widget - +// *param1 - +//----------------------------------------------------------------------------- +void CChoreoView::Select( CChoreoWidget *widget, CChoreoWidget *param1 ) +{ + if ( widget != param1 ) + return; + + if ( widget->IsSelected() ) + return; + + widget->SetSelected( true ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *widget - +// *param1 - +//----------------------------------------------------------------------------- +void CChoreoView::SelectAllEvents( CChoreoWidget *widget, CChoreoWidget *param1 ) +{ + CChoreoEventWidget *ew = dynamic_cast< CChoreoEventWidget * >( widget ); + CChoreoGlobalEventWidget *gew = dynamic_cast< CChoreoGlobalEventWidget * >( widget ); + + if ( ew || gew ) + { + if ( widget->IsSelected() ) + return; + + widget->SetSelected( true ); + } +} + +bool CChoreoView::CreateAnimationEvent( int mx, int my, char const *animationname ) +{ + if ( !animationname || !animationname[0] ) + return false; + + // Convert screen to client + POINT pt; + pt.x = mx; + pt.y = my; + + ScreenToClient( (HWND)getHandle(), &pt ); + + if ( pt.x < 0 || pt.y < 0 ) + return false; + + if ( pt.x > w2() || pt.y > h2() ) + return false; + + pt.x -= GetLabelWidth(); + m_nClickedX = pt.x; + + GetObjectsUnderMouse( pt.x, pt.y, &m_pClickedActor, &m_pClickedChannel, &m_pClickedEvent, &m_pClickedGlobalEvent, &m_nClickedTag, &m_pClickedAbsoluteTag, &m_nClickedChannelCloseCaptionButton ); + + // Find channel actor and time ( uses screen space coordinates ) + // + CChoreoChannelWidget *channel = GetChannelUnderCursorPos( pt.x, pt.y ); + if ( !channel ) + { + CChoreoActorWidget *actor = GetActorUnderCursorPos( pt.x, pt.y ); + if ( !actor ) + return false; + + // Grab first channel + if ( !actor->GetNumChannels() ) + return false; + + channel = actor->GetChannel( 0 ); + } + + if ( !channel ) + return false; + + CChoreoChannel *pchannel = channel->GetChannel(); + if ( !pchannel ) + { + Assert( 0 ); + return false; + } + + // At this point we need to ask the user what type of even to create (gesture or sequence) and just show the approprite dialog + CChoiceParams params; + strcpy( params.m_szDialogTitle, "Create Animation Event" ); + + params.m_bPositionDialog = false; + params.m_nLeft = 0; + params.m_nTop = 0; + strcpy( params.m_szPrompt, "Type of event:" ); + + params.m_Choices.RemoveAll(); + + params.m_nSelected = 0; + ChoiceText text; + strcpy( text.choice, "gesture" ); + params.m_Choices.AddToTail( text ); + strcpy( text.choice, "sequence" ); + params.m_Choices.AddToTail( text ); + + if ( !ChoiceProperties( ¶ms ) ) + return false; + + if ( params.m_nSelected < 0 ) + return false; + + switch ( params.m_nSelected ) + { + default: + case 0: + AddEvent( CChoreoEvent::GESTURE, 0, animationname ); + break; + case 1: + AddEvent( CChoreoEvent::SEQUENCE, 0, animationname ); + break; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +// *cl - +// *exp - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoView::CreateExpressionEvent( int mx, int my, CExpClass *cl, CExpression *exp ) +{ + if ( !m_pScene ) + return false; + + if ( !exp ) + return false; + + // Convert screen to client + POINT pt; + pt.x = mx; + pt.y = my; + + ScreenToClient( (HWND)getHandle(), &pt ); + + if ( pt.x < 0 || pt.y < 0 ) + return false; + + if ( pt.x > w2() || pt.y > h2() ) + return false; + + // Find channel actor and time ( uses screen space coordinates ) + // + CChoreoChannelWidget *channel = GetChannelUnderCursorPos( pt.x, pt.y ); + if ( !channel ) + { + CChoreoActorWidget *actor = GetActorUnderCursorPos( pt.x, pt.y ); + if ( !actor ) + return false; + + // Grab first channel + if ( !actor->GetNumChannels() ) + return false; + + channel = actor->GetChannel( 0 ); + } + + if ( !channel ) + return false; + + CChoreoChannel *pchannel = channel->GetChannel(); + if ( !pchannel ) + { + Assert( 0 ); + return false; + } + + CChoreoEvent *event = m_pScene->AllocEvent(); + if ( !event ) + { + Assert( 0 ); + return false; + } + + float starttime = GetTimeValueForMouse( pt.x, false ); + + SetDirty( true ); + + PushUndo( "Create Expression" ); + + event->SetType( CChoreoEvent::EXPRESSION ); + event->SetName( exp->name ); + event->SetParameters( cl->GetName() ); + event->SetParameters2( exp->name ); + event->SetStartTime( starttime ); + event->SetChannel( pchannel ); + event->SetActor( pchannel->GetActor() ); + event->SetEndTime( starttime + 1.0f ); + + event->SnapTimes(); + + DeleteSceneWidgets(); + + // Add to appropriate channel + pchannel->AddEvent( event ); + + CreateSceneWidgets(); + + PushRedo( "Create Expression" ); + + // Redraw + InvalidateLayout(); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoView::IsPlayingScene( void ) +{ + return m_bSimulating; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::ResetTargetSettings( void ) +{ + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *w = m_SceneActors[ i ]; + if ( w ) + { + w->ResetSettings(); + } + } + + models->ClearModelTargets( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: copies the actors "settings" into the models FlexControllers +// Input : dt - +//----------------------------------------------------------------------------- +void CChoreoView::UpdateCurrentSettings( void ) +{ + StudioModel *defaultModel = models->GetActiveStudioModel(); + + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *w = m_SceneActors[ i ]; + if ( !w ) + continue; + + if ( !w->GetActor()->GetActive() ) + continue; + + StudioModel *model = FindAssociatedModel( m_pScene, w->GetActor() ); + if ( !model ) + continue; + + CStudioHdr *hdr = model->GetStudioHdr(); + if ( !hdr ) + continue; + + float *current = w->GetSettings(); + + for ( LocalFlexController_t j = LocalFlexController_t(0); j < hdr->numflexcontrollers(); j++ ) + { + int k = hdr->pFlexcontroller( j )->localToGlobal; + + if (k != -1) + { + if ( defaultModel == model && g_pFlexPanel->IsEdited( k ) ) + { + model->SetFlexController( j, g_pFlexPanel->GetSlider( k ) ); + } + else + { + model->SetFlexController( j, current[ k ] ); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +// tagnum - +//----------------------------------------------------------------------------- +void CChoreoView::DeleteEventRelativeTag( CChoreoEvent *event, int tagnum ) +{ + if ( !event ) + return; + + CEventRelativeTag *tag = event->GetRelativeTag( tagnum ); + if ( !tag ) + return; + + SetDirty( true ); + + PushUndo( "Delete Event Tag" ); + + event->RemoveRelativeTag( tag->GetName() ); + + m_pScene->ReconcileTags(); + + PushRedo( "Delete Event Tag" ); + + g_pPhonemeEditor->redraw(); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void CChoreoView::AddEventRelativeTag( void ) +{ + CChoreoEventWidget *ew = m_pClickedEvent; + if ( !ew ) + return; + + CChoreoEvent *event = ew->GetEvent(); + if ( !event->GetEndTime() ) + { + Con_ErrorPrintf( "Event Tag: Can only tag events with an end time\n" ); + return; + } + + CInputParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Event Tag Name" ); + strcpy( params.m_szPrompt, "Name:" ); + + strcpy( params.m_szInputText, "" ); + + if ( !InputProperties( ¶ms ) ) + return; + + if ( strlen( params.m_szInputText ) <= 0 ) + { + Con_ErrorPrintf( "Event Tag Name: No name entered!\n" ); + return; + } + + RECT bounds = ew->getBounds(); + + // Convert click to frac + float frac = 0.0f; + if ( bounds.right - bounds.left > 0 ) + { + frac = (float)( m_nClickedX - bounds.left ) / (float)( bounds.right - bounds.left ); + frac = min( 1.0f, frac ); + frac = max( 0.0f, frac ); + } + + SetDirty( true ); + + PushUndo( "Add Event Tag" ); + + event->AddRelativeTag( params.m_szInputText, frac ); + + PushRedo( "Add Event Tag" ); + + InvalidateLayout(); + g_pPhonemeEditor->redraw(); + g_pExpressionTool->redraw(); + g_pGestureTool->redraw(); + g_pRampTool->redraw(); + g_pSceneRampTool->redraw(); +} + +CChoreoChannelWidget *CChoreoView::FindChannelForEvent( CChoreoEvent *event ) +{ + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *a = m_SceneActors[ i ]; + if ( !a ) + continue; + + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannelWidget *c = a->GetChannel( j ); + if ( !c ) + continue; + + for ( int k = 0; k < c->GetNumEvents(); k++ ) + { + CChoreoEventWidget *e = c->GetEvent( k ); + if ( !e ) + continue; + + if ( e->GetEvent() != event ) + continue; + + return c; + } + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +// Output : CChoreoEventWidget +//----------------------------------------------------------------------------- +CChoreoEventWidget *CChoreoView::FindWidgetForEvent( CChoreoEvent *event ) +{ + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *a = m_SceneActors[ i ]; + if ( !a ) + continue; + + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannelWidget *c = a->GetChannel( j ); + if ( !c ) + continue; + + for ( int k = 0; k < c->GetNumEvents(); k++ ) + { + CChoreoEventWidget *e = c->GetEvent( k ); + if ( !e ) + continue; + + if ( e->GetEvent() != event ) + continue; + + return e; + } + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::SelectAll( void ) +{ + TraverseWidgets( &CChoreoView::SelectAllEvents, NULL ); + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::DeselectAll( void ) +{ + TraverseWidgets( &CChoreoView::Deselect, NULL ); + redraw(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +//----------------------------------------------------------------------------- +void CChoreoView::UpdateStatusArea( int mx, int my ) +{ + FLYOVER fo; + + GetObjectsUnderMouse( mx, my, &fo.a, &fo.c, + &fo.e, &fo.ge, &fo.tag, &fo.at, &fo.ccbutton ); + + if ( fo.a ) + { + m_Flyover.a = fo.a; + } + if ( fo.e ) + { + m_Flyover.e = fo.e; + } + if ( fo.c ) + { + m_Flyover.c = fo.c; + } + if ( fo.ge ) + { + m_Flyover.ge = fo.ge; + } + if ( fo.tag != -1 ) + { + m_Flyover.tag = fo.tag; + } + if ( fo.ccbutton != -1 ) + { + m_Flyover.ccbutton = fo.ccbutton; + // m_Flyover.e = NULL; + } + + RECT rcClip; + GetClientRect( (HWND)getHandle(), &rcClip ); + rcClip.bottom -= m_nScrollbarHeight; + rcClip.top = rcClip.bottom - m_nInfoHeight; + rcClip.right -= m_nScrollbarHeight; + + CChoreoWidgetDrawHelper drawHelper( this, rcClip, COLOR_CHOREO_BACKGROUND ); + + drawHelper.StartClipping( rcClip ); + + RedrawStatusArea( drawHelper, rcClip ); + + drawHelper.StopClipping(); + ValidateRect( (HWND)getHandle(), &rcClip ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::ClearStatusArea( void ) +{ + memset( &m_Flyover, 0, sizeof( m_Flyover ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rcStatus - +//----------------------------------------------------------------------------- +void CChoreoView::RedrawStatusArea( CChoreoWidgetDrawHelper& drawHelper, RECT& rcStatus ) +{ + drawHelper.DrawFilledRect( COLOR_CHOREO_BACKGROUND, rcStatus ); + + drawHelper.DrawColoredLine( COLOR_INFO_BORDER, PS_SOLID, 1, rcStatus.left, rcStatus.top, + rcStatus.right, rcStatus.top ); + + RECT rcInfo = rcStatus; + + rcInfo.top += 2; + + if ( m_Flyover.e ) + { + m_Flyover.e->redrawStatus( drawHelper, rcInfo ); + } + if ( m_Flyover.c && + m_Flyover.ccbutton != -1 ) + { + m_Flyover.c->redrawStatus( drawHelper, rcInfo, m_Flyover.ccbutton ); + } + + if ( m_pScene ) + { + char sz[ 512 ]; + + int fontsize = 9; + int fontweight = FW_NORMAL; + + RECT rcText; + rcText = rcInfo; + rcText.bottom = rcText.top + fontsize + 2; + + char const *mapname = m_pScene->GetMapname(); + if ( mapname ) + { + sprintf( sz, "Associated .bsp: %s", mapname[ 0 ] ? mapname : "none" ); + + int len = drawHelper.CalcTextWidth( "Arial", fontsize, fontweight, sz ); + rcText.left = rcText.right - len - 10; + + drawHelper.DrawColoredText( "Arial", fontsize, fontweight, COLOR_INFO_TEXT, rcText, sz ); + + OffsetRect( &rcText, 0, fontsize + 2 ); + } + + sprintf( sz, "Scene: %s", GetChoreoFile() ); + + int len = drawHelper.CalcTextWidth( "Arial", fontsize, fontweight, sz ); + rcText.left = rcText.right - len - 10; + + drawHelper.DrawColoredText( "Arial", fontsize, fontweight, COLOR_INFO_TEXT, rcText, sz ); + } + +// drawHelper.DrawColoredText( "Arial", 12, 500, RGB( 0, 0, 0 ), rcInfo, m_Flyover.e ? m_Flyover.e->GetEvent()->GetName() : "" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void CChoreoView::MoveEventToBack( CChoreoEvent *event ) +{ + // Now find channel widget + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *a = m_SceneActors[ i ]; + if ( !a ) + continue; + + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannelWidget *c = a->GetChannel( j ); + if ( !c ) + continue; + + for ( int k = 0; k < c->GetNumEvents(); k++ ) + { + CChoreoEventWidget *e = c->GetEvent( k ); + if ( !e ) + continue; + + if ( event == e->GetEvent() ) + { + // Move it to back of channel's list + c->MoveEventToTail( e ); + InvalidateLayout(); + return; + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoView::GetEndRow( void ) +{ + RECT rcClient; + GetClientRect( (HWND)getHandle(), &rcClient ); + + return rcClient.bottom - ( m_nInfoHeight + m_nScrollbarHeight ); +} + +// Undo/Redo +void CChoreoView::Undo( void ) +{ + if ( m_UndoStack.Size() > 0 && m_nUndoLevel > 0 ) + { + m_nUndoLevel--; + CVUndo *u = m_UndoStack[ m_nUndoLevel ]; + Assert( u->undo ); + + DeleteSceneWidgets(); + + *m_pScene = *(u->undo); + g_MDLViewer->InitGridSettings(); + + CreateSceneWidgets(); + + ReportSceneClearToTools(); + ClearStatusArea(); + m_pClickedActor = NULL; + m_pClickedChannel = NULL; + m_pClickedEvent = NULL; + m_pClickedGlobalEvent = NULL; + } + + InvalidateLayout(); +} + +void CChoreoView::Redo( void ) +{ + if ( m_UndoStack.Size() > 0 && m_nUndoLevel <= m_UndoStack.Size() - 1 ) + { + CVUndo *u = m_UndoStack[ m_nUndoLevel ]; + Assert( u->redo ); + + DeleteSceneWidgets(); + + *m_pScene = *(u->redo); + g_MDLViewer->InitGridSettings(); + + CreateSceneWidgets(); + + ReportSceneClearToTools(); + ClearStatusArea(); + m_pClickedActor = NULL; + m_pClickedChannel = NULL; + m_pClickedEvent = NULL; + m_pClickedGlobalEvent = NULL; + + m_nUndoLevel++; + } + + InvalidateLayout(); +} + +static char *CopyString( const char *in ) +{ + int len = strlen( in ); + char *n = new char[ len + 1 ]; + strcpy( n, in ); + return n; +} + +void CChoreoView::PushUndo( const char *description ) +{ + Assert( !m_bRedoPending ); + m_bRedoPending = true; + WipeRedo(); + + // Copy current data + CChoreoScene *u = new CChoreoScene( this ); + *u = *m_pScene; + CVUndo *undo = new CVUndo; + undo->undo = u; + undo->redo = NULL; + undo->udescription = CopyString( description ); + undo->rdescription = NULL; + m_UndoStack.AddToTail( undo ); + m_nUndoLevel++; +} + +void CChoreoView::PushRedo( const char *description ) +{ + Assert( m_bRedoPending ); + m_bRedoPending = false; + + // Copy current data + CChoreoScene *r = new CChoreoScene( this ); + *r = *m_pScene; + CVUndo *undo = m_UndoStack[ m_nUndoLevel - 1 ]; + undo->redo = r; + undo->rdescription = CopyString( description ); + + // Always redo here to reflect that someone has made a change + redraw(); +} + +void CChoreoView::WipeUndo( void ) +{ + while ( m_UndoStack.Size() > 0 ) + { + CVUndo *u = m_UndoStack[ 0 ]; + delete u->undo; + delete u->redo; + delete[] u->udescription; + delete[] u->rdescription; + delete u; + m_UndoStack.Remove( 0 ); + } + m_nUndoLevel = 0; +} + +void CChoreoView::WipeRedo( void ) +{ + // Wipe everything above level + while ( m_UndoStack.Size() > m_nUndoLevel ) + { + CVUndo *u = m_UndoStack[ m_nUndoLevel ]; + delete u->undo; + delete u->redo; + delete[] u->udescription; + delete[] u->rdescription; + delete u; + m_UndoStack.Remove( m_nUndoLevel ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CChoreoView::GetUndoDescription( void ) +{ + if ( CanUndo() ) + { + CVUndo *u = m_UndoStack[ m_nUndoLevel - 1 ]; + return u->udescription; + } + return "???undo"; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CChoreoView::GetRedoDescription( void ) +{ + if ( CanRedo() ) + { + CVUndo *u = m_UndoStack[ m_nUndoLevel ]; + return u->rdescription; + } + return "???redo"; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoView::CanUndo() +{ + return m_nUndoLevel != 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoView::CanRedo() +{ + return m_nUndoLevel != m_UndoStack.Size(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::OnGestureTool( void ) +{ + if ( m_pClickedEvent->GetEvent()->GetType() != CChoreoEvent::GESTURE ) + return; + + g_pGestureTool->SetEvent( m_pClickedEvent->GetEvent() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChoreoView::OnExpressionTool( void ) +{ + if ( m_pClickedEvent->GetEvent()->GetType() != CChoreoEvent::FLEXANIMATION ) + return; + + g_pExpressionTool->SetEvent( m_pClickedEvent->GetEvent() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CChoreoScene +//----------------------------------------------------------------------------- +CChoreoScene *CChoreoView::GetScene( void ) +{ + return m_pScene; +} + +bool CChoreoView::CanPaste( void ) +{ + char const *copyfile = COPYPASTE_FILENAME; + + if ( !filesystem->FileExists( copyfile ) ) + { + return false; + } + + return true; +} + +void CChoreoView::CopyEvents( void ) +{ + if ( !m_pScene ) + return; + + char const *copyfile = COPYPASTE_FILENAME; + MakeFileWriteable( copyfile ); + ExportVCDFile( copyfile ); +} + +void CChoreoView::PasteEvents( void ) +{ + if ( !m_pScene ) + return; + + if ( !CanPaste() ) + return; + + char const *copyfile = COPYPASTE_FILENAME; + + ImportVCDFile( copyfile ); +} + +void CChoreoView::ImportEvents( void ) +{ + if ( !m_pScene ) + return; + + if ( !m_pClickedActor || !m_pClickedChannel ) + return; + + char eventfile[ 512 ]; + if ( !FacePoser_ShowOpenFileNameDialog( eventfile, sizeof( eventfile ), "scenes", "*.vce" ) ) + return; + + char fullpathbuf[ 512 ]; + char *fullpath = eventfile; + if ( !Q_IsAbsolutePath( eventfile ) ) + { + filesystem->RelativePathToFullPath( eventfile, "GAME", fullpathbuf, sizeof( fullpathbuf ) ); + fullpath = fullpathbuf; + } + + if ( !filesystem->FileExists( fullpath ) ) + return; + + LoadScriptFile( fullpath ); + + DeselectAll(); + + SetDirty( true ); + + PushUndo( "Import Events" ); + + m_pScene->ImportEvents( tokenprocessor, m_pClickedActor->GetActor(), m_pClickedChannel->GetChannel() ); + + PushRedo( "Import Events" ); + + CreateSceneWidgets(); + // Redraw + InvalidateLayout(); + + Con_Printf( "Imported events from %s\n", fullpath ); +} + +void CChoreoView::ExportEvents( void ) +{ + char eventfilename[ 512 ]; + if ( !FacePoser_ShowSaveFileNameDialog( eventfilename, sizeof( eventfilename ), "scenes", "*.vce" ) ) + return; + + Q_DefaultExtension( eventfilename, ".vce", sizeof( eventfilename ) ); + + Con_Printf( "Exporting events to %s\n", eventfilename ); + + // Write to file + CUtlVector< CChoreoEvent * > events; + + // Find selected eventss + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *a = m_SceneActors[ i ]; + if ( !a ) + continue; + + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannelWidget *c = a->GetChannel( j ); + if ( !c ) + continue; + + for ( int k = 0; k < c->GetNumEvents(); k++ ) + { + CChoreoEventWidget *e = c->GetEvent( k ); + if ( !e ) + continue; + + if ( !e->IsSelected() ) + continue; + + CChoreoEvent *event = e->GetEvent(); + if ( !event ) + continue; + + events.AddToTail( event ); + } + } + } + + if ( events.Size() > 0 ) + { + m_pScene->ExportEvents( eventfilename, events ); + } + else + { + Con_Printf( "No events selected\n" ); + } +} + +void CChoreoView::ExportVCDFile( char const *filename ) +{ + Con_Printf( "Exporting to %s\n", filename ); + + // Unmark everything + m_pScene->MarkForSaveAll( false ); + + // Mark everything related to selected events + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *a = m_SceneActors[ i ]; + if ( !a ) + continue; + + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannelWidget *c = a->GetChannel( j ); + if ( !c ) + continue; + + for ( int k = 0; k < c->GetNumEvents(); k++ ) + { + CChoreoEventWidget *e = c->GetEvent( k ); + if ( !e ) + continue; + + if ( !e->IsSelected() ) + continue; + + CChoreoEvent *event = e->GetEvent(); + if ( !event ) + continue; + + event->SetMarkedForSave( true ); + if ( event->GetChannel() ) + { + event->GetChannel()->SetMarkedForSave( true ); + } + if ( event->GetActor() ) + { + event->GetActor()->SetMarkedForSave( true ); + } + } + } + } + + m_pScene->ExportMarkedToFile( filename ); +} + +void CChoreoView::ImportVCDFile( char const *filename ) +{ + CChoreoScene *merge = LoadScene( filename ); + if ( !merge ) + { + Con_Printf( "Couldn't load from .vcd %s\n", filename ); + return; + } + + DeselectAll(); + + CUtlRBTree< CChoreoEvent *, int > oldEvents( 0, 0, DefLessFunc( CChoreoEvent * ) ); + + int i; + for ( i = 0; i < m_SceneActors.Count(); ++i ) + { + CChoreoActorWidget *actor = m_SceneActors[ i ]; + if ( !actor ) + continue; + + for ( int j = 0; j < actor->GetNumChannels(); j++ ) + { + CChoreoChannelWidget *channel = actor->GetChannel( j ); + if ( !channel ) + continue; + + for ( int k = 0; k < channel->GetNumEvents(); k++ ) + { + CChoreoEventWidget *event = channel->GetEvent( k ); + if ( !event ) + continue; + + oldEvents.Insert( event->GetEvent() ); + } + } + } + + SetDirty( true ); + + PushUndo( "Merge/Import VCD" ); + + m_pScene->Merge( merge ); + + PushRedo( "Merge/Import VCD" ); + + DeleteSceneWidgets(); + CreateSceneWidgets(); + + // Force scroll bars to recompute + ForceScrollBarsToRecompute( false ); + + // Now walk through the "new" events and select everything that wasn't already there (all of the stuff that was "added" during the merge) + for ( i = 0; i < m_SceneActors.Count(); ++i ) + { + CChoreoActorWidget *actor = m_SceneActors[ i ]; + if ( !actor ) + continue; + + for ( int j = 0; j < actor->GetNumChannels(); j++ ) + { + CChoreoChannelWidget *channel = actor->GetChannel( j ); + if ( !channel ) + continue; + + for ( int k = 0; k < channel->GetNumEvents(); k++ ) + { + CChoreoEventWidget *event = channel->GetEvent( k ); + if ( !event ) + continue; + + if ( oldEvents.Find( event->GetEvent() ) == oldEvents.InvalidIndex() ) + { + event->SetSelected( true ); + } + } + } + } + + // Redraw + InvalidateLayout(); + + Con_Printf( "Imported vcd '%s'\n", filename ); + + delete merge; + + redraw(); +} + +void CChoreoView::ExportVCD() +{ + char scenefile[ 512 ]; + if ( !FacePoser_ShowSaveFileNameDialog( scenefile, sizeof( scenefile ), "scenes", "*.vcd" ) ) + { + return; + } + + Q_DefaultExtension( scenefile, ".vcd", sizeof( scenefile ) ); + + ExportVCDFile( scenefile ); +} + +void CChoreoView::ImportVCD() +{ + if ( !m_pScene ) + return; + + if ( !m_pClickedActor || !m_pClickedChannel ) + return; + + char scenefile[ 512 ]; + if ( !FacePoser_ShowOpenFileNameDialog( scenefile, sizeof( scenefile ), "scenes", "*.vcd" ) ) + { + return; + } + + ImportVCDFile( scenefile ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoView::IsProcessing( void ) +{ + if ( !m_pScene ) + return false; + + if ( m_flScrub != m_flScrubTarget ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoView::ShouldProcessSpeak( void ) +{ + if ( !g_pControlPanel->AllToolsDriveSpeech() ) + { + if ( !IsActiveTool() ) + return false; + } + + if ( IFacePoserToolWindow::IsAnyToolScrubbing() ) + return true; + + if ( IFacePoserToolWindow::IsAnyToolProcessing() ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *scene - +// *event - +//----------------------------------------------------------------------------- +void CChoreoView::ProcessSpeak( CChoreoScene *scene, CChoreoEvent *event ) +{ + if ( !ShouldProcessSpeak() ) + return; + + Assert( event->GetType() == CChoreoEvent::SPEAK ); + Assert( scene ); + + float t = scene->GetTime(); + + StudioModel *model = FindAssociatedModel( scene, event->GetActor() ); + + // See if we should trigger CC + char soundname[ 512 ]; + Q_strncpy( soundname, event->GetParameters(), sizeof( soundname ) ); + + float actualEndTime = event->GetEndTime(); + + if ( event->GetCloseCaptionType() == CChoreoEvent::CC_MASTER ) + { + char tok[ CChoreoEvent::MAX_CCTOKEN_STRING ]; + if ( event->GetPlaybackCloseCaptionToken( tok, sizeof( tok ) ) ) + { + // Use the token as the sound name lookup, too. + if ( event->IsUsingCombinedFile() && + ( event->GetNumSlaves() > 0 ) ) + { + Q_strncpy( soundname, tok, sizeof( soundname ) ); + actualEndTime = max( actualEndTime, event->GetLastSlaveEndTime() ); + } + } + } + + CAudioMixer *mixer = event->GetMixer(); + if ( !mixer || !sound->IsSoundPlaying( mixer ) ) + { + CSoundParameters params; + float volume = VOL_NORM; + + gender_t gender = GENDER_NONE; + if (model) + { + gender = soundemitter->GetActorGender( model->GetFileName() ); + } + + if ( !Q_stristr( soundname, ".wav" ) && + soundemitter->GetParametersForSound( soundname, params, gender ) ) + { + volume = params.volume; + } + + sound->PlaySound( + model, + volume, + va( "sound/%s", FacePoser_TranslateSoundName( soundname, model ) ), + &mixer ); + event->SetMixer( mixer ); + } + + mixer = event->GetMixer(); + if ( !mixer ) + return; + + mixer->SetDirection( m_flFrameTime >= 0.0f ); + float starttime, endtime; + starttime = event->GetStartTime(); + endtime = actualEndTime; + + float soundtime = endtime - starttime; + if ( soundtime <= 0.0f ) + return; + + float f = ( t - starttime ) / soundtime; + f = clamp( f, 0.0f, 1.0f ); + + // Compute sample + float numsamples = (float)mixer->GetSource()->SampleCount(); + + int cursample = f * numsamples; + cursample = clamp( cursample, 0, numsamples - 1 ); + + int realsample = mixer->GetSamplePosition(); + + int dsample = cursample - realsample; + + int samplelimit = mixer->GetSource()->SampleRate() * 0.02f; // don't shift until samples are off by this much + if (IsScrubbing()) + { + samplelimit = mixer->GetSource()->SampleRate() * 0.01f; // make it shorter tolerance when scrubbing + } + + if ( abs( dsample ) > samplelimit ) + { + mixer->SetSamplePosition( cursample, IsScrubbing() ); + } + mixer->SetActive( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +void CChoreoView::ProcessSubscene( CChoreoScene *scene, CChoreoEvent *event ) +{ + Assert( event->GetType() == CChoreoEvent::SUBSCENE ); + + CChoreoScene *subscene = event->GetSubScene(); + if ( !subscene ) + return; + + if ( subscene->SimulationFinished() ) + return; + + // Have subscenes think for appropriate time + subscene->Think( m_flScrub ); +} + +void CChoreoView::PositionControls() +{ + int topx = GetCaptionHeight() + SCRUBBER_HEIGHT; + + int bx = 2; + int bw = 16; + + m_btnPlay->setBounds( bx, topx + 4, 16, 16 ); + + bx += bw + 2; + + m_btnPause->setBounds( bx, topx + 4, 16, 16 ); + bx += bw + 2; + m_btnStop->setBounds( bx, topx + 4, 16, 16 ); + bx += bw + 2; + m_pPlaybackRate->setBounds( bx, topx + 4, 100, 16 ); +} + +void CChoreoView::SetChoreoFile( char const *filename ) +{ + strcpy( m_szChoreoFile, filename ); + if ( m_szChoreoFile[ 0 ] ) + { + char sz[ 256 ]; + if ( IsFileWriteable( m_szChoreoFile ) ) + { + Q_snprintf( sz, sizeof( sz ), " - %s", m_szChoreoFile ); + } + else + { + Q_snprintf( sz, sizeof( sz ), " - %s [Read-Only]", m_szChoreoFile ); + } + SetSuffix( sz ); + } + else + { + SetSuffix( "" ); + } +} + +char const *CChoreoView::GetChoreoFile( void ) const +{ + return m_szChoreoFile; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rcHandle - +//----------------------------------------------------------------------------- +void CChoreoView::GetScrubHandleRect( RECT& rcHandle, bool clipped ) +{ + float pixel = 0.0f; + + if ( m_pScene ) + { + float currenttime = m_flScrub; + float starttime = m_flStartTime; + float endtime = m_flEndTime; + + float screenfrac = ( currenttime - starttime ) / ( endtime - starttime ); + + pixel = GetLabelWidth() + screenfrac * ( w2() - GetLabelWidth() ); + + if ( clipped ) + { + pixel = clamp( pixel, SCRUBBER_HANDLE_WIDTH/2, w2() - SCRUBBER_HANDLE_WIDTH/2 ); + } + } + + rcHandle.left = pixel-SCRUBBER_HANDLE_WIDTH/2; + rcHandle.right = pixel + SCRUBBER_HANDLE_WIDTH/2; + rcHandle.top = 2 + GetCaptionHeight(); + rcHandle.bottom = rcHandle.top + SCRUBBER_HANDLE_HEIGHT; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rcArea - +//----------------------------------------------------------------------------- +void CChoreoView::GetScrubAreaRect( RECT& rcArea ) +{ + rcArea.left = 0; + rcArea.right = w2(); + rcArea.top = 2 + GetCaptionHeight(); + rcArea.bottom = rcArea.top + SCRUBBER_HEIGHT - 4; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rcHandle - +//----------------------------------------------------------------------------- +void CChoreoView::DrawScrubHandle( CChoreoWidgetDrawHelper& drawHelper ) +{ + RECT rcHandle; + GetScrubHandleRect( rcHandle, true ); + + HBRUSH br = CreateSolidBrush( RGB( 0, 150, 100 ) ); + + drawHelper.DrawFilledRect( br, rcHandle ); + + // + char sz[ 32 ]; + sprintf( sz, "%.3f", m_flScrub ); + + int len = drawHelper.CalcTextWidth( "Arial", 9, 500, sz ); + + RECT rcText = rcHandle; + int textw = rcText.right - rcText.left; + + rcText.left += ( textw - len ) / 2; + + drawHelper.DrawColoredText( "Arial", 9, 500, RGB( 255, 255, 255 ), rcText, sz ); + + DeleteObject( br ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoView::IsMouseOverScrubHandle( mxEvent *event ) +{ + RECT rcHandle; + GetScrubHandleRect( rcHandle, true ); + InflateRect( &rcHandle, 2, 2 ); + + POINT pt; + pt.x = (short)event->x; + pt.y = (short)event->y; + if ( PtInRect( &rcHandle, pt ) ) + { + return true; + } + return false; +} + +bool CChoreoView::IsMouseOverScrubArea( mxEvent *event ) +{ + RECT rcArea; + GetScrubAreaRect( rcArea ); + + InflateRect( &rcArea, 2, 2 ); + + POINT pt; + pt.x = (short)event->x; + pt.y = (short)event->y; + if ( PtInRect( &rcArea, pt ) ) + { + return true; + } + + return false; +} + +bool CChoreoView::IsScrubbing( void ) const +{ + bool scrubbing = ( m_nDragType == DRAGTYPE_SCRUBBER ) ? true : false; + return scrubbing; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dt - +//----------------------------------------------------------------------------- +void CChoreoView::Think( float dt ) +{ + bool scrubbing = IFacePoserToolWindow::IsAnyToolScrubbing(); + + ScrubThink( dt, scrubbing, this ); +} + +static int lastthinkframe = -1; +void CChoreoView::ScrubThink( float dt, bool scrubbing, IFacePoserToolWindow *invoker ) +{ + // Make sure we don't get called more than once per frame + int thisframe = g_MDLViewer->GetCurrentFrame(); + if ( thisframe == lastthinkframe ) + return; + + lastthinkframe = thisframe; + + if ( !m_pScene ) + return; + + if ( m_flScrubTarget == m_flScrub && !scrubbing ) + { + // Act like it's paused + if ( IFacePoserToolWindow::ShouldAutoProcess() ) + { + m_bSimulating = true; + SceneThink( m_flScrub ); + } + + if ( m_bSimulating && !m_bPaused ) + { + //FinishSimulation(); + } + return; + } + + // Make sure we're solving head turns during playback + models->SetSolveHeadTurn( 1 ); + + if ( m_bPaused ) + { + SceneThink( m_flScrub ); + return; + } + + // Make sure phonemes are loaded + FacePoser_EnsurePhonemesLoaded(); + + if ( !m_bSimulating ) + { + m_bSimulating = true; + } + + float d = m_flScrubTarget - m_flScrub; + int sign = d > 0.0f ? 1 : -1; + + float maxmove = dt * m_flPlaybackRate; + + float prevScrub = m_flScrub; + + if ( sign > 0 ) + { + if ( d < maxmove ) + { + m_flScrub = m_flScrubTarget; + } + else + { + m_flScrub += maxmove; + } + } + else + { + if ( -d < maxmove ) + { + m_flScrub = m_flScrubTarget; + } + else + { + m_flScrub -= maxmove; + } + } + + m_flFrameTime = ( m_flScrub - prevScrub ); + + SceneThink( m_flScrub ); + + DrawScrubHandle(); + + if ( scrubbing ) + { + g_pMatSysWindow->Frame(); + } + + if ( invoker != g_pExpressionTool ) + { + g_pExpressionTool->ForceScrubPositionFromSceneTime( m_flScrub ); + } + if ( invoker != g_pGestureTool ) + { + g_pGestureTool->ForceScrubPositionFromSceneTime( m_flScrub ); + } + if ( invoker != g_pRampTool ) + { + g_pRampTool->ForceScrubPositionFromSceneTime( m_flScrub ); + } + if ( invoker != g_pSceneRampTool ) + { + g_pSceneRampTool->ForceScrubPositionFromSceneTime( m_flScrub ); + } +} + +void CChoreoView::DrawScrubHandle( void ) +{ + if ( !m_bCanDraw ) + return; + + // Handle new time and + RECT rcArea; + GetScrubAreaRect( rcArea ); + + CChoreoWidgetDrawHelper drawHelper( this, rcArea, COLOR_CHOREO_BACKGROUND ); + DrawScrubHandle( drawHelper ); +} + +void CChoreoView::SetScrubTime( float t ) +{ + m_flScrub = t; + + m_bPaused = false; +} + +void CChoreoView::SetScrubTargetTime( float t ) +{ + m_flScrubTarget = t; + + m_bPaused = false; +} + +void CChoreoView::ClampTimeToSelectionInterval( float& timeval ) +{ + // FIXME hook this up later + return; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : show - +//----------------------------------------------------------------------------- +void CChoreoView::ShowButtons( bool show ) +{ + m_btnPlay->setVisible( show ); + m_btnPause->setVisible( show ); + m_btnStop->setVisible( show ); + m_pPlaybackRate->setVisible( show ); +} + +void CChoreoView::RememberSelectedEvents( CUtlVector< CChoreoEvent * >& list ) +{ + GetSelectedEvents( list ); +} + +void CChoreoView::ReselectEvents( CUtlVector< CChoreoEvent * >& list ) +{ + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *actor = m_SceneActors[ i ]; + if ( !actor ) + continue; + + for ( int j = 0; j < actor->GetNumChannels(); j++ ) + { + CChoreoChannelWidget *channel = actor->GetChannel( j ); + if ( !channel ) + continue; + + for ( int k = 0; k < channel->GetNumEvents(); k++ ) + { + CChoreoEventWidget *event = channel->GetEvent( k ); + if ( !event ) + continue; + + CChoreoEvent *check = event->GetEvent(); + if ( list.Find( check ) != list.InvalidIndex() ) + { + event->SetSelected( true ); + } + } + } + } + +} + +void CChoreoView::OnChangeScale( void ) +{ + CChoreoScene *scene = m_pScene; + if ( !scene ) + { + return; + } + + // Zoom time in / out + CInputParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Change Zoom" ); + strcpy( params.m_szPrompt, "New scale (e.g., 2.5x):" ); + + Q_snprintf( params.m_szInputText, sizeof( params.m_szInputText ), "%.2f", (float)GetTimeZoom( GetToolName() ) / 100.0f ); + + if ( !InputProperties( ¶ms ) ) + return; + + SetTimeZoom( GetToolName(), clamp( (int)( 100.0f * atof( params.m_szInputText ) ), 1, MAX_TIME_ZOOM ), false ); + + // Force scroll bars to recompute + ForceScrollBarsToRecompute( false ); + + CUtlVector< CChoreoEvent * > selected; + RememberSelectedEvents( selected ); + + DeleteSceneWidgets(); + CreateSceneWidgets(); + + ReselectEvents( selected ); + + InvalidateLayout(); + Con_Printf( "Zoom factor %i %%\n", GetTimeZoom( GetToolName() ) ); +} + +void CChoreoView::OnCheckSequenceLengths( void ) +{ + if ( !m_pScene ) + return; + + Con_Printf( "Checking sequence durations...\n" ); + + bool changed = FixupSequenceDurations( m_pScene, true ); + + if ( !changed ) + { + Con_Printf( " no changes...\n" ); + return; + } + + SetDirty( true ); + + PushUndo( "Check sequence lengths" ); + + FixupSequenceDurations( m_pScene, false ); + + PushRedo( "Check sequence lengths" ); + + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *scene - +//----------------------------------------------------------------------------- +void CChoreoView::InvalidateTrackLookup_R( CChoreoScene *scene ) +{ + // No need to undo since this data doesn't matter + int c = scene->GetNumEvents(); + for ( int i = 0; i < c; i++ ) + { + CChoreoEvent *event = scene->GetEvent( i ); + if ( !event ) + continue; + + switch ( event->GetType() ) + { + default: + break; + case CChoreoEvent::FLEXANIMATION: + { + event->SetTrackLookupSet( false ); + } + break; + case CChoreoEvent::SUBSCENE: + { + CChoreoScene *sub = event->GetSubScene(); + // NOTE: Don't bother loading it now if it's not on hand + if ( sub ) + { + InvalidateTrackLookup_R( sub ); + } + } + break; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Model changed so we'll have to re-index flex anim tracks +//----------------------------------------------------------------------------- +void CChoreoView::InvalidateTrackLookup( void ) +{ + if ( !m_pScene ) + return; + + InvalidateTrackLookup_R( m_pScene ); +} + + + +bool CChoreoView::IsRampOnly( void ) const +{ + return m_bRampOnly; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *scene - +// *event - +//----------------------------------------------------------------------------- +void CChoreoView::ProcessInterrupt( CChoreoScene *scene, CChoreoEvent *event ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *scene - +// *event - +//----------------------------------------------------------------------------- +void CChoreoView::ProcessPermitResponses( CChoreoScene *scene, CChoreoEvent *event ) +{ +} + +void CChoreoView::ApplyBounds( int& mx, int& my ) +{ + if ( !m_bUseBounds ) + return; + + mx = clamp( mx, m_nMinX, m_nMaxX ); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns -1 if no event found +// Input : *channel - +// *e - +// forward - +// Output : float +//----------------------------------------------------------------------------- +float CChoreoView::FindNextEventTime( CChoreoEvent::EVENTTYPE type, CChoreoChannel *channel, CChoreoEvent *e, bool forward ) +{ + bool foundone = false; + float bestTime = -1.0f; + float bestGap = 999999.0f; + + int c = channel->GetNumEvents(); + for ( int i = 0; i < c; i++ ) + { + CChoreoEvent *test = channel->GetEvent( i ); + if ( test->GetType() != type ) + continue; + + if ( forward ) + { + float dt = test->GetStartTime() - e->GetEndTime(); + if ( dt <= 0.0f ) + continue; + + if ( dt < bestGap ) + { + foundone = true; + bestGap = dt; + bestTime = test->GetStartTime(); + } + } + else + { + float dt = e->GetStartTime() - test->GetEndTime(); + if ( dt <= 0.0f ) + continue; + + if ( dt < bestGap ) + { + foundone = true; + bestGap = dt; + bestTime = test->GetEndTime(); + } + } + } + + return bestTime; +} + +void CChoreoView::CalcBounds( int movetype ) +{ + m_bUseBounds = false; + m_nMinX = 0; + m_nMaxX = 0; + + if ( !m_pClickedEvent ) + return; + + switch ( movetype ) + { + default: + break; + case DRAGTYPE_EVENT_MOVE: + case DRAGTYPE_EVENT_STARTTIME: + case DRAGTYPE_EVENT_ENDTIME: + case DRAGTYPE_EVENT_STARTTIME_RESCALE: + case DRAGTYPE_EVENT_ENDTIME_RESCALE: + { + m_nMinX = GetPixelForTimeValue( 0 ); + m_nMaxX = GetPixelForTimeValue( m_pScene->FindStopTime() ); + + CChoreoEvent *e = m_pClickedEvent->GetEvent(); + + m_bUseBounds = false; // e && e->GetType() == CChoreoEvent::GESTURE; + // FIXME: use this for finding adjacent gesture edges (kenb) + if ( m_bUseBounds ) + { + CChoreoChannel *channel = e->GetChannel(); + Assert( channel ); + + float forwardTime = FindNextEventTime( e->GetType(), channel, e, true ); + float reverseTime = FindNextEventTime( e->GetType(), channel, e, false ); + + // Compute pixel for time + int nextPixel = forwardTime != -1 ? GetPixelForTimeValue( forwardTime ) : m_nMaxX; + int prevPixel = reverseTime != -1 ? GetPixelForTimeValue( reverseTime ) : m_nMinX; + + int startPixel = GetPixelForTimeValue( e->GetStartTime() ); + int endPixel = GetPixelForTimeValue( e->GetEndTime() ); + + switch ( movetype ) + { + case DRAGTYPE_EVENT_MOVE: + { + m_nMinX = prevPixel + ( m_xStart - startPixel ) + 1; + m_nMaxX = nextPixel - ( endPixel - m_xStart ) - 1; + } + break; + case DRAGTYPE_EVENT_STARTTIME: + case DRAGTYPE_EVENT_STARTTIME_RESCALE: + { + m_nMinX = prevPixel + ( m_xStart - startPixel ) + 1; + } + break; + case DRAGTYPE_EVENT_ENDTIME: + case DRAGTYPE_EVENT_ENDTIME_RESCALE: + { + m_nMaxX = nextPixel - ( endPixel - m_xStart ) - 1; + } + break; + } + } + } + break; + } +} + +bool CChoreoView::ShouldSelectEvent( SelectionParams_t ¶ms, CChoreoEvent *event ) +{ + if ( params.forward ) + { + if ( event->GetStartTime() >= params.time ) + return true; + } + else + { + float endtime = event->HasEndTime() ? event->GetEndTime() : event->GetStartTime(); + + if ( endtime <= params.time ) + return true; + } + return false; +} + +void CChoreoView::SelectEvents( SelectionParams_t& params ) +{ + if ( !m_pScene ) + return; + + if ( !m_pClickedActor ) + return; + + if ( params.type == SelectionParams_t::SP_CHANNEL && !m_pClickedChannel ) + return; + + //CChoreoActor *actor = m_pClickedActor->GetActor(); + CChoreoChannel *channel = m_pClickedChannel ? m_pClickedChannel->GetChannel() : NULL; + + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *a = m_SceneActors[ i ]; + if ( !a ) + continue; + + //if ( a->GetActor() != actor ) + // continue; + + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannelWidget *c = a->GetChannel( j ); + if ( !c ) + continue; + + if ( params.type == SelectionParams_t::SP_CHANNEL && + c->GetChannel() != channel ) + continue; + + if ( params.type == SelectionParams_t::SP_ACTIVE && + !c->GetChannel()->GetActive() ) + continue; + + for ( int k = 0; k < c->GetNumEvents(); k++ ) + { + CChoreoEventWidget *e = c->GetEvent( k ); + if ( !e ) + continue; + + CChoreoEvent *event = e->GetEvent(); + if ( !event ) + continue; + + if ( !ShouldSelectEvent( params, event ) ) + continue; + + e->SetSelected( true ); + } + } + } + + // Now handle global events, too + for ( int i = 0; i < m_SceneGlobalEvents.Count(); i++ ) + { + CChoreoGlobalEventWidget *e = m_SceneGlobalEvents[ i ]; + if ( !e ) + continue; + + CChoreoEvent *event = e->GetEvent(); + if ( !event ) + continue; + + if ( !ShouldSelectEvent( params, event ) ) + continue; + + e->SetSelected( true ); + } + + redraw(); +} + +void CChoreoView::SetTimeZoom( char const *tool, int tz, bool preserveFocus ) +{ + if ( !m_pScene ) + return; + + // No change + int oldZoom = GetTimeZoom( tool ); + if ( tz == oldZoom ) + return; + + SetDirty( true ); + + POINT pt; + ::GetCursorPos( &pt ); + ::ScreenToClient( (HWND)getHandle(), &pt ); + + // Now figure out time under cursor at old zoom scale + float t = GetTimeValueForMouse( pt.x, true ); + + m_pScene->SetTimeZoom( tool, tz ); + + if ( preserveFocus ) + { + RECT rc; + GetClientRect( (HWND)getHandle(), &rc ); + RECT rcClient = rc; + rcClient.top += GetStartRow(); + OffsetRect( &rcClient, 0, -m_nTopOffset ); + m_flStartTime = m_flLeftOffset / GetPixelsPerSecond(); + m_flEndTime = m_flStartTime + (float)( rcClient.right - GetLabelWidth() ) / GetPixelsPerSecond(); + + // Now figure out tie under pt.x + float newT = GetTimeValueForMouse( pt.x, true ); + if ( newT != t ) + { + // We need to scroll over a bit + float pps = GetPixelsPerSecond(); + float movePixels = pps * ( newT - t ); + + m_flLeftOffset -= movePixels; + if ( m_flLeftOffset < 0.0f ) + { + //float fixup = - m_flLeftOffset; + m_flLeftOffset = 0; + } + + // float maxtime = m_pScene->FindStopTime(); + float flApparentEndTime = max( m_pScene->FindStopTime(), 5.0f ) + 5.0f; + if ( m_flEndTime > flApparentEndTime ) + { + movePixels = pps * ( m_flEndTime - flApparentEndTime ); + m_flLeftOffset = max( 0.0f, m_flLeftOffset - movePixels ); + } + } + } + + // Deal with the slider + RepositionHSlider(); + redraw(); +} + +int CChoreoView::GetTimeZoom( char const *tool ) +{ + if ( !m_pScene ) + return 100; + + return m_pScene->GetTimeZoom( tool ); +} + +void CChoreoView::CheckInsertTime( CChoreoEvent *e, float dt, float starttime, float endtime ) +{ + // Not influenced + float eventend = e->HasEndTime() ? e->GetEndTime() : e->GetStartTime(); + + if ( eventend < starttime ) + return; + + if ( e->GetStartTime() > starttime ) + { + e->OffsetTime( dt ); + e->SnapTimes(); + } + else if ( !e->IsFixedLength() && e->HasEndTime() ) // the event starts before start, but ends after start time, need to insert space into the event, act like user dragged end time + { + float newduration = e->GetDuration() + dt; + RescaleRamp( e, newduration ); + switch ( e->GetType() ) + { + default: + break; + case CChoreoEvent::GESTURE: + { + e->RescaleGestureTimes( e->GetStartTime(), e->GetEndTime() + dt, true ); + } + break; + case CChoreoEvent::FLEXANIMATION: + { + RescaleExpressionTimes( e, e->GetStartTime(), e->GetEndTime() + dt ); + } + break; + } + e->OffsetEndTime( dt ); + e->SnapTimes(); + e->ResortRamp(); + } + + switch ( e->GetType() ) + { + default: + break; + case CChoreoEvent::SPEAK: + { + // Try and load wav to get length + CAudioSource *wave = sound->LoadSound( va( "sound/%s", FacePoser_TranslateSoundName( e ) ) ); + if ( wave ) + { + e->SetEndTime( e->GetStartTime() + wave->GetRunningLength() ); + delete wave; + } + } + break; + case CChoreoEvent::SEQUENCE: + { + CheckSequenceLength( e, false ); + } + break; + case CChoreoEvent::GESTURE: + { + CheckGestureLength( e, false ); + } + break; + } +} + +void CChoreoView::OnInsertTime() +{ + if ( !m_rgABPoints[ 0 ].active && + !m_rgABPoints[ 1 ].active ) + { + return; + } + + Con_Printf( "OnInsertTime()\n" ); + + float starttime = m_rgABPoints[ 0 ].time; + float endtime = m_rgABPoints[ 1 ].time; + + // Sort samples correctly + if ( starttime > endtime ) + { + float temp = starttime; + starttime = endtime; + endtime = temp; + } + + float dt = endtime - starttime; + if ( dt == 0.0f ) + { + // Nothing to do... + return; + } + + SetDirty( true ); + + PushUndo( "Insert Time" ); + + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *actor = m_SceneActors[ i ]; + if ( !actor ) + continue; + + for ( int j = 0; j < actor->GetNumChannels(); j++ ) + { + CChoreoChannelWidget *channel = actor->GetChannel( j ); + if ( !channel ) + continue; + + for ( int k = 0; k < channel->GetNumEvents(); k++ ) + { + CChoreoEventWidget *event = channel->GetEvent( k ); + if ( !event ) + continue; + + CChoreoEvent *e = event->GetEvent(); + if ( !e ) + continue; + + CheckInsertTime( e, dt, starttime, endtime ); + } + } + } + + // Now handle global events, too + for ( int i = 0; i < m_SceneGlobalEvents.Count(); i++ ) + { + CChoreoGlobalEventWidget *event = m_SceneGlobalEvents[ i ]; + if ( !event ) + continue; + + CChoreoEvent *e = event->GetEvent(); + if ( !e ) + continue; + + CheckInsertTime( e, dt, starttime, endtime ); + } + + PushRedo( "Insert Time" ); + InvalidateLayout(); + + g_pExpressionTool->LayoutItems( true ); + g_pExpressionTool->redraw(); + g_pGestureTool->redraw(); + g_pRampTool->redraw(); + g_pSceneRampTool->redraw(); +} + +void CChoreoView::CheckDeleteTime( CChoreoEvent *e, float dt, float starttime, float endtime, bool& deleteEvent ) +{ + deleteEvent = false; + + // Not influenced + float eventend = e->HasEndTime() ? e->GetEndTime() : e->GetStartTime(); + + if ( eventend < starttime ) + { + return; + } + + // On right side of start mark, just shift left + if ( e->GetStartTime() > starttime ) + { + // If it has no duration and it's in the bounds then kill it. + if ( !e->HasEndTime() && e->GetStartTime() < endtime ) + { + deleteEvent = true; + return; + } + else + { + float shift = e->GetStartTime() - starttime; + float maxoffset = min( dt, shift ); + + e->OffsetTime( -maxoffset ); + e->SnapTimes(); + } + } + else if ( !e->IsFixedLength() && e->HasEndTime() ) // the event starts before start, but ends after start time, need to insert space into the event, act like user dragged end time + { + float shiftend = e->GetEndTime() - starttime; + float maxoffset = min( dt, shiftend ); + + float newduration = e->GetDuration() - maxoffset; + if ( newduration <= 0.0f ) + { + deleteEvent = true; + return; + } + else + { + RescaleRamp( e, newduration ); + switch ( e->GetType() ) + { + default: + break; + case CChoreoEvent::GESTURE: + { + e->RescaleGestureTimes( e->GetStartTime(), e->GetEndTime() - maxoffset, true ); + } + break; + case CChoreoEvent::FLEXANIMATION: + { + RescaleExpressionTimes( e, e->GetStartTime(), e->GetEndTime() - maxoffset ); + } + break; + } + e->OffsetEndTime( -maxoffset ); + e->SnapTimes(); + e->ResortRamp(); + } + } + + switch ( e->GetType() ) + { + default: + break; + case CChoreoEvent::SPEAK: + { + // Try and load wav to get length + CAudioSource *wave = sound->LoadSound( va( "sound/%s", FacePoser_TranslateSoundName( e ) ) ); + if ( wave ) + { + e->SetEndTime( e->GetStartTime() + wave->GetRunningLength() ); + delete wave; + } + } + break; + case CChoreoEvent::SEQUENCE: + { + CheckSequenceLength( e, false ); + } + break; + case CChoreoEvent::GESTURE: + { + CheckGestureLength( e, false ); + } + break; + } +} + +void CChoreoView::OnDeleteTime() +{ + if ( !m_rgABPoints[ 0 ].active && + !m_rgABPoints[ 1 ].active ) + { + return; + } + + Con_Printf( "OnDeleteTime()\n" ); + + float starttime = m_rgABPoints[ 0 ].time; + float endtime = m_rgABPoints[ 1 ].time; + + // Sort samples correctly + if ( starttime > endtime ) + { + float temp = starttime; + starttime = endtime; + endtime = temp; + } + + float dt = endtime - starttime; + if ( dt == 0.0f ) + { + // Nothing to do... + return; + } + + SetDirty( true ); + + PushUndo( "Delete Time" ); + + CUtlVector< CChoreoEventWidget * > deletions; + CUtlVector< CChoreoGlobalEventWidget * > global_deletions; + + for ( int i = 0; i < m_SceneActors.Size(); i++ ) + { + CChoreoActorWidget *actor = m_SceneActors[ i ]; + if ( !actor ) + continue; + + for ( int j = 0; j < actor->GetNumChannels(); j++ ) + { + CChoreoChannelWidget *channel = actor->GetChannel( j ); + if ( !channel ) + continue; + + for ( int k = 0; k < channel->GetNumEvents(); k++ ) + { + CChoreoEventWidget *event = channel->GetEvent( k ); + if ( !event ) + continue; + + CChoreoEvent *e = event->GetEvent(); + if ( !e ) + continue; + + bool deleteEvent = false; + + CheckDeleteTime( e, dt, starttime, endtime, deleteEvent ); + + if ( deleteEvent ) + { + deletions.AddToTail( event ); + } + } + } + } + + // Now handle global events, too + for ( int i = 0; i < m_SceneGlobalEvents.Count(); i++ ) + { + CChoreoGlobalEventWidget *event = m_SceneGlobalEvents[ i ]; + if ( !event ) + continue; + + CChoreoEvent *e = event->GetEvent(); + if ( !e ) + continue; + + bool deleteEvent = false; + CheckDeleteTime( e, dt, starttime, endtime, deleteEvent ); + + if ( deleteEvent ) + { + global_deletions.AddToTail( event ); + } + } + + for ( int i = 0; i < deletions.Count(); i++ ) + { + CChoreoEventWidget *w = deletions[ i ]; + + CChoreoEvent *e = w->GetEvent(); + CChoreoChannel *channel = e->GetChannel(); + if ( channel ) + { + channel->RemoveEvent( e ); + } + m_pScene->DeleteReferencedObjects( e ); + } + + for ( int i = 0; i < global_deletions.Count(); i++ ) + { + CChoreoGlobalEventWidget *w = global_deletions[ i ]; + CChoreoEvent *e = w->GetEvent(); + m_pScene->DeleteReferencedObjects( e ); + } + + // Force scroll bars to recompute + ForceScrollBarsToRecompute( false ); + + if ( deletions.Count() > 0 || global_deletions.Count() > 0 ) + { + DeleteSceneWidgets(); + CreateSceneWidgets(); + } + + PushRedo( "Delete Time" ); + + InvalidateLayout(); + + g_pExpressionTool->LayoutItems( true ); + g_pExpressionTool->redraw(); + g_pGestureTool->redraw(); + g_pRampTool->redraw(); + g_pSceneRampTool->redraw(); +} + +void CChoreoView::OnModelChanged() +{ + InvalidateTrackLookup(); + // OnCheckSequenceLengths(); +} + +void CChoreoView::SetShowCloseCaptionData( bool show ) +{ + m_bShowCloseCaptionData = show; +} + +bool CChoreoView::GetShowCloseCaptionData( void ) const +{ + return m_bShowCloseCaptionData; +} + +void CChoreoView::OnToggleCloseCaptionTags() +{ + m_bShowCloseCaptionData = !m_bShowCloseCaptionData; + InvalidateLayout(); +} + + + +static bool EventStartTimeLessFunc( CChoreoEvent * const &p1, CChoreoEvent * const &p2 ) +{ + CChoreoEvent *w1; + CChoreoEvent *w2; + + w1 = const_cast< CChoreoEvent * >( p1 ); + w2 = const_cast< CChoreoEvent * >( p2 ); + + return w1->GetStartTime() < w2->GetStartTime(); +} + +bool CChoreoView::GenerateCombinedFile( char const *outfilename, char const *cctoken, gender_t gender, CUtlRBTree< CChoreoEvent * >& sorted ) +{ + CUtlVector< CombinerEntry > work; + + char actualfile[ 512 ]; + soundemitter->GenderExpandString( gender, outfilename, actualfile, sizeof( actualfile ) ); + if ( Q_strlen( actualfile ) <= 0 ) + { + return false; + } + + int i = sorted.FirstInorder(); + if ( i != sorted.InvalidIndex() ) + { + CChoreoEvent *e = sorted[ i ]; + + float startoffset = e->GetStartTime(); + + do + { + e = sorted[ i ]; + + float curoffset = e->GetStartTime(); + + CombinerEntry ce; + Q_snprintf( ce.wavefile, sizeof( ce.wavefile ), "sound/%s", FacePoser_TranslateSoundNameGender( e->GetParameters(), gender ) ); + ce.startoffset = curoffset - startoffset; + + work.AddToTail( ce ); + + i = sorted.NextInorder( i ); + } + while ( i != sorted.InvalidIndex() ); + } + + bool ok = soundcombiner->CombineSoundFiles( filesystem, actualfile, work ); + if ( !ok ) + { + Con_ErrorPrintf( "Failed to create combined sound '%s':'%s'\n", cctoken, actualfile ); + return false; + } + Con_Printf( "Created combined sound '%s':'%s'\n", cctoken, actualfile ); + return true; +} + +bool CChoreoView::ValidateCombinedFileCheckSum( char const *outfilename, char const *cctoken, gender_t gender, CUtlRBTree< CChoreoEvent * >& sorted ) +{ + CUtlVector< CombinerEntry > work; + + char actualfile[ 512 ]; + soundemitter->GenderExpandString( gender, outfilename, actualfile, sizeof( actualfile ) ); + if ( Q_strlen( actualfile ) <= 0 ) + { + return false; + } + + int i = sorted.FirstInorder(); + if ( i != sorted.InvalidIndex() ) + { + CChoreoEvent *e = sorted[ i ]; + + float startoffset = e->GetStartTime(); + + do + { + e = sorted[ i ]; + + float curoffset = e->GetStartTime(); + + CombinerEntry ce; + Q_snprintf( ce.wavefile, sizeof( ce.wavefile ), "sound/%s", FacePoser_TranslateSoundNameGender( e->GetParameters(), gender ) ); + ce.startoffset = curoffset - startoffset; + + work.AddToTail( ce ); + + i = sorted.NextInorder( i ); + } + while ( i != sorted.InvalidIndex() ); + } + + return soundcombiner->IsCombinedFileChecksumValid( filesystem, actualfile, work ); +} + +void SuggestCaption( char *dest, int destlen, CUtlVector< CChoreoEvent * >& events ) +{ + // Walk through events and concatenate current captions, or raw wav data if have any + dest[ 0 ] = 0; + + int c = events.Count(); + for ( int i = 0 ; i < c; ++i ) + { + CChoreoEvent *e = events[ i ]; + + bool found = false; + char tok[ CChoreoEvent::MAX_CCTOKEN_STRING ]; + if ( e->GetPlaybackCloseCaptionToken( tok, sizeof( tok ) ) ) + { + wchar_t *localized = g_pLocalize->Find( tok ); + if ( localized ) + { + found = true; + + char ansi[ 1024 ]; + g_pLocalize->ConvertUnicodeToANSI( localized, ansi, sizeof( ansi ) ); + Q_strncat( dest, ansi, destlen, COPY_ALL_CHARACTERS ); + } + } + + if ( !found ) + { + // See if the wav file has data... + CAudioSource *wave = sound->LoadSound( va( "sound/%s", FacePoser_TranslateSoundName( e ) ) ); + if ( wave ) + { + CSentence *sentence = wave->GetSentence(); + if ( sentence ) + { + Q_strncat( dest, sentence->GetText(), destlen, COPY_ALL_CHARACTERS ); + found = true; + } + } + } + + if ( found && Q_strlen( dest ) > 0 && i != c - 1 ) + { + Q_strncat( dest, " ", destlen, COPY_ALL_CHARACTERS ); + } + } +} + +void CChoreoView::OnCombineSpeakEvents() +{ + if ( !m_pScene ) + return; + + CChoreoChannel *firstChannel = NULL; + + CUtlVector< CChoreoEvent * > selected; + GetSelectedEvents( selected ); + + int c = selected.Count(); + // Find the appropriate event by iterating across all actors and channels + for ( int i = c - 1; i >= 0; --i ) + { + CChoreoEvent *e = selected[ i ]; + + if ( e->GetType() != CChoreoEvent::SPEAK ) + { + Con_ErrorPrintf( "Can't combine events, all events must be SPEAK events.\n" ); + return; + } + + if ( !firstChannel ) + { + firstChannel = e->GetChannel(); + } + else if ( e->GetChannel() != firstChannel ) + { + Con_ErrorPrintf( "Can't combine events, all events must reside in the same channel.\n" ); + return; + } + } + + if ( selected.Count() < 2 ) + { + Con_ErrorPrintf( "Can't combine events, must have at least two events selected.\n" ); + return; + } + + // Let the user pick a CC phrase + CCloseCaptionLookupParams params; + Q_strncpy( params.m_szDialogTitle, "Choose Close Caption Token", sizeof( params.m_szDialogTitle ) ); + + params.m_bPositionDialog = false; + params.m_nLeft = 0; + params.m_nTop = 0; + + char playbacktoken[ CChoreoEvent::MAX_CCTOKEN_STRING ]; + if ( !selected[0]->GetPlaybackCloseCaptionToken( playbacktoken, sizeof( playbacktoken ) ) ) + { + return; + } + + if ( !Q_stristr( playbacktoken, "_cc" ) ) + { + Q_strncpy( params.m_szCCToken, va( "%s_cc", playbacktoken ), sizeof( params.m_szCCToken ) ); + } + else + { + Q_strncpy( params.m_szCCToken, va( "%s", playbacktoken ), sizeof( params.m_szCCToken ) ); + } + + // User hit okay and value actually changed? + if ( !CloseCaptionLookup( ¶ms ) && + params.m_szCCToken[0] != 0 ) + { + return; + } + + // See if the token exists? + StringIndex_t stringIndex = g_pLocalize->FindIndex( params.m_szCCToken ); + if ( INVALID_LOCALIZE_STRING_INDEX == stringIndex ) + { + // Add token to closecaption_english file. + // Guess at string and ask user to confirm. + CInputParams ip; + memset( &ip, 0, sizeof( ip ) ); + + Q_strncpy( ip.m_szDialogTitle, "Add Close Caption", sizeof( ip.m_szDialogTitle ) ); + Q_snprintf( ip.m_szPrompt, sizeof( ip.m_szPrompt ), "Token (%s):", params.m_szCCToken ); + + char suggested[ 2048 ]; + + SuggestCaption( suggested, sizeof( suggested ), selected ); + + Q_snprintf( ip.m_szInputText, sizeof( ip.m_szInputText ), "%s", suggested ); + + if ( !InputProperties( &ip ) ) + { + Con_Printf( "Combining of sound events cancelled\n" ); + return; + } + + if ( Q_strlen( ip.m_szInputText ) == 0 ) + { + Q_snprintf( ip.m_szInputText, sizeof( ip.m_szInputText ), "!!!%s", params.m_szCCToken ); + } + + char const *captionFile = "resource/closecaption_english.txt"; + + if ( !filesystem->IsFileWritable( captionFile, "GAME" ) ) + { + Warning( "Forcing %s to be writable!!!\n", captionFile ); + MakeFileWriteable( captionFile ); + } + + wchar_t unicode[ 2048 ]; + g_pLocalize->ConvertANSIToUnicode( ip.m_szInputText, unicode, sizeof( unicode ) ); + + g_pLocalize->AddString( params.m_szCCToken, unicode, captionFile ); + g_pLocalize->SaveToFile( captionFile ); + } + + SetDirty( true ); + + PushUndo( "Combine Sound Events" ); + + c = selected.Count(); + for ( int i = 0 ; i < c; ++i ) + { + selected[ i ]->SetCloseCaptionToken( params.m_szCCToken ); + } + + PushRedo( "Combine Sound Events" ); + + // Redraw + InvalidateLayout(); + + Con_Printf( "Changed %i events to use close caption token '%s'\n", c, params.m_szCCToken ); + + // Sort the sounds by start time + + CUtlRBTree< CChoreoEvent * > sorted( 0, 0, EventStartTimeLessFunc ); + + // Sort items + c = selected.Count(); + bool genderwildcard = false; + for ( int i = 0; i < c; i++ ) + { + CChoreoEvent *e = selected[ i ]; + sorted.Insert( e ); + + // Get the sound entry name and use it to look up the gender info + // Look up the sound level from the soundemitter system + if ( !genderwildcard ) + { + genderwildcard = soundemitter->IsUsingGenderToken( e->GetParameters() ); + } + } + + + char outfilename[ 512 ]; + Q_memset( outfilename, 0, sizeof( outfilename ) ); + + CChoreoEvent *e = sorted[ sorted.FirstInorder() ]; + + // Update whether we use the $gender token + e->SetCombinedUsingGenderToken( genderwildcard ); + + if ( !e->ComputeCombinedBaseFileName( outfilename, sizeof( outfilename ), genderwildcard ) ) + { + Con_ErrorPrintf( "Unable to regenerate wav file name for combined sound\n" ); + return; + } + + int soundindex = soundemitter->GetSoundIndex( e->GetParameters() ); + char const *scriptfile = soundemitter->GetSourceFileForSound( soundindex ); + if ( !scriptfile || !scriptfile[0] ) + { + Con_ErrorPrintf( "Unable to find existing script to use for new combined sound entry.\n" ); + return; + } + + // Create a new sound entry for this sound + CAddSoundParams asp; + Q_memset( &asp, 0, sizeof( asp ) ); + Q_strncpy( asp.m_szDialogTitle, "Add Combined Sound Entry", sizeof( asp.m_szDialogTitle ) ); + Q_strncpy( asp.m_szWaveFile, outfilename + Q_strlen( "sound/"), sizeof( asp.m_szWaveFile ) ); + Q_strncpy( asp.m_szScriptName, scriptfile, sizeof( asp.m_szScriptName ) ); + Q_strncpy( asp.m_szSoundName, params.m_szCCToken, sizeof( asp.m_szSoundName ) ); + + asp.m_bAllowExistingSound = true; + asp.m_bReadOnlySoundName = true; + + if ( !AddSound( &asp, (HWND)g_MDLViewer->getHandle() ) ) + { + return; + } + + if ( genderwildcard ) + { + GenerateCombinedFile( outfilename, params.m_szCCToken, GENDER_MALE, sorted ); + GenerateCombinedFile( outfilename, params.m_szCCToken, GENDER_FEMALE, sorted ); + } + else + { + GenerateCombinedFile( outfilename, params.m_szCCToken, GENDER_NONE, sorted ); + } +} + +bool CChoreoView::ValidateCombinedSoundCheckSum( CChoreoEvent *e ) +{ + if ( !e || e->GetType() != CChoreoEvent::SPEAK ) + return false; + + bool genderwildcard = e->IsCombinedUsingGenderToken(); + char outfilename[ 512 ]; + Q_memset( outfilename, 0, sizeof( outfilename ) ); + if ( !e->ComputeCombinedBaseFileName( outfilename, sizeof( outfilename ), genderwildcard ) ) + { + Con_ErrorPrintf( "Unable to regenerate wav file name for combined sound (%s)\n", e->GetCloseCaptionToken() ); + return false; + } + + bool checksumvalid = false; + + CUtlRBTree< CChoreoEvent * > eventList( 0, 0, EventStartTimeLessFunc ); + + if ( !e->GetChannel()->GetSortedCombinedEventList( e->GetCloseCaptionToken(), eventList ) ) + { + Con_ErrorPrintf( "Unable to generated combined event list (%s)\n", e->GetCloseCaptionToken() ); + return false; + } + + + if ( genderwildcard ) + { + checksumvalid = ValidateCombinedFileCheckSum( outfilename, e->GetCloseCaptionToken(), GENDER_MALE, eventList ); + checksumvalid &= ValidateCombinedFileCheckSum( outfilename, e->GetCloseCaptionToken(), GENDER_FEMALE, eventList ); + } + else + { + checksumvalid = ValidateCombinedFileCheckSum( outfilename, e->GetCloseCaptionToken(), GENDER_NONE, eventList ); + } + + return checksumvalid; +} + +void CChoreoView::OnRemoveSpeakEventFromGroup() +{ + if ( !m_pScene ) + return; + + int i, c; + + CUtlVector< CChoreoEvent * > selected; + CUtlVector< CChoreoEvent * > processlist; + if ( GetSelectedEvents( selected ) > 0 ) + { + + int c = selected.Count(); + // Find the appropriate event by iterating across all actors and channels + for ( i = c - 1; i >= 0; --i ) + { + CChoreoEvent *e = selected[ i ]; + if ( e->GetType() != CChoreoEvent::SPEAK ) + { + selected.Remove( i ); + continue; + } + + if ( e->GetCloseCaptionType() == CChoreoEvent::CC_DISABLED ) + { + selected.Remove( i ); + continue; + } + + m_pClickedChannel->GetMasterAndSlaves( e, processlist ); + } + } + else + { + m_pClickedChannel->GetMasterAndSlaves( m_pClickedChannel->GetCaptionClickedEvent(), processlist ); + } + + if ( selected.Count() < 1 ) + { + Con_ErrorPrintf( "No eligible SPEAK event selected.\n" ); + return; + } + + SetDirty( true ); + + PushUndo( "Remove speak event(s)" ); + + c = processlist.Count(); + for ( i = 0 ; i < c; ++i ) + { + processlist[ i ]->SetCloseCaptionToken( "" ); + processlist[ i ]->SetCloseCaptionType( CChoreoEvent::CC_MASTER ); + processlist[ i ]->SetUsingCombinedFile( false ); + processlist[ i ]->SetRequiredCombinedChecksum( 0 ); + processlist[ i ]->SetNumSlaves( 0 ); + processlist[ i ]->SetLastSlaveEndTime( 0.0f ); + } + + PushRedo( "Remove speak event(s)" ); + + // Redraw + InvalidateLayout(); + Con_Printf( "Reverted %i events to use default close caption token\n", c ); +} + +bool CChoreoView::AreSelectedEventsCombinable() +{ + CUtlVector< CChoreoEvent * > events; + if ( GetSelectedEvents( events ) <= 0 ) + return false; + + CChoreoChannel *firstChannel = NULL; + + CUtlVector< CChoreoEvent * > selected; + GetSelectedEvents( selected ); + + int c = selected.Count(); + // Find the appropriate event by iterating across all actors and channels + for ( int i = c - 1; i >= 0; --i ) + { + CChoreoEvent *e = selected[ i ]; + + if ( e->GetType() != CChoreoEvent::SPEAK ) + { + return false; + } + + if ( !firstChannel ) + { + firstChannel = e->GetChannel(); + } + else if ( e->GetChannel() != firstChannel ) + { + return false; + } + } + return selected.Count() >= 2 ? true : false; +} + +bool CChoreoView::AreSelectedEventsInSpeakGroup() +{ + CUtlVector< CChoreoEvent * > selected; + if ( GetSelectedEvents( selected ) <= 0 ) + { + if ( m_pClickedChannel ) + { + CChoreoEvent *e = m_pClickedChannel->GetCaptionClickedEvent(); + if ( e && e->GetCloseCaptionType() == CChoreoEvent::CC_MASTER && + e->GetNumSlaves() >= 1 ) + { + return true; + } + } + return false; + } + + int c = selected.Count(); + // Find the appropriate event by iterating across all actors and channels + for ( int i = c - 1; i >= 0; --i ) + { + CChoreoEvent *e = selected[ i ]; + if ( e->GetType() != CChoreoEvent::SPEAK ) + { + selected.Remove( i ); + continue; + } + + if ( e->GetCloseCaptionType() == CChoreoEvent::CC_DISABLED ) + { + selected.Remove( i ); + continue; + } + } + + return selected.Count() >= 1 ? true : false; + +} + +void CChoreoView::OnChangeCloseCaptionToken( CChoreoEvent *e ) +{ + CCloseCaptionLookupParams params; + Q_strncpy( params.m_szDialogTitle, "Close Caption Token Lookup", sizeof( params.m_szDialogTitle ) ); + + params.m_bPositionDialog = false; + params.m_nLeft = 0; + params.m_nTop = 0; + // strcpy( params.m_szPrompt, "Choose model:" ); + + Q_strncpy( params.m_szCCToken, e->GetCloseCaptionToken(), sizeof( params.m_szCCToken ) ); + + // User hit okay and value actually changed? + if ( CloseCaptionLookup( ¶ms ) && + Q_stricmp( e->GetCloseCaptionToken(), params.m_szCCToken ) ) + { + char oldToken[ CChoreoEvent::MAX_CCTOKEN_STRING ]; + Q_strncpy( oldToken, e->GetCloseCaptionToken(), sizeof( oldToken ) ); + + CUtlVector< CChoreoEvent * > events; + m_pClickedChannel->GetMasterAndSlaves( e, events ); + + if ( events.Count() < 2 ) + { + Con_ErrorPrintf( "Can't combine events, must have at least two events selected.\n" ); + } + + SetDirty( true ); + + PushUndo( "Change closecaption token" ); + + // Make the change... + int c = events.Count(); + for ( int i = 0 ; i < c; ++i ) + { + events[i]->SetCloseCaptionToken( params.m_szCCToken ); + } + + PushRedo( "Change closecaption token" ); + + InvalidateLayout(); + + Con_Printf( "Close Caption token for '%s' changed to '%s'\n", e->GetName(), params.m_szCCToken ); + } +} + +void CChoreoView::OnToggleCloseCaptionsForEvent() +{ + if ( !m_pClickedChannel ) + { + return; + } + + CChoreoEvent *e = m_pClickedChannel->GetCaptionClickedEvent(); + + if ( !e ) + { + return; + } + + CChoreoEvent::CLOSECAPTION newType = CChoreoEvent::CC_MASTER; + // Can't mess with slave + switch ( e->GetCloseCaptionType() ) + { + default: + case CChoreoEvent::CC_SLAVE: + return; + case CChoreoEvent::CC_MASTER: + newType = CChoreoEvent::CC_DISABLED; + break; + case CChoreoEvent::CC_DISABLED: + newType = CChoreoEvent::CC_MASTER; + break; + } + + SetDirty( true ); + + PushUndo( "Enable/disable captions" ); + + // Make the change... + e->SetCloseCaptionType( newType ); + + PushRedo( "Enable/disable captions" ); + + InvalidateLayout(); + + Con_Printf( "Close Caption type for '%s' changed to '%s'\n", e->GetName(), CChoreoEvent::NameForCCType( newType ) ); + +} + +void CChoreoView::StopScene() +{ + SetScrubTargetTime( m_flScrub ); + FinishSimulation(); + sound->Flush(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +//----------------------------------------------------------------------------- +template <class T> +void DeleteAllAndPurge( T &tree ) +{ + T::IndexType_t i; + + for ( i = tree.FirstInorder(); i != T::InvalidIndex(); i = tree.NextInorder( i ) ) + { + delete tree[i]; + } + + tree.Purge(); +} + +void CChoreoView::OnPlaceNextSpeakEvent() +{ + CUtlVector< CChoreoEvent * > list; + GetSelectedEvents( list ); + if ( list.Count() != 1 ) + { + Warning( "Can't place sound event, nothing selected\n" ); + return; + } + + CChoreoEvent *ev = list[ 0 ]; + if ( ev->GetType() != CChoreoEvent::SPEAK ) + { + Warning( "Can't place sound event, no previous sound event selected\n" ); + return; + } + + CChoreoChannelWidget *widget = FindChannelForEvent( ev ); + if ( !widget ) + { + Warning( "Can't place sound event, can't find channel widget for event\n" ); + return; + } + + CChoreoChannel *channel = widget->GetChannel(); + if ( !channel ) + { + Warning( "Can't place sound event, can't find channel for new event\n" ); + return; + } + + CUtlRBTree< char const *, int > m_SortedNames( 0, 0, NameLessFunc ); + + int c = soundemitter->GetSoundCount(); + for ( int i = 0; i < c; i++ ) + { + char const *name = soundemitter->GetSoundName( i ); + if ( name && name[ 0 ] ) + { + m_SortedNames.Insert( strdup( name ) ); + } + } + + int idx = m_SortedNames.Find( ev->GetParameters() ); + if ( idx == m_SortedNames.InvalidIndex() ) + { + Warning( "Can't place sound event, can't find '%s' in sound list\n", ev->GetParameters() ); + DeleteAllAndPurge( m_SortedNames ); + return; + } + + int nextIdx = m_SortedNames.NextInorder( idx ); + if ( nextIdx == m_SortedNames.InvalidIndex() ) + { + Warning( "Can't place sound event, can't next sound after '%s' in sound list\n", ev->GetParameters() ); + DeleteAllAndPurge( m_SortedNames ); + return; + } + + DeselectAll(); + + SetDirty( true ); + + PushUndo( "Place Next Speak Event" ); + + CChoreoEvent *event = m_pScene->AllocEvent(); + Assert( event ); + if ( event ) + { + // Copy everything for source event + *event = *ev; + + event->SetParameters( m_SortedNames[ nextIdx ] ); + // Start it at the end time... + event->SetStartTime( event->GetEndTime() ); + event->SetResumeCondition( false ); + event->ClearAllRelativeTags(); + event->ClearAllTimingTags(); + event->ClearAllAbsoluteTags( CChoreoEvent::PLAYBACK ); + event->ClearAllAbsoluteTags( CChoreoEvent::ORIGINAL ); + + event->SetChannel( channel ); + event->SetActor( channel->GetActor() ); + + // Try and load wav to get length + CAudioSource *wave = sound->LoadSound( va( "sound/%s", FacePoser_TranslateSoundName( event ) ) ); + if ( wave ) + { + event->SetEndTime( event->GetStartTime() + wave->GetRunningLength() ); + delete wave; + } + + DeleteSceneWidgets(); + + // Add to appropriate channel + channel->AddEvent( event ); + + CreateSceneWidgets(); + + CChoreoEventWidget *eventWidget = FindWidgetForEvent( event ); + if ( eventWidget ) + { + eventWidget->SetSelected( true ); + } + + // Redraw + InvalidateLayout(); + } + + PushRedo( "Place Next Speak Event" ); + + DeleteAllAndPurge( m_SortedNames ); +} + +enum +{ + FM_LEFT = 0, + FM_RIGHT, + FM_SMALLESTWIDE, + FM_LARGESTWIDE +}; + +static int FindMetric( int type, CUtlVector< CChoreoEvent * > &list, float& value ) +{ + float bestVal = 999999.0f; + int bestIndex = -1; + bool greater = true; + + switch ( type ) + { + default: + case FM_LEFT: + case FM_SMALLESTWIDE: + greater = false; + break; + case FM_RIGHT: + case FM_LARGESTWIDE: + bestVal = -bestVal; + greater = true; + break; + } + int c = list.Count(); + for ( int i = 0; i < c; ++i ) + { + CChoreoEvent *e = list[ i ]; + if ( type != FM_LEFT && + !e->HasEndTime() ) + continue; + + float val; + switch ( type ) + { + default: + case FM_LEFT: + val = e->GetStartTime(); + break; + case FM_RIGHT: + val = e->GetEndTime(); + break; + case FM_SMALLESTWIDE: + case FM_LARGESTWIDE: + val = e->GetDuration(); + break; + } + + if ( greater ) + { + if ( val <= bestVal ) + continue; + } + else + { + if ( val >= bestVal ) + continue; + } + + bestVal = val; + bestIndex = i; + } + + value = bestVal; + return bestIndex; +} + +void CChoreoView::OnAlign( bool left ) +{ + CUtlVector< CChoreoEvent * > list; + GetSelectedEvents( list ); + + if ( left ) + { + for ( int i = 0; i < m_SceneGlobalEvents.Size(); i++ ) + { + CChoreoGlobalEventWidget *event = m_SceneGlobalEvents[ i ]; + if ( !event || !event->IsSelected() ) + continue; + + list.AddToTail( event->GetEvent() ); + } + } + + int numSel = list.Count(); + if ( numSel < 2 ) + { + Warning( "Can't align, must have at least two events selected\n" ); + return; + } + + float value; + int idx = FindMetric( left ? FM_LEFT : FM_RIGHT, list, value ); + if ( idx == -1 ) + { + return; + } + + SetDirty( true ); + + char undotext[ 128 ]; + Q_snprintf( undotext, sizeof( undotext ), "Align %s", left ? "Left" : "Right" ); + PushUndo( undotext ); + + for ( int i = 0; i < numSel; ++i ) + { + if ( i == idx ) + continue; + + CChoreoEvent *e = list[ i ]; + + float newStartTime = left ? value : ( value - e->GetDuration() ); + float offset = newStartTime - e->GetStartTime(); + e->OffsetTime( offset ); + } + + PushRedo( undotext ); + + InvalidateLayout(); +} + +void CChoreoView::OnMakeSameSize( bool smallest ) +{ + CUtlVector< CChoreoEvent * > list; + int numSel = GetSelectedEvents( list ); + if ( numSel < 2 ) + { + Warning( "Can't align, must have at least two events selected\n" ); + return; + } + + float value; + int idx = FindMetric( smallest ? FM_SMALLESTWIDE : FM_LARGESTWIDE, list, value ); + if ( idx == -1 ) + { + return; + } + + SetDirty( true ); + + char undotext[ 128 ]; + Q_snprintf( undotext, sizeof( undotext ), "Size to %s", smallest ? "Smallest" : "Largest" ); + PushUndo( undotext ); + + for ( int i = 0; i < numSel; ++i ) + { + if ( i == idx ) + continue; + + list[ i ]->SetEndTime( list[ i ]->GetStartTime() + value ); + } + + PushRedo( undotext ); + + InvalidateLayout(); +} + +void CChoreoView::SelectAllEventsInActor( CChoreoActorWidget *actor ) +{ + TraverseWidgets( &CChoreoView::SelectInActor, actor ); + redraw(); +} + +void CChoreoView::SelectAllEventsInChannel( CChoreoChannelWidget *channel ) +{ + TraverseWidgets( &CChoreoView::SelectInChannel, channel ); + redraw(); +} + +void CChoreoView::SelectInActor( CChoreoWidget *widget, CChoreoWidget *param1 ) +{ + CChoreoEventWidget *ev = dynamic_cast< CChoreoEventWidget * >( widget ); + if ( !ev ) + return; + + if ( ev->IsSelected() ) + return; + + CChoreoChannel *ch = ev->GetEvent()->GetChannel(); + if ( !ch ) + return; + CChoreoActor *actor = ch->GetActor(); + if ( !actor ) + return; + + CChoreoActorWidget *actorw = dynamic_cast< CChoreoActorWidget * >( param1 ); + if ( !actorw ) + return; + + if ( actorw->GetActor() != actor ) + return; + + widget->SetSelected( true ); +} + +void CChoreoView::SelectInChannel( CChoreoWidget *widget, CChoreoWidget *param1 ) +{ + CChoreoEventWidget *ev = dynamic_cast< CChoreoEventWidget * >( widget ); + if ( !ev ) + return; + + if ( ev->IsSelected() ) + return; + + CChoreoChannel *ch = ev->GetEvent()->GetChannel(); + if ( !ch ) + return; + + CChoreoChannelWidget *chw = dynamic_cast< CChoreoChannelWidget * >( param1 ); + if ( !chw ) + return; + + if ( chw->GetChannel() != ch ) + return; + + widget->SetSelected( true ); +} + diff --git a/utils/hlfaceposer/choreoview.h b/utils/hlfaceposer/choreoview.h new file mode 100644 index 0000000..bd741c1 --- /dev/null +++ b/utils/hlfaceposer/choreoview.h @@ -0,0 +1,832 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#ifndef CHOREOVIEW_H +#define CHOREOVIEW_H +#ifdef _WIN32 +#pragma once +#endif + + +#include <mxtk/mx.h> +#include <mxtk/mxWindow.h> +#include "mxBitmapButton.h" +#include "utlvector.h" +#include "ChoreoWidget.h" +#include "ichoreoeventcallback.h" +#include "faceposertoolwindow.h" +#include "ChoreoEvent.h" +#include "mathlib/mathlib.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" + +#define IDC_CV_CC_LANGUAGESTART 5300 + +#define IDC_STOPSCENE 5000 +#define IDC_PLAYSCENE 5001 +#define IDC_PAUSESCENE 5002 +#define IDC_CHOREOVSCROLL 5003 +#define IDC_CHOREOHSCROLL 5004 + +#define IDC_ADDACTOR 5005 +#define IDC_DELETEACTOR 5006 +#define IDC_MOVEACTORUP 5007 +#define IDC_MOVEACTORDOWN 5008 +#define IDC_EDITACTOR 5009 + +#define IDC_EDITEVENT 5010 +#define IDC_DELETEEVENT 5011 + +#define IDC_ADDEVENT_EXPRESSION 5012 +#define IDC_ADDEVENT_GESTURE 5013 +#define IDC_ADDEVENT_LOOKAT 5014 +#define IDC_ADDEVENT_MOVETO 5015 +#define IDC_ADDEVENT_SPEAK 5016 +#define IDC_ADDEVENT_FACE 5017 +#define IDC_ADDEVENT_FIRETRIGGER 5018 +#define IDC_ADDEVENT_SEQUENCE 5019 +#define IDC_ADDEVENT_GENERIC 5020 + +#define IDC_CV_CHANGESCALE 5021 + +#define IDC_EDITGLOBALEVENT 5022 +#define IDC_DELETEGLOBALEVENT 5023 +#define IDC_ADDEVENT_PAUSE 5024 + +#define IDC_ADDCHANNEL 5025 +#define IDC_EDITCHANNEL 5026 +#define IDC_DELETECHANNEL 5027 +#define IDC_MOVECHANNELUP 5028 +#define IDC_MOVECHANNELDOWN 5029 + +#define IDC_CHANNELOPEN 5030 +#define IDC_CHANNELCLOSE 5031 +#define IDC_CBACTORACTIVE 5032 +#define IDC_DELETERELATIVETAG 5033 +#define IDC_ADDTIMINGTAG 5034 + +#define IDC_SELECTALL 5035 +#define IDC_DESELECTALL 5036 +#define IDC_MOVETOBACK 5037 + +#define IDC_UNDO 5038 +#define IDC_REDO 5039 + +#define IDC_EXPRESSIONTOOL 5040 +#define IDC_ASSOCIATEBSP 5041 + +#define IDC_ADDEVENT_FLEXANIMATION 5042 + +#define IDC_COPYEVENTS 5043 +#define IDC_PASTEEVENTS 5044 + +#define IDC_IMPORTEVENTS 5045 +#define IDC_EXPORTEVENTS 5046 + +#define IDC_ADDEVENT_SUBSCENE 5047 +#define IDC_PLAYSCENE_BACKWARD 5048 + +#define IDC_ASSOCIATEMODEL 5049 +#define IDC_CHOREO_PLAYBACKRATE 5050 + +#define IDC_CV_CHECKSEQLENGTHS 5051 +#define IDC_CV_PROCESSSEQUENCES 5052 + +#define IDC_GESTURETOOL 5053 + +#define IDC_ADDEVENT_LOOP 5054 +#define IDC_CV_TOGGLERAMPONLY 5055 + +#define IDC_ADDEVENT_INTERRUPT 5056 +#define IDC_ADDEVENT_STOPPOINT 5067 + +#define SCENE_ACTION_UNKNOWN 0 +#define SCENE_ACTION_CANCEL 1 +#define SCENE_ACTION_RESUME 2 + +#define SCENE_ANIMLAYER_SEQUENCE 0 +#define SCENE_ANIMLAYER_GESTURE 1 + +#define IDC_ADDEVENT_NULLGESTURE 5068 + +#define IDC_SELECTEVENTS_ALL_BEFORE 5069 +#define IDC_SELECTEVENTS_ALL_AFTER 5070 +#define IDC_SELECTEVENTS_ACTIVE_BEFORE 5071 +#define IDC_SELECTEVENTS_ACTIVE_AFTER 5072 +#define IDC_SELECTEVENTS_CHANNEL_BEFORE 5073 +#define IDC_SELECTEVENTS_CHANNEL_AFTER 5074 + +#define IDC_INSERT_TIME 5075 +#define IDC_DELETE_TIME 5076 + +#define IDC_EXPORT_VCD 5077 +#define IDC_IMPORT_VCD 5078 + +#define IDC_ADDEVENT_PERMITRESPONSES 5079 + +#define IDC_CV_CC_SHOW 5080 +#define IDC_CV_COMBINESPEAKEVENTS 5081 +#define IDC_CV_REMOVESPEAKEVENTFROMGROUP 5082 +#define IDC_CV_CHANGECLOSECAPTIONTOKEN 5083 +#define IDC_CV_TOGGLECLOSECAPTIONS 5084 + +#define IDC_CV_ALIGN_LEFT 5085 +#define IDC_CV_ALIGN_RIGHT 5086 +#define IDC_CV_SAMESIZE_SMALLEST 5087 +#define IDC_CV_SAMESIZE_LARGEST 5088 + +#define IDC_CV_ALLEVENTS_CHANNEL 5089 +#define IDC_CV_ALLEVENTS_ACTOR 5090 + +#define IDC_CV_ENABLEEVENTS 5091 +#define IDC_CV_DISABLEEVENTS 5092 + +///////////////////////////////////////////////////////////////////////////// +// CChoreoView window +class CChoreoScene; +class CChoreoEvent; +class CChoreoActor; +class CChoreoChannel; + +class CChoreoActorWidget; +class CChoreoChannelWidget; +class CChoreoEventWidget; +class CChoreoGlobalEventWidget; +class CChoreoWidgetDrawHelper; + +class PhonemeEditor; +class CExpression; +class CExpClass; +class StudioModel; + +//----------------------------------------------------------------------------- +// Purpose: The main view of the choreography data for a scene +//----------------------------------------------------------------------------- +class CChoreoView : public mxWindow, public IFacePoserToolWindow, public IChoreoEventCallback +{ +// Construction +public: + CChoreoView( mxWindow *parent, int x, int y, int w, int h, int id ); + virtual ~CChoreoView(); + + virtual void OnModelChanged(); + + virtual void OnDelete(); + virtual bool CanClose(); + + void InvalidateTrackLookup( void ); + + // Implements IChoreoEventCallback + virtual void StartEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ); + virtual void EndEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ); + virtual void ProcessEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ); + virtual bool CheckEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ); + + void SetChoreoFile( char const *filename ); + char const *GetChoreoFile( void ) const; + + // Scene load/unload + void LoadSceneFromFile( const char *filename ); + CChoreoScene *LoadScene( char const *filename ); + void UnloadScene( void ); + + // UI + void New( void ); + void Save( void ); + void SaveAs( void ); + void Load( void ); + void LoadNext( void ); + bool Close( void ); + + // Drag/drop from expression thumbnail + + bool CreateExpressionEvent( int mx, int my, CExpClass *cl, CExpression *exp ); + bool CreateAnimationEvent( int mx, int my, char const *animationname ); + + void SelectAll( void ); + void DeselectAll( void ); + + struct SelectionParams_t + { + enum + { + SP_CHANNEL = 0, + SP_ACTIVE, + SP_ALL + }; + + float time; + bool forward; + int type; + }; + + void SelectEvents( SelectionParams_t& params ); + + // Channel/actor right click menu + void NewChannel( void ); + void DeleteChannel( CChoreoChannel *channel ); + void MoveChannelUp( CChoreoChannel *channel ); + void MoveChannelDown( CChoreoChannel *channel ); + void EditChannel( CChoreoChannel *channel ); + void AddEvent( int type, int subtype = 0, char const *defaultparameters = NULL ); + + // Actor right click menu + void NewActor( void ); + void DeleteActor( CChoreoActor *actor ); + void MoveActorUp( CChoreoActor *actor ); + void MoveActorDown( CChoreoActor *actor ); + void EditActor( CChoreoActor *actor ); + + // Event menu + void EditEvent( CChoreoEvent *event ); + void DeleteSelectedEvents( void ); + void EnableSelectedEvents( bool state ); + void DeleteEventRelativeTag( CChoreoEvent *event, int tagnum ); + void AddEventRelativeTag( void ); + CChoreoEventWidget *FindWidgetForEvent( CChoreoEvent *event ); + CChoreoChannelWidget *FindChannelForEvent( CChoreoEvent *event ); + + void MoveEventToBack( CChoreoEvent *event ); + void OnExpressionTool( void ); + void OnGestureTool( void ); + + // Global event ( pause ) menu + void EditGlobalEvent( CChoreoEvent *event ); + void DeleteGlobalEvent( CChoreoEvent *event ); + void AddGlobalEvent( CChoreoEvent::EVENTTYPE type ); + + void AssociateBSP( void ); + void AssociateModel( void ); + void AssociateModelToActor( CChoreoActor *actor, int modelindex ); + + // UI Layout + void CreateSceneWidgets( void ); + void DeleteSceneWidgets( void ); + void LayoutScene( void ); + void InvalidateLayout( void ); + void ForceScrollBarsToRecompute( bool resetthumb ); + + // Layout data that children should obey + int GetLabelWidth( void ); + int GetStartRow( void ); + int GetRowHeight( void ); + int GetFontSize( void ); + int GetEndRow( void ); + + // Simulation + void PlayScene( bool forward ); + void PauseScene( void ); + + bool IsPlayingScene( void ); + void ResetTargetSettings( void ); + void UpdateCurrentSettings( void ); + + virtual void Think( float dt ); + virtual bool IsScrubbing( void ) const; + virtual bool IsProcessing( void ); + + + bool ShouldProcessSpeak( void ); + + void SceneThink( float time ); + void PauseThink( void ); + void FinishSimulation( void ); + void StopScene(); + + float GetStartTime( void ); + float GetEndTime( void ); + void SetStartTime( float time ); + + void ProcessExpression( CChoreoScene *scene, CChoreoEvent *event ); + void ProcessFlexAnimation( CChoreoScene *scene, CChoreoEvent *event ); + void ProcessLookat( CChoreoScene *scene, CChoreoEvent *event ); + bool GetTarget( CChoreoScene *scene, CChoreoEvent *event, Vector &vecTarget, QAngle &vecAngle ); + void ProcessFace( CChoreoScene *scene, CChoreoEvent *event ); + void ProcessGesture( CChoreoScene *scene, CChoreoEvent *event ); + void ProcessSequence( CChoreoScene *scene, CChoreoEvent *event ); + void ProcessMoveto( CChoreoScene *scene, CChoreoEvent *event ); + int GetMovetoSequence( CChoreoScene *scene, CChoreoEvent *event, StudioModel *model ); + void ProcessSubscene( CChoreoScene *scene, CChoreoEvent *event ); + void ProcessPause( CChoreoScene *scene, CChoreoEvent *event ); + void ProcessSpeak( CChoreoScene *scene, CChoreoEvent *event ); + void ProcessLoop( CChoreoScene *scene, CChoreoEvent *event ); + void ProcessInterrupt( CChoreoScene *scene, CChoreoEvent *event ); + void ProcessPermitResponses( CChoreoScene *scene, CChoreoEvent *event ); + + float GetPixelsPerSecond( void ); + + // mxWindow overrides + virtual void redraw(); + virtual bool PaintBackground( void ); + virtual int handleEvent( mxEvent *event ); + + // Draw helpers + void DrawSceneABTicks( CChoreoWidgetDrawHelper& drawHelper ); + void DrawTimeLine( CChoreoWidgetDrawHelper& drawHelper, RECT& rc, float left, float right ); + void DrawBackground( CChoreoWidgetDrawHelper& drawHelper, RECT& rc ); + void DrawRelativeTagLines( CChoreoWidgetDrawHelper& drawHelper, RECT& rc ); + + // Remap click position to/from time value + float GetTimeValueForMouse( int mx, bool clip = false ); + int GetPixelForTimeValue( float time, bool *clipped = NULL ); + float GetTimeDeltaForMouseDelta( int mx, int origmx ); + + // Readjust slider + void MoveTimeSliderToPos( int x ); + + // Dirty flag for file save prompting + + bool GetDirty( void ); + void SetDirty( bool dirty, bool clearundo = true ); + + void ShowContextMenu( int mx, int my ); + + // Caller must first translate mouse into screen coordinates + bool IsMouseOverTimeline( int mx, int my ); + + CChoreoActorWidget *GetActorUnderCursorPos( int mx, int my ); + CChoreoChannelWidget *GetChannelUnderCursorPos( int mx, int my ); + CChoreoEventWidget *GetEventUnderCursorPos( int mx, int my ); + CChoreoGlobalEventWidget *GetGlobalEventUnderCursorPos( int mx, int my ); + int GetTagUnderCursorPos( CChoreoEventWidget *event, int mx, int my ); + CEventAbsoluteTag *GetAbsoluteTagUnderCursorPos( CChoreoEventWidget *event, int mx, int my ); + + + void GetObjectsUnderMouse( int mx, int my, + CChoreoActorWidget **actor, + CChoreoChannelWidget **channel, + CChoreoEventWidget **event, + CChoreoGlobalEventWidget **globalevent, + int* clickedTag, + CEventAbsoluteTag **absolutetag, + int *clickedCCButton ); + + void OnDoubleClicked( void ); + + void ApplyBounds( int& mx, int& my ); + void CalcBounds( int movetype ); + + void MouseStartDrag( mxEvent *event, int mx, int my ); + void MouseContinueDrag( mxEvent *event, int mx, int my ); + void MouseFinishDrag( mxEvent *event, int mx, int my ); + void MouseMove( int mx, int my ); + + //void StartDraggingGlobalEvent( int mx, int my ); + void StartDraggingEvent( int mx, int my ); + //void FinishDraggingGlobalEvent( int mx, int my ); + void FinishDraggingEvent( mxEvent *event, int mx, int my ); + + // Draw focus rect while mouse dragging is going on + void DrawFocusRect( void ); + int ComputeEventDragType( int mx, int my ); + + void SetCurrentWaveFile( const char *filename, CChoreoEvent *event ); + + void RecomputeWaves(); + + typedef void (CChoreoView::*CVMEMBERFUNC)( CChoreoWidget *widget, CChoreoWidget *param1 ); + + void TraverseWidgets( CVMEMBERFUNC pfn, CChoreoWidget *param1 ); + + int CountSelectedEvents( void ); + int CountSelectedGlobalEvents( void ); + int GetSelectedEvents( CUtlVector< CChoreoEvent * >& events ); + int GetSelectedEventWidgets( CUtlVector< CChoreoEventWidget * >& events ); + int GetEarliestEventIndex( CUtlVector< CChoreoEventWidget * >& events ); + int GetLatestEventIndex( CUtlVector< CChoreoEventWidget * >& events ); + + // Traversal functions + void Deselect( CChoreoWidget *widget, CChoreoWidget *param1 ); + void Select( CChoreoWidget *widget, CChoreoWidget *param1 ); + void SelectInActor( CChoreoWidget *widget, CChoreoWidget *param1 ); + void SelectInChannel( CChoreoWidget *widget, CChoreoWidget *param1 ); + + void SelectAllEvents( CChoreoWidget *widget, CChoreoWidget *param1 ); + + void SelectAllEventsInActor( CChoreoActorWidget *actor ); + void SelectAllEventsInChannel( CChoreoChannelWidget *channel ); + + // Adjust scroll bars + void RepositionVSlider( void ); + void RepositionHSlider( void ); + + void UpdateStatusArea( int mx, int my ); + void ClearStatusArea( void ); + void RedrawStatusArea( CChoreoWidgetDrawHelper& drawHelper, RECT& rcStatus ); + + void GetUndoLevels( int& current, int& number ); + + // Undo/Redo + void Undo( void ); + void Redo( void ); + + bool CanUndo(); + bool CanRedo(); + + // Do push before changes + void PushUndo( const char *description ); + // Do this push after changes, must match pushundo 1for1 + void PushRedo( const char *description ); + + void WipeUndo( void ); + void WipeRedo( void ); + + const char *GetUndoDescription( void ); + const char *GetRedoDescription( void ); + + CChoreoScene *GetScene( void ); + + void ReportSceneClearToTools( void ); + + void CopyEvents( void ); + void PasteEvents( void ); + void ImportEvents( void ); + void ExportEvents( void ); + + void ExportVCD(); + void ImportVCD(); + + void ExportVCDFile( char const *filename ); + void ImportVCDFile( char const *filename ); + + bool CanPaste( void ); + + bool IsMouseOverScrubHandle( mxEvent *event ); + bool IsMouseOverScrubArea( mxEvent *event ); + + void GetScrubHandleRect( RECT& rcHandle, bool clipped = false ); + void GetScrubAreaRect( RECT& rcArea ); + void DrawScrubHandle( CChoreoWidgetDrawHelper& drawHelper ); + void DrawScrubHandle( void ); + void ScrubThink( float dt, bool scrubbing, IFacePoserToolWindow *invoker ); + + void ClampTimeToSelectionInterval( float& timeval ); + + void SetScrubTime( float t ); + void SetScrubTargetTime( float t ); + + void OnCheckSequenceLengths( void ); + + bool IsRampOnly( void ) const; + + void SetTimeZoom( const char *tool, int tz, bool preserveFocus ); + int GetTimeZoom( const char *tool ); + template< class T > + void SetPreservedTimeZoom( T *other, int tz ); + template< class T > + int HandleZoomKey( T *other, int keyCode ); + + void OnInsertTime(); + void OnDeleteTime(); + + bool GetShowCloseCaptionData( void ) const; + bool ValidateCombinedSoundCheckSum( CChoreoEvent *e ); + + void OnPlaceNextSpeakEvent(); + +private: + + void CheckInsertTime( CChoreoEvent *e, float dt, float starttime, float endtime ); + void CheckDeleteTime( CChoreoEvent *d, float dt, float starttime, float endtime, bool& deleteEvent ); + + bool FixupSequenceDurations( CChoreoScene *scene, bool checkonly ); + bool CheckSequenceLength( CChoreoEvent *e, bool checkonly ); + bool CheckGestureLength( CChoreoEvent *e, bool checkonly ); + bool DefaultGestureLength( CChoreoEvent *e, bool checkonly ); + bool AutoaddGestureKeys( CChoreoEvent *e, bool checkonly ); + + void InvalidateTrackLookup_R( CChoreoScene *scene ); + + void ShowButtons( bool show ); + + // Compute full size of data in pixels, for setting up scroll bars + int ComputeVPixelsNeeded( void ); + int ComputeHPixelsNeeded( void ); + void PositionControls(); + + void OnChangeScale(); + + float FindNextEventTime( CChoreoEvent::EVENTTYPE type, CChoreoChannel *channel, CChoreoEvent *e, bool forward ); + + bool ShouldSelectEvent( SelectionParams_t ¶ms, CChoreoEvent *event ); + + bool IsMouseOverSceneEndTime( int mx ); + + void StartDraggingSceneEndTime( int mx, int my ); + void FinishDraggingSceneEndTime( mxEvent *event, int mx, int my ); + + void SetShowCloseCaptionData( bool show ); + + void OnToggleCloseCaptionTags(); + void OnCombineSpeakEvents(); + void OnRemoveSpeakEventFromGroup(); + void OnChangeCloseCaptionToken( CChoreoEvent *e ); + + bool AreSelectedEventsCombinable(); + bool AreSelectedEventsInSpeakGroup(); + + void OnToggleCloseCaptionsForEvent(); + + bool GenerateCombinedFile( char const *outfilename, const char *cctoken, gender_t gender, CUtlRBTree< CChoreoEvent * >& sorted ); + bool ValidateCombinedFileCheckSum( char const *outfilename, char const *cctoken, gender_t gender, CUtlRBTree< CChoreoEvent * >& sorted ); + + void RememberSelectedEvents( CUtlVector< CChoreoEvent * >& list ); + void ReselectEvents( CUtlVector< CChoreoEvent * >& list ); + + void OnAlign( bool left ); + void OnMakeSameSize( bool smallest ); + + bool IsMouseOverEventEdge( CChoreoEventWidget *ew, bool bLeftEdge, int mx, int my ); + bool IsMouseOverEvent( CChoreoEventWidget *ew, int mx, int my ); + typedef struct + { + bool active; + float time; + } SCENEAB; + + void PlaceABPoint( int mx ); + void ClearABPoints( void ); + + SCENEAB m_rgABPoints[ 2 ]; + int m_nCurrentABPoint; + + bool m_bForward; + + // The underlying scene we are editing + CChoreoScene *m_pScene; + + enum + { + MAX_ACTORS = 32 + }; + + typedef struct + { + bool expanded; + } ACTORSTATE; + + ACTORSTATE m_ActorExpanded[ MAX_ACTORS ]; + // The scene's ui actors + CUtlVector < CChoreoActorWidget * > m_SceneActors; + // The scenes segment markers + CUtlVector < CChoreoGlobalEventWidget * > m_SceneGlobalEvents; + + // How many pixels per second we are showing in the UI + float m_flPixelsPerSecond; + + // Do we need to move controls? + bool m_bLayoutIsValid; + + // Starting row of first actor + int m_nStartRow; + // How wide the actor/channel name area is + int m_nLabelWidth; + // Height between channel/actor names + int m_nRowHeight; + // Font size for drawing event labels + int m_nFontSize; + + // Height/width of scroll bars + int m_nScrollbarHeight; + // Height off info area for flyover info / help + int m_nInfoHeight; + + // Simulation info + float m_flStartTime; + float m_flEndTime; + + float m_flFrameTime; + bool m_bSimulating; + bool m_bPaused; + float m_flLastSpeedScale; + bool m_bResetSpeedScale; + bool m_bAutomated; + int m_nAutomatedAction; + float m_flAutomationDelay; + float m_flAutomationTime; + + // Some rectangles for the UI + RECT m_rcTitles; + RECT m_rcTimeLine; + + // Play/pause buttons for simulation + mxBitmapButton *m_btnPlay; + mxBitmapButton *m_btnPause; + mxBitmapButton *m_btnStop; + + mxSlider *m_pPlaybackRate; + float m_flPlaybackRate; + + // The scroll bars + mxScrollbar *m_pVertScrollBar; + mxScrollbar *m_pHorzScrollBar; + int m_nLastHPixelsNeeded; + int m_nLastVPixelsNeeded; + + // Current sb values + int m_nTopOffset; + float m_flLeftOffset; + + // Need save? + bool m_bDirty; + // Currently loaded scene file + char m_szChoreoFile[ 256 ]; + + CChoreoActorWidget *m_pClickedActor; + CChoreoChannelWidget *m_pClickedChannel; + CChoreoEventWidget *m_pClickedEvent; + CChoreoGlobalEventWidget *m_pClickedGlobalEvent; + // Relative to the clicked event + int m_nClickedTag; + CEventAbsoluteTag *m_pClickedAbsoluteTag; + int m_nSelectedEvents; + int m_nClickedX, m_nClickedY; + int m_nClickedChannelCloseCaptionButton; + + // For mouse dragging + // How close to right or left edge the mouse has to be before the wider/shorten + // cursor shows up + enum + { + DRAG_EVENT_EDGE_TOLERANCE = 5, + }; + + // When dragging, what time of action is being performed + enum + { + DRAGTYPE_NONE = 0, + DRAGTYPE_EVENT_MOVE, + DRAGTYPE_EVENT_STARTTIME, + DRAGTYPE_EVENT_STARTTIME_RESCALE, + DRAGTYPE_EVENT_ENDTIME, + DRAGTYPE_EVENT_ENDTIME_RESCALE, + DRAGTYPE_EVENTTAG_MOVE, + DRAGTYPE_EVENTABSTAG_MOVE, + DRAGTYPE_SCRUBBER, + DRAGTYPE_SCENE_ENDTIME, + DRAGTYPE_RESCALELEFT, + DRAGTYPE_RESCALERIGHT, + }; + + float m_flScrub; + float m_flScrubTarget; + + bool m_bDragging; + int m_xStart; + int m_yStart; + + bool m_bUseBounds; + int m_nMinX; + int m_nMaxX; + + struct CFocusRect + { + RECT m_rcOrig; + RECT m_rcFocus; + }; + CUtlVector < CFocusRect > m_FocusRects; + + int m_nDragType; + HCURSOR m_hPrevCursor; + + struct FLYOVER + { + CChoreoActorWidget *a; + CChoreoChannelWidget *c; + CChoreoEventWidget *e; + CChoreoGlobalEventWidget *ge; + int tag; + CEventAbsoluteTag *at; + int ccbutton; + }; + + FLYOVER m_Flyover; + + bool m_bCanDraw; + + struct CVUndo + { + CChoreoScene *undo; + CChoreoScene *redo; + char *udescription; + char *rdescription; + }; + + CUtlVector< CVUndo * > m_UndoStack; + int m_nUndoLevel; + bool m_bRedoPending; + + bool m_bProcessSequences; + + float m_flLastMouseClickTime; + + bool m_bSuppressLayout; + + bool m_bRampOnly; + float m_flScrubberTimeOffset; + + bool m_bShowCloseCaptionData; + + bool m_bForceProcess; + + // cached version of the local directory when a scene is loaded + CUtlVector< CUtlString > m_nextFileList; +}; + +extern CChoreoView *g_pChoreoView; + +template< class T > +void CChoreoView::SetPreservedTimeZoom( T *other, int tz ) +{ + POINT pt; + ::GetCursorPos( &pt ); + ::ScreenToClient( (HWND)other->getHandle(), &pt ); + + // Now figure out time under cursor at old zoom scale + float t = other->GetTimeValueForMouse( pt.x, true ); + + // Call CChoreoView's version + SetTimeZoom( other->GetToolName(), tz, false ); + + // Now figure out tie under pt.x + float newT = other->GetTimeValueForMouse( pt.x, true ); + if ( newT != t ) + { + // We need to scroll over a bit + float pps = other->GetPixelsPerSecond(); + float movePixels = pps * ( newT - t ); + + float newOffset = other->m_flLeftOffset - movePixels; + if ( newOffset < 0.0f ) + { + newOffset = 0; + } + + float ed = other->GetEventEndTime(); + float flLastPixel = ed * pps; + if ( newOffset + other->w2() > flLastPixel ) + { + newOffset = flLastPixel - other->w2(); + } + + other->m_flLeftOffset = newOffset; + other->m_pHorzScrollBar->setValue( (int)( other->m_flLeftOffset ) ); + } + + other->RepositionHSlider(); +} + +template< class T > +int CChoreoView::HandleZoomKey( T *other, int keyCode ) +{ + int iret = 1; + switch ( keyCode ) + { + default: + { + iret = 0; + } + break; + + case VK_HOME: + { + other->MoveTimeSliderToPos( 0 ); + } + break; + case VK_END: + { + float maxtime = other->GetEventEndTime(); + int pixels = max( 0, (int)( maxtime * other->GetPixelsPerSecond() ) - other->w2() ); + other->MoveTimeSliderToPos( pixels ); + } + break; + case VK_PRIOR: // PgUp + { + int window = other->w2(); + other->m_flLeftOffset = max( other->m_flLeftOffset - (float)window, 0.0f ); + other->MoveTimeSliderToPos( (int)other->m_flLeftOffset ); + } + break; + case VK_NEXT: // PgDown + { + int window = other->w2(); + float maxtime = other->GetEventEndTime(); + int pixels = max( 0, (int)( maxtime * other->GetPixelsPerSecond() ) - other->w2() ); + other->m_flLeftOffset = min( other->m_flLeftOffset + (float)window, (float)pixels ); + other->MoveTimeSliderToPos( (int)other->m_flLeftOffset ); + } + break; + } + + return iret; +} + + +float SnapTime( float input, float granularity ); + +class StudioModel; +StudioModel *FindAssociatedModel( CChoreoScene *scene, CChoreoActor *a ); + +#endif // CHOREOVIEW_H diff --git a/utils/hlfaceposer/choreoviewcolors.h b/utils/hlfaceposer/choreoviewcolors.h new file mode 100644 index 0000000..1492a93 --- /dev/null +++ b/utils/hlfaceposer/choreoviewcolors.h @@ -0,0 +1,50 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef CHOREOVIEWCOLORS_H +#define CHOREOVIEWCOLORS_H +#ifdef _WIN32 +#pragma once +#endif + +// Defines colors for choreo view +#define COLOR_CHOREO_BACKGROUND RGB( 240, 240, 220 ) +#define COLOR_CHOREO_DARKBACKGROUND RGB( 230, 230, 200 ) +#define COLOR_CHOREO_TEXT RGB( 0, 0, 0 ) +#define COLOR_CHOREO_LIGHTTEXT RGB( 180, 180, 120 ) + +#define COLOR_CHOREO_EVENT RGB( 250, 100, 100 ) +#define COLOR_CHOREO_EVENT_TRIGGERED RGB( 32, 130, 150 ) +#define COLOR_CHOREO_EVENT_SELECTED RGB( 220, 150, 150 ) + +#define COLOR_CHOREO_DIVIDER RGB( 63, 63, 63 ) +#define COLOR_CHOREO_ACTORNAME RGB( 80, 150, 150 ) +#define COLOR_CHOREO_ACTORNAME_INACTIVE RGB( 150, 150, 150 ) +#define COLOR_CHOREO_ACTORLINE RGB( 200, 200, 175 ) +#define COLOR_CHOREO_CHANNELNAME RGB( 150, 150, 100 ) +#define COLOR_CHOREO_CHANNELLINE RGB( 63, 63, 31 ) +#define COLOR_CHOREO_SEGMENTDIVIDER RGB( 63, 120, 255 ) +#define COLOR_CHOREO_SEGMENTDIVIDER_BG RGB( 170, 190, 230 ) + +#define COLOR_CHOREO_LOOPPOINT RGB( 255, 120, 255 ) +#define COLOR_CHOREO_LOOPPOINT_BG RGB( 255, 100, 150 ) +#define COLOR_CHOREO_LOOPPOINT_START_BG RGB( 255, 150, 150 ) + +#define COLOR_CHOREO_STOPPOINT RGB( 255, 31, 31 ) +#define COLOR_CHOREO_STOPPOINT_BG RGB( 255, 0, 0 ) + +#define COLOR_CHOREO_PLAYBACKTICK RGB( 180, 31, 31 ) +#define COLOR_CHOREO_TIMELINE RGB( 31, 31, 127 ) +#define COLOR_CHOREO_ENDTIME RGB( 0, 0, 255 ) +#define COLOR_CHOREO_PLAYBACKTICKTEXT RGB( 127, 0, 0 ) +#define COLOR_CHOREO_TICKAB RGB( 31, 120, 31 ) + +#define COLOR_INFO_BACKGROUND RGB( 240, 240, 240 ) +#define COLOR_INFO_TEXT RGB( 63, 31, 0 ) +#define COLOR_INFO_BORDER RGB( 100, 100, 250 ) + +#endif // CHOREOVIEWCOLORS_H diff --git a/utils/hlfaceposer/choreowidget.cpp b/utils/hlfaceposer/choreowidget.cpp new file mode 100644 index 0000000..4f08097 --- /dev/null +++ b/utils/hlfaceposer/choreowidget.cpp @@ -0,0 +1,163 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include <stdio.h> +#include "choreowidget.h" +#include "choreoview.h" + +// Static elements +CChoreoScene *CChoreoWidget::m_pScene = NULL; +CChoreoView *CChoreoWidget::m_pView = NULL; + +static int widgets = 0; +//----------------------------------------------------------------------------- +// CChoreoWidget new/delete +// All fields in the object are all initialized to 0. +//----------------------------------------------------------------------------- +void *CChoreoWidget::operator new( size_t stAllocateBlock ) +{ + widgets++; + // call into engine to get memory + Assert( stAllocateBlock != 0 ); + return calloc( 1, stAllocateBlock ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pMem - +//----------------------------------------------------------------------------- +void CChoreoWidget::operator delete( void *pMem ) +{ + widgets--; + // set the memory to a known value + int size = _msize( pMem ); + memset( pMem, 0xfe, size ); + + // get the engine to free the memory + free( pMem ); +} + +//----------------------------------------------------------------------------- +// Purpose: Construct widget, all widgets clip their children and brethren +// Input : *parent - +//----------------------------------------------------------------------------- +CChoreoWidget::CChoreoWidget( CChoreoWidget *parent ) +{ + m_bSelected = false; + m_pParent = parent; + m_bVisible = true; + + m_rcBounds.left = m_rcBounds.right = m_rcBounds.top = m_rcBounds.bottom = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CChoreoWidget::~CChoreoWidget( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Default implementation, just return base row height +//----------------------------------------------------------------------------- +int CChoreoWidget::GetItemHeight( void ) +{ + return m_pView->GetRowHeight(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +//----------------------------------------------------------------------------- +void CChoreoWidget::LocalToScreen( int& mx, int& my ) +{ + /* + HWND wnd = (HWND)getHandle(); + if ( !wnd ) + return; + + POINT pt; + pt.x = (short)mx; + pt.y = (short)my; + + ClientToScreen( wnd, &pt ); + + mx = pt.x; + my = pt.y; + */ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CChoreoWidget::IsSelected( void ) +{ + return m_bSelected; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : selected - +//----------------------------------------------------------------------------- +void CChoreoWidget::SetSelected( bool selected ) +{ + m_bSelected = selected; +} + +void CChoreoWidget::setBounds( int x, int y, int w, int h ) +{ + m_rcBounds.left = x; + m_rcBounds.right = x + w; + m_rcBounds.top = y; + m_rcBounds.bottom = y + h; +} + +int CChoreoWidget::x( void ) +{ + return m_rcBounds.left; +} + +int CChoreoWidget::y( void ) +{ + return m_rcBounds.top; +} + +int CChoreoWidget::w( void ) +{ + return m_rcBounds.right - m_rcBounds.left; +} + +int CChoreoWidget::h( void ) +{ + return m_rcBounds.bottom - m_rcBounds.top; +} + +CChoreoWidget *CChoreoWidget::getParent( void ) +{ + return m_pParent; +} + +void CChoreoWidget::setVisible( bool visible ) +{ + m_bVisible = visible; +} + +bool CChoreoWidget::getVisible( void ) +{ + return m_bVisible; +} + +void CChoreoWidget::getBounds( RECT& bounds ) +{ + bounds = m_rcBounds; +} + +RECT &CChoreoWidget::getBounds( void ) +{ + return m_rcBounds; +} diff --git a/utils/hlfaceposer/choreowidget.h b/utils/hlfaceposer/choreowidget.h new file mode 100644 index 0000000..275ea40 --- /dev/null +++ b/utils/hlfaceposer/choreowidget.h @@ -0,0 +1,79 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#ifndef CHOREOWIDGET_H +#define CHOREOWIDGET_H +#ifdef _WIN32 +#pragma once +#endif + +#include <mxtk/mx.h> + +class CChoreoView; +class CChoreoScene; +class CChoreoWidgetDrawHelper; + +//----------------------------------------------------------------------------- +// Purpose: CChoreoWidgets are mxWindows that we show in the Choreography view area +// so that we can manipulate them with the mouse. The widgets follow the scene +// hierarchy of actors/channels/events, without having to hang off of the underlying +// data. They are just for the UI. +//----------------------------------------------------------------------------- +class CChoreoWidget +{ +public: + // memory handling, uses calloc so members are zero'd out on instantiation + void *operator new( size_t stAllocateBlock ); + void operator delete( void *pMem ); + + CChoreoWidget( CChoreoWidget *parent ); + virtual ~CChoreoWidget( void ); + + // All widgets implement these pure virtuals + + // Called to force a widget to create its children based on the scene data + virtual void Create( void ) = 0; + // Force widget to redo layout of self and any children + virtual void Layout( RECT& rc ) = 0; + // Redraw the widget + virtual void redraw( CChoreoWidgetDrawHelper& drawHelper ) = 0; + // Don't overdraw background + virtual bool PaintBackground( void ) { return false; }; + // Determine height to reserver for widget ( Actors can be expanded or collapsed, e.g. ) + virtual int GetItemHeight( void ); + + virtual void LocalToScreen( int& mx, int& my ); + + virtual bool IsSelected( void ); + virtual void SetSelected( bool selected ); + + virtual void setBounds( int x, int y, int w, int h ); + virtual int x( void ); + virtual int y( void ); + virtual int w( void ); + virtual int h( void ); + virtual CChoreoWidget *getParent( void ); + virtual void setVisible( bool visible ); + virtual bool getVisible( void ); + + virtual void getBounds( RECT& bounds ); + virtual RECT &getBounds( void ); + + // Globally accessible scene and view pointers + static CChoreoScene *m_pScene; + static CChoreoView *m_pView; + +private: + bool m_bSelected; + bool m_bVisible; + + RECT m_rcBounds; + +protected: + CChoreoWidget *m_pParent; +}; + +#endif // CHOREOWIDGET_H diff --git a/utils/hlfaceposer/choreowidgetdrawhelper.cpp b/utils/hlfaceposer/choreowidgetdrawhelper.cpp new file mode 100644 index 0000000..86796d2 --- /dev/null +++ b/utils/hlfaceposer/choreowidgetdrawhelper.cpp @@ -0,0 +1,1069 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "tier0/dbg.h" +#include <stdio.h> +#include "choreoview.h" +#include "choreowidgetdrawhelper.h" +#include "choreoviewcolors.h" + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *widget - +//----------------------------------------------------------------------------- +CChoreoWidgetDrawHelper::CChoreoWidgetDrawHelper( mxWindow *widget ) +{ + Init( widget, 0, 0, 0, 0, COLOR_CHOREO_BACKGROUND, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *widget - +//----------------------------------------------------------------------------- +CChoreoWidgetDrawHelper::CChoreoWidgetDrawHelper( mxWindow *widget, COLORREF bgColor ) +{ + Init( widget, 0, 0, 0, 0, bgColor, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *widget - +// bounds - +//----------------------------------------------------------------------------- +CChoreoWidgetDrawHelper::CChoreoWidgetDrawHelper( mxWindow *widget, RECT& bounds ) +{ + Init( widget, bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top, COLOR_CHOREO_BACKGROUND, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *widget - +// bounds - +//----------------------------------------------------------------------------- +CChoreoWidgetDrawHelper::CChoreoWidgetDrawHelper( mxWindow *widget, RECT& bounds, bool noPageFlip ) +{ + Init( widget, bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top, COLOR_CHOREO_BACKGROUND, noPageFlip ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *widget - +// x - +// y - +// w - +// h - +//----------------------------------------------------------------------------- +CChoreoWidgetDrawHelper::CChoreoWidgetDrawHelper( mxWindow *widget, int x, int y, int w, int h, COLORREF bgColor ) +{ + Init( widget, x, y, w, h, bgColor, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *widget - +// bounds - +// bgColor - +//----------------------------------------------------------------------------- +CChoreoWidgetDrawHelper::CChoreoWidgetDrawHelper( mxWindow *widget, RECT& bounds, COLORREF bgColor ) +{ + Init( widget, bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top, bgColor, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *widget - +// x - +// y - +// w - +// h - +//----------------------------------------------------------------------------- +void CChoreoWidgetDrawHelper::Init( mxWindow *widget, int x, int y, int w, int h, COLORREF bgColor, bool noPageFlip ) +{ + m_bNoPageFlip = noPageFlip; + + m_x = x; + m_y = y; + + m_w = w ? w : widget->w2(); + m_h = h ? h : widget->h2(); + + m_hWnd = (HWND)widget->getHandle(); + Assert( m_hWnd ); + m_dcReal = GetDC( m_hWnd ); + m_rcClient.left = m_x; + m_rcClient.top = m_y; + m_rcClient.right = m_x + m_w; + m_rcClient.bottom = m_y + m_h; + + if ( !noPageFlip ) + { + m_dcMemory = CreateCompatibleDC( m_dcReal ); + m_bmMemory = CreateCompatibleBitmap( m_dcReal, m_w, m_h ); + m_bmOld = (HBITMAP)SelectObject( m_dcMemory, m_bmMemory ); + } + else + { + m_dcMemory = m_dcReal; + m_x = m_y = 0; + } + + m_clrOld = SetBkColor( m_dcMemory, bgColor ); + + RECT rcFill = m_rcClient; + OffsetRect( &rcFill, -m_rcClient.left, -m_rcClient.top ); + + if ( !noPageFlip ) + { + HBRUSH br = CreateSolidBrush( bgColor ); + FillRect( m_dcMemory, &rcFill, br ); + DeleteObject( br ); + } + + m_ClipRegion = (HRGN)0; +} + +//----------------------------------------------------------------------------- +// Purpose: Finish up +//----------------------------------------------------------------------------- +CChoreoWidgetDrawHelper::~CChoreoWidgetDrawHelper( void ) +{ + SelectClipRgn( m_dcMemory, NULL ); + + while ( m_ClipRects.Size() > 0 ) + { + StopClipping(); + } + + if ( !m_bNoPageFlip ) + { + BitBlt( m_dcReal, m_x, m_y, m_w, m_h, m_dcMemory, 0, 0, SRCCOPY ); + + SetBkColor( m_dcMemory, m_clrOld ); + + SelectObject( m_dcMemory, m_bmOld ); + DeleteObject( m_bmMemory ); + + DeleteObject( m_dcMemory ); + } + + ReleaseDC( m_hWnd, m_dcReal ); + + ValidateRect( m_hWnd, &m_rcClient ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoWidgetDrawHelper::GetWidth( void ) +{ + return m_w; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CChoreoWidgetDrawHelper::GetHeight( void ) +{ + return m_h; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rc - +//----------------------------------------------------------------------------- +void CChoreoWidgetDrawHelper::GetClientRect( RECT& rc ) +{ + rc.left = rc.top = 0; + rc.right = m_w; + rc.bottom = m_h; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : HDC +//----------------------------------------------------------------------------- +HDC CChoreoWidgetDrawHelper::GrabDC( void ) +{ + return m_dcMemory; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *font - +// pointsize - +// weight - +// maxwidth - +// rcText - +// *fmt - +// ... - +//----------------------------------------------------------------------------- +void CChoreoWidgetDrawHelper::CalcTextRect( const char *font, int pointsize, int weight, int maxwidth, RECT& rcText, const char *fmt, ... ) +{ + va_list args; + static char output[1024]; + + va_start( args, fmt ); + vprintf( fmt, args ); + vsprintf( output, fmt, args ); + + HFONT fnt = CreateFont( + -pointsize, + 0, + 0, + 0, + weight, + FALSE, + FALSE, + FALSE, + ANSI_CHARSET, + OUT_TT_PRECIS, + CLIP_DEFAULT_PRECIS, + ANTIALIASED_QUALITY, + DEFAULT_PITCH, + font ); + + HFONT oldFont = (HFONT)SelectObject( m_dcMemory, fnt ); + + DrawText( m_dcMemory, output, -1, &rcText, DT_LEFT | DT_NOPREFIX | DT_VCENTER | DT_WORDBREAK | DT_CALCRECT ); + + SelectObject( m_dcMemory, oldFont ); + DeleteObject( fnt ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *font - +// pointsize - +// weight - +// *fmt - +// ... - +// Output : int +//----------------------------------------------------------------------------- +int CChoreoWidgetDrawHelper::CalcTextWidth( const char *font, int pointsize, int weight, const char *fmt, ... ) +{ + va_list args; + static char output[1024]; + + va_start( args, fmt ); + vprintf( fmt, args ); + vsprintf( output, fmt, args ); + + HFONT fnt = CreateFont( + -pointsize, + 0, + 0, + 0, + weight, + FALSE, + FALSE, + FALSE, + ANSI_CHARSET, + OUT_TT_PRECIS, + CLIP_DEFAULT_PRECIS, + ANTIALIASED_QUALITY, + DEFAULT_PITCH, + font ); + + HDC screen = GetDC( NULL ); + + HFONT oldFont = (HFONT)SelectObject( screen, fnt ); + + RECT rcText; + rcText.left = rcText.top = 0; + rcText.bottom = pointsize + 5; + rcText.right = rcText.left + 2048; + + DrawText( screen, output, -1, &rcText, DT_LEFT | DT_NOPREFIX | DT_VCENTER | DT_SINGLELINE | DT_CALCRECT ); + + SelectObject( screen, oldFont ); + DeleteObject( fnt ); + + ReleaseDC( NULL, screen ); + + return rcText.right; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *font - +// pointsize - +// weight - +// *fmt - +// ... - +// Output : int +//----------------------------------------------------------------------------- +int CChoreoWidgetDrawHelper::CalcTextWidthW( const char *font, int pointsize, int weight, const wchar_t *fmt, ... ) +{ + va_list args; + static wchar_t output[1024]; + + va_start( args, fmt ); + vwprintf( fmt, args ); + vswprintf( output, fmt, args ); + + HFONT fnt = CreateFont( + -pointsize, + 0, + 0, + 0, + weight, + FALSE, + FALSE, + FALSE, + ANSI_CHARSET, + OUT_TT_PRECIS, + CLIP_DEFAULT_PRECIS, + ANTIALIASED_QUALITY, + DEFAULT_PITCH, + font ); + + HDC screen = GetDC( NULL ); + + HFONT oldFont = (HFONT)SelectObject( screen, fnt ); + + RECT rcText; + rcText.left = rcText.top = 0; + rcText.bottom = pointsize + 5; + rcText.right = rcText.left + 2048; + + DrawTextW( screen, output, -1, &rcText, DT_LEFT | DT_NOPREFIX | DT_VCENTER | DT_SINGLELINE | DT_CALCRECT ); + + SelectObject( screen, oldFont ); + DeleteObject( fnt ); + + ReleaseDC( NULL, screen ); + + return rcText.right; +} +//----------------------------------------------------------------------------- +// Purpose: +// Input : fnt - +// *fmt - +// ... - +// Output : int +//----------------------------------------------------------------------------- +int CChoreoWidgetDrawHelper::CalcTextWidth( HFONT fnt, const char *fmt, ... ) +{ + va_list args; + static char output[1024]; + + va_start( args, fmt ); + vprintf( fmt, args ); + vsprintf( output, fmt, args ); + + HDC screen = GetDC( NULL ); + + HFONT oldFont = (HFONT)SelectObject( screen, fnt ); + + RECT rcText; + rcText.left = rcText.top = 0; + rcText.bottom = 1000; + rcText.right = rcText.left + 2048; + + DrawText( screen, output, -1, &rcText, DT_LEFT | DT_NOPREFIX | DT_VCENTER | DT_SINGLELINE | DT_CALCRECT ); + + SelectObject( screen, oldFont ); + + ReleaseDC( NULL, screen ); + + return rcText.right; +} + +int CChoreoWidgetDrawHelper::CalcTextWidthW( HFONT fnt, const wchar_t *fmt, ... ) +{ + va_list args; + static wchar_t output[1024]; + + va_start( args, fmt ); + vwprintf( fmt, args ); + vswprintf( output, fmt, args ); + + HDC screen = GetDC( NULL ); + + HFONT oldFont = (HFONT)SelectObject( screen, fnt ); + + RECT rcText; + rcText.left = rcText.top = 0; + rcText.bottom = 1000; + rcText.right = rcText.left + 2048; + + DrawTextW( screen, output, -1, &rcText, DT_LEFT | DT_NOPREFIX | DT_VCENTER | DT_SINGLELINE | DT_CALCRECT ); + + SelectObject( screen, oldFont ); + + ReleaseDC( NULL, screen ); + + return rcText.right; +} +//----------------------------------------------------------------------------- +// Purpose: +// Input : *font - +// pointsize - +// weight - +// clr - +// rcText - +// *fmt - +// ... - +//----------------------------------------------------------------------------- +void CChoreoWidgetDrawHelper::DrawColoredText( const char *font, int pointsize, int weight, COLORREF clr, RECT& rcText, const char *fmt, ... ) +{ + va_list args; + static char output[1024]; + + va_start( args, fmt ); + vsprintf( output, fmt, args ); + va_end( args ); + + DrawColoredTextCharset( font, pointsize, weight, ANSI_CHARSET, clr, rcText, output ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *font - +// pointsize - +// weight - +// clr - +// rcText - +// *fmt - +// ... - +//----------------------------------------------------------------------------- +void CChoreoWidgetDrawHelper::DrawColoredTextW( const char *font, int pointsize, int weight, COLORREF clr, RECT& rcText, const wchar_t *fmt, ... ) +{ + va_list args; + static wchar_t output[1024]; + + va_start( args, fmt ); + vswprintf( output, fmt, args ); + va_end( args ); + + DrawColoredTextCharsetW( font, pointsize, weight, ANSI_CHARSET, clr, rcText, output ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : font - +// clr - +// rcText - +// *fmt - +// ... - +//----------------------------------------------------------------------------- +void CChoreoWidgetDrawHelper::DrawColoredText( HFONT font, COLORREF clr, RECT& rcText, const char *fmt, ... ) +{ + va_list args; + static char output[1024]; + + va_start( args, fmt ); + vsprintf( output, fmt, args ); + va_end( args ); + + HFONT oldFont = (HFONT)SelectObject( m_dcMemory, font ); + COLORREF oldColor = SetTextColor( m_dcMemory, clr ); + int oldMode = SetBkMode( m_dcMemory, TRANSPARENT ); + + RECT rcTextOffset = rcText; + OffsetSubRect( rcTextOffset ); + + DrawText( m_dcMemory, output, -1, &rcTextOffset, DT_LEFT | DT_NOPREFIX | DT_VCENTER | DT_SINGLELINE | DT_WORD_ELLIPSIS ); + + SetBkMode( m_dcMemory, oldMode ); + + SetTextColor( m_dcMemory, oldColor ); + + SelectObject( m_dcMemory, oldFont ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : font - +// clr - +// rcText - +// *fmt - +// ... - +//----------------------------------------------------------------------------- +void CChoreoWidgetDrawHelper::DrawColoredTextW( HFONT font, COLORREF clr, RECT& rcText, const wchar_t *fmt, ... ) +{ + va_list args; + static wchar_t output[1024]; + + va_start( args, fmt ); + vswprintf( output, fmt, args ); + va_end( args ); + + HFONT oldFont = (HFONT)SelectObject( m_dcMemory, font ); + COLORREF oldColor = SetTextColor( m_dcMemory, clr ); + int oldMode = SetBkMode( m_dcMemory, TRANSPARENT ); + + RECT rcTextOffset = rcText; + OffsetSubRect( rcTextOffset ); + + DrawTextW( m_dcMemory, output, -1, &rcTextOffset, DT_LEFT | DT_NOPREFIX | DT_VCENTER | DT_SINGLELINE | DT_WORD_ELLIPSIS ); + + SetBkMode( m_dcMemory, oldMode ); + + SetTextColor( m_dcMemory, oldColor ); + + SelectObject( m_dcMemory, oldFont ); +} +//----------------------------------------------------------------------------- +// Purpose: +// Input : *font - +// pointsize - +// weight - +// clr - +// rcText - +// *fmt - +// ... - +//----------------------------------------------------------------------------- +void CChoreoWidgetDrawHelper::DrawColoredTextCharset( const char *font, int pointsize, int weight, DWORD charset, COLORREF clr, RECT& rcText, const char *fmt, ... ) +{ + va_list args; + static char output[1024]; + + va_start( args, fmt ); + vsprintf( output, fmt, args ); + va_end( args ); + + + HFONT fnt = CreateFont( + -pointsize, + 0, + 0, + 0, + weight, + FALSE, + FALSE, + FALSE, + charset, + OUT_TT_PRECIS, + CLIP_DEFAULT_PRECIS, + ANTIALIASED_QUALITY, + DEFAULT_PITCH, + font ); + + HFONT oldFont = (HFONT)SelectObject( m_dcMemory, fnt ); + COLORREF oldColor = SetTextColor( m_dcMemory, clr ); + int oldMode = SetBkMode( m_dcMemory, TRANSPARENT ); + + RECT rcTextOffset = rcText; + OffsetSubRect( rcTextOffset ); + + DrawText( m_dcMemory, output, -1, &rcTextOffset, DT_LEFT | DT_NOPREFIX | DT_VCENTER | DT_SINGLELINE | DT_WORD_ELLIPSIS ); + + SetBkMode( m_dcMemory, oldMode ); + + SetTextColor( m_dcMemory, oldColor ); + + SelectObject( m_dcMemory, oldFont ); + DeleteObject( fnt ); +} + +void CChoreoWidgetDrawHelper::DrawColoredTextCharsetW( const char *font, int pointsize, int weight, DWORD charset, COLORREF clr, RECT& rcText, const wchar_t *fmt, ... ) +{ + va_list args; + static wchar_t output[1024]; + + va_start( args, fmt ); + vswprintf( output, fmt, args ); + va_end( args ); + + + HFONT fnt = CreateFont( + -pointsize, + 0, + 0, + 0, + weight, + FALSE, + FALSE, + FALSE, + charset, + OUT_TT_PRECIS, + CLIP_DEFAULT_PRECIS, + ANTIALIASED_QUALITY, + DEFAULT_PITCH, + font ); + + HFONT oldFont = (HFONT)SelectObject( m_dcMemory, fnt ); + COLORREF oldColor = SetTextColor( m_dcMemory, clr ); + int oldMode = SetBkMode( m_dcMemory, TRANSPARENT ); + + RECT rcTextOffset = rcText; + OffsetSubRect( rcTextOffset ); + + DrawTextW( m_dcMemory, output, -1, &rcTextOffset, DT_LEFT | DT_NOPREFIX | DT_VCENTER | DT_SINGLELINE | DT_WORD_ELLIPSIS ); + + SetBkMode( m_dcMemory, oldMode ); + + SetTextColor( m_dcMemory, oldColor ); + + SelectObject( m_dcMemory, oldFont ); + DeleteObject( fnt ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *font - +// pointsize - +// weight - +// clr - +// rcText - +// *fmt - +// ... - +//----------------------------------------------------------------------------- +void CChoreoWidgetDrawHelper::DrawColoredTextMultiline( const char *font, int pointsize, int weight, COLORREF clr, RECT& rcText, const char *fmt, ... ) +{ + va_list args; + static char output[1024]; + + va_start( args, fmt ); + vprintf( fmt, args ); + vsprintf( output, fmt, args ); + + HFONT fnt = CreateFont( + -pointsize, + 0, + 0, + 0, + weight, + FALSE, + FALSE, + FALSE, + ANSI_CHARSET, + OUT_TT_PRECIS, + CLIP_DEFAULT_PRECIS, + ANTIALIASED_QUALITY, + DEFAULT_PITCH, + font ); + + HFONT oldFont = (HFONT)SelectObject( m_dcMemory, fnt ); + COLORREF oldColor = SetTextColor( m_dcMemory, clr ); + int oldMode = SetBkMode( m_dcMemory, TRANSPARENT ); + + RECT rcTextOffset = rcText; + OffsetSubRect( rcTextOffset ); + + DrawText( m_dcMemory, output, -1, &rcTextOffset, DT_LEFT | DT_NOPREFIX | DT_VCENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS ); + + SetBkMode( m_dcMemory, oldMode ); + + SetTextColor( m_dcMemory, oldColor ); + + SelectObject( m_dcMemory, oldFont ); + DeleteObject( fnt ); +} +//----------------------------------------------------------------------------- +// Purpose: +// Input : r - +// g - +// b - +// style - +// width - +// x1 - +// y1 - +// x2 - +// y2 - +//----------------------------------------------------------------------------- +void CChoreoWidgetDrawHelper::DrawColoredLine( COLORREF clr, int style, int width, int x1, int y1, int x2, int y2 ) +{ + HPEN pen = CreatePen( style, width, clr ); + HPEN oldPen = (HPEN)SelectObject( m_dcMemory, pen ); + MoveToEx( m_dcMemory, x1-m_x, y1-m_y, NULL ); + LineTo( m_dcMemory, x2-m_x, y2-m_y ); + SelectObject( m_dcMemory, oldPen ); + DeleteObject( pen ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : clr - +// style - +// width - +// count - +// *pts - +//----------------------------------------------------------------------------- +void CChoreoWidgetDrawHelper::DrawColoredPolyLine( COLORREF clr, int style, int width, CUtlVector< POINT >& points ) +{ + int c = points.Count(); + if ( c < 2 ) + return; + + HPEN pen = CreatePen( style, width, clr ); + HPEN oldPen = (HPEN)SelectObject( m_dcMemory, pen ); + + POINT *temp = (POINT *)_alloca( c * sizeof( POINT ) ); + Assert( temp ); + int i; + for ( i = 0; i < c; i++ ) + { + POINT *pt = &points[ i ]; + + temp[ i ].x = pt->x - m_x; + temp[ i ].y = pt->y - m_y; + } + + Polyline( m_dcMemory, temp, c ); + + SelectObject( m_dcMemory, oldPen ); + DeleteObject( pen ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : r - +// g - +// b - +// style - +// width - +// x1 - +// y1 - +// x2 - +// y2 - +//----------------------------------------------------------------------------- +POINTL CChoreoWidgetDrawHelper::DrawColoredRamp( COLORREF clr, int style, int width, int x1, int y1, int x2, int y2, float rate, float sustain ) +{ + HPEN pen = CreatePen( style, width, clr ); + HPEN oldPen = (HPEN)SelectObject( m_dcMemory, pen ); + MoveToEx( m_dcMemory, x1-m_x, y1-m_y, NULL ); + int dx = x2 - x1; + int dy = y2 - y1; + + POINTL p; + p.x = 0L; + p.y = 0L; + for (float i = 0.1f; i <= 1.09f; i += 0.1f) + { + float j = 3.0f * i * i - 2.0f * i * i * i; + p.x = x1+(int)(dx*i*(1.0f-rate))-m_x; + p.y = y1+(int)(dy*sustain*j)-m_y; + LineTo( m_dcMemory, p.x, p.y ); + } + SelectObject( m_dcMemory, oldPen ); + DeleteObject( pen ); + + return p; +}; + +//----------------------------------------------------------------------------- +// Purpose: Draw a filled rect +// Input : clr - +// x1 - +// y1 - +// x2 - +// y2 - +//----------------------------------------------------------------------------- +void CChoreoWidgetDrawHelper::DrawFilledRect( COLORREF clr, RECT& rc ) +{ + RECT rcCopy = rc; + + HBRUSH br = CreateSolidBrush( clr ); + OffsetSubRect( rcCopy ); + FillRect( m_dcMemory, &rcCopy, br ); + DeleteObject( br ); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw a filled rect +// Input : clr - +// x1 - +// y1 - +// x2 - +// y2 - +//----------------------------------------------------------------------------- +void CChoreoWidgetDrawHelper::DrawFilledRect( COLORREF clr, int x1, int y1, int x2, int y2 ) +{ + HBRUSH br = CreateSolidBrush( clr ); + RECT rc; + rc.left = x1; + rc.right = x2; + rc.top = y1; + rc.bottom = y2; + OffsetSubRect( rc ); + FillRect( m_dcMemory, &rc, br ); + DeleteObject( br ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : clr - +// style - +// width - +// rc - +//----------------------------------------------------------------------------- +void CChoreoWidgetDrawHelper::DrawOutlinedRect( COLORREF clr, int style, int width, RECT& rc ) +{ + DrawOutlinedRect( clr, style, width, rc.left, rc.top, rc.right, rc.bottom ); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw an outlined rect +// Input : clr - +// style - +// width - +// x1 - +// y1 - +// x2 - +// y2 - +//----------------------------------------------------------------------------- +void CChoreoWidgetDrawHelper::DrawOutlinedRect( COLORREF clr, int style, int width, int x1, int y1, int x2, int y2 ) +{ + HPEN oldpen, pen; + HBRUSH oldbrush, brush; + + pen = CreatePen( PS_SOLID, width, clr ); + oldpen = (HPEN)SelectObject( m_dcMemory, pen ); + + brush = (HBRUSH)GetStockObject( NULL_BRUSH ); + oldbrush = (HBRUSH)SelectObject( m_dcMemory, brush ); + + RECT rc; + rc.left = x1; + rc.right = x2; + rc.top = y1; + rc.bottom = y2; + OffsetSubRect( rc); + + Rectangle( m_dcMemory, rc.left, rc.top, rc.right, rc.bottom ); + + SelectObject( m_dcMemory, oldbrush ); + DeleteObject( brush ); + SelectObject( m_dcMemory, oldpen ); + DeleteObject( pen ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : x1 - +// y1 - +// x2 - +// y2 - +// clr - +// thickness - +//----------------------------------------------------------------------------- +void CChoreoWidgetDrawHelper::DrawLine( int x1, int y1, int x2, int y2, COLORREF clr, int thickness ) +{ + HPEN oldpen, pen; + HBRUSH oldbrush, brush; + + pen = CreatePen( PS_SOLID, thickness, clr ); + oldpen = (HPEN)SelectObject( m_dcMemory, pen ); + + brush = (HBRUSH)GetStockObject( NULL_BRUSH ); + oldbrush = (HBRUSH)SelectObject( m_dcMemory, brush ); + + // Offset + x1 -= m_x; + x2 -= m_x; + y1 -= m_y; + y2 -= m_y; + + MoveToEx( m_dcMemory, x1, y1, NULL ); + LineTo( m_dcMemory, x2, y2 ); + + SelectObject( m_dcMemory, oldbrush ); + DeleteObject( brush ); + SelectObject( m_dcMemory, oldpen ); + DeleteObject( pen ); +} +//----------------------------------------------------------------------------- +// Purpose: +// Input : rc - +// fillr - +// fillg - +// fillb - +//----------------------------------------------------------------------------- +void CChoreoWidgetDrawHelper::DrawTriangleMarker( RECT& rc, COLORREF fill, bool inverted /*= false*/ ) +{ + POINT region[3]; + int cPoints = 3; + + if ( !inverted ) + { + region[ 0 ].x = rc.left - m_x; + region[ 0 ].y = rc.top - m_y; + + region[ 1 ].x = rc.right - m_x; + region[ 1 ].y = rc.top - m_y; + + region[ 2 ].x = ( ( rc.left + rc.right ) / 2 ) - m_x; + region[ 2 ].y = rc.bottom - m_y; + } + else + { + region[ 0 ].x = rc.left - m_x; + region[ 0 ].y = rc.bottom - m_y; + + region[ 1 ].x = rc.right - m_x; + region[ 1 ].y = rc.bottom - m_y; + + region[ 2 ].x = ( ( rc.left + rc.right ) / 2 ) - m_x; + region[ 2 ].y = rc.top - m_y; + } + + HRGN rgn = CreatePolygonRgn( region, cPoints, ALTERNATE ); + + int oldPF = SetPolyFillMode( m_dcMemory, ALTERNATE ); + + HBRUSH brFace = CreateSolidBrush( fill ); + + FillRgn( m_dcMemory, rgn, brFace ); + + DeleteObject( brFace ); + + SetPolyFillMode( m_dcMemory, oldPF ); + + DeleteObject( rgn ); +} + +void CChoreoWidgetDrawHelper::StartClipping( RECT& clipRect ) +{ + RECT fixed = clipRect; + OffsetSubRect( fixed ); + + m_ClipRects.AddToTail( fixed ); + + ClipToRects(); +} + +void CChoreoWidgetDrawHelper::StopClipping( void ) +{ + Assert( m_ClipRects.Size() > 0 ); + if ( m_ClipRects.Size() <= 0 ) + return; + + m_ClipRects.Remove( m_ClipRects.Size() - 1 ); + + ClipToRects(); +} + +void CChoreoWidgetDrawHelper::ClipToRects( void ) +{ + SelectClipRgn( m_dcMemory, NULL ); + if ( m_ClipRegion ) + { + DeleteObject( m_ClipRegion ); + m_ClipRegion = HRGN( 0 ); + } + + if ( m_ClipRects.Size() > 0 ) + { + RECT rc = m_ClipRects[ 0 ]; + m_ClipRegion = CreateRectRgn( rc.left, rc.top, rc.right, rc.bottom ); + for ( int i = 1; i < m_ClipRects.Size(); i++ ) + { + RECT add = m_ClipRects[ i ]; + + HRGN addIn = CreateRectRgn( add.left, add.top, add.right, add.bottom ); + HRGN result = CreateRectRgn( 0, 0, 100, 100 ); + + CombineRgn( result, m_ClipRegion, addIn, RGN_AND ); + + DeleteObject( m_ClipRegion ); + DeleteObject( addIn ); + + m_ClipRegion = result; + } + } + + SelectClipRgn( m_dcMemory, m_ClipRegion ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rc - +//----------------------------------------------------------------------------- +void CChoreoWidgetDrawHelper::OffsetSubRect( RECT& rc ) +{ + OffsetRect( &rc, -m_x, -m_y ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : br - +// rc - +//----------------------------------------------------------------------------- +void CChoreoWidgetDrawHelper::DrawFilledRect( HBRUSH br, RECT& rc ) +{ + RECT rcFill = rc; + OffsetSubRect( rcFill ); + FillRect( m_dcMemory, &rcFill, br ); +} + +void CChoreoWidgetDrawHelper::DrawCircle( COLORREF clr, int x, int y, int radius, bool filled /*= true*/ ) +{ + RECT rc; + int ihalfradius = radius >> 1; + + rc.left = x - ihalfradius; + rc.right = rc.left + 2 * ihalfradius; + rc.top = y - ihalfradius; + rc.bottom = y + 2 * ihalfradius - 1; + + OffsetSubRect( rc ); + + HPEN pen = CreatePen( PS_SOLID, 1, clr ); + HBRUSH br = CreateSolidBrush( clr ); + + HPEN oldPen = (HPEN)SelectObject( m_dcMemory, pen ); + HBRUSH oldBr = (HBRUSH)SelectObject( m_dcMemory, br ); + + if ( filled ) + { + Ellipse( m_dcMemory, rc.left, rc.top, rc.right, rc.bottom ); + } + else + { + Arc( m_dcMemory, rc.left, rc.top, rc.right, rc.bottom, + rc.left, rc.top, rc.left, rc.top ); + } + + SelectObject( m_dcMemory, oldPen ); + SelectObject( m_dcMemory, oldBr ); + + DeleteObject( pen ); + DeleteObject( br ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rc - +// clr1 - +// clr2 - +// vertical - +//----------------------------------------------------------------------------- +void CChoreoWidgetDrawHelper::DrawGradientFilledRect( RECT& rc, COLORREF clr1, COLORREF clr2, bool vertical ) +{ + RECT rcDraw = rc; + OffsetRect( &rcDraw, -m_x, -m_y ); + + TRIVERTEX vert[2] ; + GRADIENT_RECT gradient_rect; + vert[0].x = rcDraw.left; + vert[0].y = rcDraw.top; + vert[0].Red = GetRValue( clr1 ) << 8; + vert[0].Green = GetGValue( clr1 ) << 8; + vert[0].Blue = GetBValue( clr1 ) << 8; + vert[0].Alpha = 0x0000; + + vert[1].x = rcDraw.right; + vert[1].y = rcDraw.bottom; + vert[1].Red = GetRValue( clr2 ) << 8; + vert[1].Green = GetGValue( clr2 ) << 8; + vert[1].Blue = GetBValue( clr2 ) << 8; + vert[1].Alpha = 0x0000; + + gradient_rect.UpperLeft = 0; + gradient_rect.LowerRight = 1; + + GradientFill( + m_dcMemory, + vert, 2, + &gradient_rect, 1, + vertical ? GRADIENT_FILL_RECT_V : GRADIENT_FILL_RECT_H ); +} diff --git a/utils/hlfaceposer/choreowidgetdrawhelper.h b/utils/hlfaceposer/choreowidgetdrawhelper.h new file mode 100644 index 0000000..ca6b06b --- /dev/null +++ b/utils/hlfaceposer/choreowidgetdrawhelper.h @@ -0,0 +1,122 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef CHOREOWIDGETDRAWHELPER_H +#define CHOREOWIDGETDRAWHELPER_H +#ifdef _WIN32 +#pragma once +#endif + +#include <mxtk/mx.h> +#include "choreowidget.h" +#include "utlvector.h" + +//----------------------------------------------------------------------------- +// Purpose: Helper class that automagically sets up and destroys a memory device- +// context for flicker-free refershes +//----------------------------------------------------------------------------- +class CChoreoWidgetDrawHelper +{ +public: + // Construction/destruction + CChoreoWidgetDrawHelper( mxWindow *widget); + CChoreoWidgetDrawHelper( mxWindow *widget, COLORREF bgColor ); + CChoreoWidgetDrawHelper( mxWindow *widget, int x, int y, int w, int h, COLORREF bgColor ); + CChoreoWidgetDrawHelper( mxWindow *widget, RECT& bounds ); + CChoreoWidgetDrawHelper( mxWindow *widget, RECT& bounds, COLORREF bgColor ); + + CChoreoWidgetDrawHelper( mxWindow *widget, RECT& bounds, bool noPageFlip ); + + virtual ~CChoreoWidgetDrawHelper( void ); + + // Allow caller to draw onto the memory dc, too + HDC GrabDC( void ); + + // Compute text size + static int CalcTextWidth( const char *font, int pointsize, int weight, PRINTF_FORMAT_STRING const char *fmt, ... ); + static int CalcTextWidth( HFONT font, PRINTF_FORMAT_STRING const char *fmt, ... ); + + static int CalcTextWidthW( const char *font, int pointsize, int weight, PRINTF_FORMAT_STRING const wchar_t *fmt, ... ); + static int CalcTextWidthW( HFONT font, PRINTF_FORMAT_STRING const wchar_t *fmt, ... ); + + void DrawColoredTextW( const char *font, int pointsize, int weight, COLORREF clr, RECT& rcText, PRINTF_FORMAT_STRING const wchar_t *fmt, ... ); + void DrawColoredTextW( HFONT font, COLORREF clr, RECT& rcText, PRINTF_FORMAT_STRING const wchar_t *fmt, ... ); + void DrawColoredTextCharsetW( const char *font, int pointsize, int weight, DWORD charset, COLORREF clr, RECT& rcText, PRINTF_FORMAT_STRING const wchar_t *fmt, ... ); + + void CalcTextRect( const char *font, int pointsize, int weight, int maxwidth, RECT& rcText, PRINTF_FORMAT_STRING const char *fmt, ... ); + + // Draw text + void DrawColoredText( const char *font, int pointsize, int weight, COLORREF clr, RECT& rcText, PRINTF_FORMAT_STRING const char *fmt, ... ); + void DrawColoredText( HFONT font, COLORREF clr, RECT& rcText, PRINTF_FORMAT_STRING const char *fmt, ... ); + void DrawColoredTextCharset( const char *font, int pointsize, int weight, DWORD charset, COLORREF clr, RECT& rcText, PRINTF_FORMAT_STRING const char *fmt, ... ); + void DrawColoredTextMultiline( const char *font, int pointsize, int weight, COLORREF clr, RECT& rcText, PRINTF_FORMAT_STRING const char *fmt, ... ); + // Draw a line + void DrawColoredLine( COLORREF clr, int style, int width, int x1, int y1, int x2, int y2 ); + void DrawColoredPolyLine( COLORREF clr, int style, int width, CUtlVector< POINT >& points ); + + // Draw a blending ramp + POINTL DrawColoredRamp( COLORREF clr, int style, int width, int x1, int y1, int x2, int y2, float rate, float sustain ); + // Draw a filled rect + void DrawFilledRect( COLORREF clr, int x1, int y1, int x2, int y2 ); + // Draw an outlined rect + void DrawOutlinedRect( COLORREF clr, int style, int width, int x1, int y1, int x2, int y2 ); + void DrawOutlinedRect( COLORREF clr, int style, int width, RECT& rc ); + + void DrawFilledRect( HBRUSH br, RECT& rc ); + void DrawFilledRect( COLORREF clr, RECT& rc ); + + void DrawGradientFilledRect( RECT& rc, COLORREF clr1, COLORREF clr2, bool vertical ); + + void DrawLine( int x1, int y1, int x2, int y2, COLORREF clr, int thickness ); + + // Draw a triangle + void DrawTriangleMarker( RECT& rc, COLORREF fill, bool inverted = false ); + + void DrawCircle( COLORREF clr, int x, int y, int radius, bool filled = true ); + + // Get width/height of draw area + int GetWidth( void ); + int GetHeight( void ); + + // Get client rect for drawing + void GetClientRect( RECT& rc ); + + void StartClipping( RECT& clipRect ); + void StopClipping( void ); + + // Remap rect if we're using a clipped viewport + void OffsetSubRect( RECT& rc ); + +private: + // Internal initializer + void Init( mxWindow *widget, int x, int y, int w, int h, COLORREF bgColor, bool noPageFlip ); + + void ClipToRects( void ); + + // The window we are drawing on + HWND m_hWnd; + // The final DC + HDC m_dcReal; + // The working DC + HDC m_dcMemory; + // Client area and offsets + RECT m_rcClient; + int m_x, m_y; + int m_w, m_h; + // Bitmap for drawing in the memory DC + HBITMAP m_bmMemory; + HBITMAP m_bmOld; + // Remember the original default color + COLORREF m_clrOld; + + CUtlVector < RECT > m_ClipRects; + HRGN m_ClipRegion; + + bool m_bNoPageFlip; +}; + +#endif // CHOREOWIDGETDRAWHELPER_H diff --git a/utils/hlfaceposer/controlpanel.cpp b/utils/hlfaceposer/controlpanel.cpp new file mode 100644 index 0000000..e97cc7b --- /dev/null +++ b/utils/hlfaceposer/controlpanel.cpp @@ -0,0 +1,976 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#include "ControlPanel.h" +#include "ViewerSettings.h" +#include "StudioModel.h" +#include "IStudioRender.h" +#include "MatSysWin.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <mxtk/mx.h> +#include <mxtk/mxBmp.h> +#include "FlexPanel.h" +#include "mxExpressionTray.h" +#include "PhonemeEditor.h" +#include "hlfaceposer.h" +#include "expclass.h" +#include "mxExpressionTab.h" +#include "ExpressionTool.h" +#include "MDLViewer.h" +#include "choreowidgetdrawhelper.h" +#include "faceposer_models.h" +#include "ifaceposerworkspace.h" +#include "choreoview.h" +#include "GestureTool.h" +#include "RampTool.h" +#include "SceneRampTool.h" +#include "phonemeextractor/PhonemeExtractor.h" +#include "tier1/KeyValues.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" +extern char g_appTitle[]; + +ControlPanel *g_pControlPanel = 0; + +//----------------------------------------------------------------------------- +// Purpose: A simple subclass so we can paint the window background +//----------------------------------------------------------------------------- +class CControlPanelTabWindow : public mxWindow +{ +public: + CControlPanelTabWindow( mxWindow *parent, int x, int y, int w, int h ) : + mxWindow( parent, x, y, w, h ) + { + FacePoser_AddWindowStyle( this, WS_CLIPSIBLINGS | WS_CLIPCHILDREN ); + }; + + virtual bool PaintBackground( void ) + { + CChoreoWidgetDrawHelper drawHelper( this ); + + RECT rc; + drawHelper.GetClientRect( rc ); + + drawHelper.DrawFilledRect( GetSysColor( COLOR_BTNFACE ), rc ); + return false; + } +}; + +ControlPanel::ControlPanel (mxWindow *parent) +: IFacePoserToolWindow( "ControlPanel", "Control Panel" ), mxWindow( parent, 0, 0, 0, 0 ), tab( 0 ) +{ + // create tabcontrol with subdialog windows + tab = new mxTab (this, 0, 0, 0, 0, IDC_TAB); + + CControlPanelTabWindow *wRender = new CControlPanelTabWindow (this, 0, 0, 0, 0); + tab->add (wRender, "Render"); + cRenderMode = new mxChoice (wRender, 5, 5, 100, 22, IDC_RENDERMODE); + cRenderMode->add ("Wireframe"); + cRenderMode->add ("Flatshaded"); + cRenderMode->add ("Smoothshaded"); + cRenderMode->add ("Textured"); + cRenderMode->select (3); + mxToolTip::add (cRenderMode, "Select Render Mode"); + + slModelGap = new mxSlider( wRender, 220, 5, 140, 20, IDC_MODELSPACING ); + slModelGap->setRange( 0.0f, 64.0f, 256 ); + slModelGap->setValue( 16 ); + mxToolTip::add (slModelGap, "Select Model Spacing" ); + new mxLabel (wRender, 220, 25, 140, 20, "Model Spacing"); + + cbAllWindowsDriveSpeech = new mxCheckBox( wRender, 220, 45, 140, 20, "All tools drive mouth", IDC_TOOLSDRIVEMOUTH ); + cbAllWindowsDriveSpeech->setChecked( g_viewerSettings.faceposerToolsDriveMouth ); + + cbGround = new mxCheckBox (wRender, 110, 5, 100, 20, "Ground", IDC_GROUND); + cbGround->setEnabled( true ); + cbMovement = new mxCheckBox (wRender, 110, 25, 100, 20, "Movement", IDC_MOVEMENT); + cbMovement->setEnabled( false ); + cbBackground = new mxCheckBox (wRender, 110, 45, 100, 20, "Background", IDC_BACKGROUND); + cbBackground->setEnabled( false ); + new mxCheckBox (wRender, 110, 65, 100, 20, "Hit Boxes", IDC_HITBOXES); + new mxCheckBox (wRender, 5, 65, 100, 20, "Bones", IDC_BONES); + mxCheckBox *cbAttachments = new mxCheckBox (wRender, 5, 45, 100, 20, "Attachments", IDC_ATTACHMENTS); + cbAttachments->setEnabled( false ); + + CControlPanelTabWindow *wSequence = new CControlPanelTabWindow (this, 0, 0, 0, 0); + tab->add (wSequence, "Sequence"); + cSequence = new mxChoice (wSequence, 5, 5, 200, 22, IDC_SEQUENCE); + mxToolTip::add (cSequence, "Select Sequence"); + + + slSpeedScale = new mxSlider (wSequence, 5, 32, 200, 18, IDC_SPEEDSCALE); + slSpeedScale->setRange (0.0, 5.0 ); + slSpeedScale->setValue (0.0); + mxToolTip::add (slSpeedScale, "Speed Scale"); + lSpeedScale = new mxLabel( wSequence, 5, 50, 200, 18 ); + lSpeedScale->setLabel( "Speed scale" ); + + CControlPanelTabWindow *wBody = new CControlPanelTabWindow (this, 0, 0, 0, 0); + tab->add (wBody, "Body"); + cBodypart = new mxChoice (wBody, 5, 5, 100, 22, IDC_BODYPART); + mxToolTip::add (cBodypart, "Choose a bodypart"); + cSubmodel = new mxChoice (wBody, 110, 5, 100, 22, IDC_SUBMODEL); + mxToolTip::add (cSubmodel, "Choose a submodel of current bodypart"); + cController = new mxChoice (wBody, 5, 30, 100, 22, IDC_CONTROLLER); + mxToolTip::add (cController, "Choose a bone controller"); + slController = new mxSlider (wBody, 105, 32, 100, 18, IDC_CONTROLLERVALUE); + slController->setRange (0, 45); + mxToolTip::add (slController, "Change current bone controller value"); + lModelInfo1 = new mxLabel (wBody, 220, 5, 120, 100, "No Model."); + lModelInfo2 = new mxLabel (wBody, 340, 5, 120, 100, ""); + cSkin = new mxChoice (wBody, 5, 55, 100, 22, IDC_SKINS); + mxToolTip::add (cSkin, "Choose a skin family"); +} + +ControlPanel::~ControlPanel() +{ +} + +bool ControlPanel::CanClose( void ) +{ + workspacefiles->StartStoringFiles( IWorkspaceFiles::EXPRESSION ); + for ( int i = 0 ; i < expressions->GetNumClasses(); i++ ) + { + CExpClass *cl = expressions->GetClass( i ); + if ( cl ) + { + workspacefiles->StoreFile( IWorkspaceFiles::EXPRESSION, cl->GetFileName() ); + } + } + workspacefiles->FinishStoringFiles( IWorkspaceFiles::EXPRESSION ); + // Now close them all, or abort exit if user doesn't want to close any that have changed + return Closeall(); +} + +void ControlPanel::OnDelete() +{ +} + +void ControlPanel::PositionControls( int width, int height ) +{ + if ( tab ) + { + tab->setBounds( 0, GetCaptionHeight(), width, height ); + } +} + +void ControlPanel::redraw() +{ + if ( !ToolCanDraw() ) + return; + + CChoreoWidgetDrawHelper helper( this, GetSysColor( COLOR_BTNFACE ) ); + HandleToolRedraw( helper ); + + BaseClass::redraw(); +} + +int +ControlPanel::handleEvent (mxEvent *event) +{ + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + int iret = 0; + + if ( HandleToolEvent( event ) ) + { + return iret; + } + + switch ( event->event ) + { + case mxEvent::Size: + { + PositionControls( event->width, event->height ); + iret = 1; + } + break; + case mxEvent::Action: + { + iret = 1; + switch (event->action) + { + case IDC_TOOLSDRIVEMOUTH: + { + g_viewerSettings.faceposerToolsDriveMouth = ((mxCheckBox *)event->widget)->isChecked(); + } + break; + case IDC_TAB: + { + g_viewerSettings.showTexture = (tab->getSelectedIndex() == 3); + } + break; + + case IDC_RENDERMODE: + { + int index = cRenderMode->getSelectedIndex(); + if (index >= 0) + { + setRenderMode (index); + } + } + break; + + case IDC_GROUND: + setShowGround (((mxCheckBox *) event->widget)->isChecked()); + break; + + case IDC_MOVEMENT: + setShowMovement (((mxCheckBox *) event->widget)->isChecked()); + break; + + case IDC_BACKGROUND: + setShowBackground (((mxCheckBox *) event->widget)->isChecked()); + break; + + case IDC_HITBOXES: + g_viewerSettings.showHitBoxes = ((mxCheckBox *) event->widget)->isChecked(); + break; + + case IDC_PHYSICSMODEL: + g_viewerSettings.showPhysicsModel = ((mxCheckBox *) event->widget)->isChecked(); + break; + + case IDC_BONES: + g_viewerSettings.showBones = ((mxCheckBox *) event->widget)->isChecked(); + break; + + case IDC_ATTACHMENTS: + g_viewerSettings.showAttachments = ((mxCheckBox *) event->widget)->isChecked(); + break; + + case IDC_SEQUENCE: + { + int index = cSequence->getSelectedIndex(); + if (index >= 0) + { + setSequence ( index ); + } + } + break; + + case IDC_SPEEDSCALE: + { + g_viewerSettings.speedScale = ((mxSlider *) event->widget)->getValue(); + lSpeedScale->setLabel( va( "Speed scale %.2f", g_viewerSettings.speedScale ) ); + } + break; + + case IDC_PRIMARYBLEND: + { + setBlend( 0, ((mxSlider *) event->widget)->getValue() ); + } + break; + + case IDC_SECONDARYBLEND: + { + setBlend( 1, ((mxSlider *) event->widget)->getValue() ); + } + break; + + case IDC_BODYPART: + { + int index = cBodypart->getSelectedIndex(); + if (index >= 0) + { + setBodypart (index); + + } + } + break; + + case IDC_SUBMODEL: + { + int index = cSubmodel->getSelectedIndex(); + if (index >= 0) + { + setSubmodel (index); + + } + } + break; + + case IDC_CONTROLLER: + { + int index = cController->getSelectedIndex(); + if (index >= 0) + setBoneController (index); + } + break; + + case IDC_CONTROLLERVALUE: + { + int index = cController->getSelectedIndex(); + if (index >= 0) + setBoneControllerValue (index, slController->getValue()); + } + break; + + case IDC_SKINS: + { + int index = cSkin->getSelectedIndex(); + if (index >= 0) + { + models->GetActiveStudioModel()->SetSkin (index); + g_viewerSettings.skin = index; + g_pMatSysWindow->redraw(); + } + } + break; + default: + iret = 0; + break; + } + } + } + + return iret; +} + +void ControlPanel::dumpModelInfo() { } + +void ControlPanel::ChangeModel( const char *filename ) +{ + HCURSOR hPrevCursor = SetCursor( LoadCursor( NULL, IDC_WAIT ) ); + + // init all the selection tabs based on the current model + initSequenceChoices(); + initBodypartChoices(); + initBoneControllerChoices(); + initSkinChoices(); + + setModelInfo(); + + SetCloseCaptionLanguageId( g_viewerSettings.cclanguageid, true ); + + g_viewerSettings.m_iEditAttachment = -1; + + g_viewerSettings.enableIK = true; + g_viewerSettings.enableTargetIK = false; + + setSequence( models->GetActiveStudioModel()->GetSequence() ); + setSpeed( g_viewerSettings.speedScale ); + + mx_setcwd (mx_getpath (filename)); + + g_pFlexPanel->initFlexes(); + + // centerView(); + // CenterOnFace(); + + IFacePoserToolWindow::ModelChanged(); + + CExpClass *cl = expressions->GetActiveClass(); + if ( cl ) + { + cl->SelectExpression( cl->GetSelectedExpression() ); + } + + SetSuffix( va( " - %s.mdl", models->GetActiveModelName() ) ); + redraw(); + + SetCursor( hPrevCursor ); +} + + + +void +ControlPanel::setRenderMode (int mode) +{ + g_viewerSettings.renderMode = mode; + g_pMatSysWindow->redraw(); +} + + +void +ControlPanel::setHighlightBone( int index ) +{ + g_viewerSettings.highlightPhysicsBone = index; +} + +void +ControlPanel::setShowGround (bool b) +{ + g_viewerSettings.showGround = b; + cbGround->setChecked (b); +} + + + +void +ControlPanel::setShowMovement (bool b) +{ + g_viewerSettings.showMovement = b; + cbMovement->setChecked (b); +} + + + +void +ControlPanel::setShowBackground (bool b) +{ + g_viewerSettings.showBackground = b; + cbBackground->setChecked (b); +} + + + +void +ControlPanel::initSequenceChoices() +{ + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if (hdr) + { + cSequence->removeAll(); + for (int i = 0; i < hdr->GetNumSeq(); i++) + { + cSequence->add (hdr->pSeqdesc(i).pszLabel()); + } + + cSequence->select (0); + } +} + + + +void +ControlPanel::setSequence (int index) +{ + cSequence->select (index); + models->GetActiveStudioModel()->SetSequence(index); + + initPoseParameters( ); +} + + +void +ControlPanel::setSpeed( float value ) +{ + g_viewerSettings.speedScale = value; + slSpeedScale->setValue( value ); +} + + +void ControlPanel::setBlend(int index, float value ) +{ + models->GetActiveStudioModel()->SetPoseParameter( index, value ); +} + + +void +ControlPanel::initBodypartChoices() +{ + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if (hdr) + { + int i; + mstudiobodyparts_t *pbodyparts = hdr->pBodypart(0); + + cBodypart->removeAll(); + if (hdr->numbodyparts() > 0) + { + for (i = 0; i < hdr->numbodyparts(); i++) + cBodypart->add (pbodyparts[i].pszName()); + + cBodypart->select (0); + + cSubmodel->removeAll(); + for (i = 0; i < pbodyparts[0].nummodels; i++) + { + char str[64]; + sprintf (str, "Submodel %d", i + 1); + cSubmodel->add (str); + } + cSubmodel->select (0); + } + } +} + + + +void +ControlPanel::setBodypart (int index) +{ + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if (hdr) + { + //cBodypart->setEn + cBodypart->select (index); + if (index < hdr->numbodyparts()) + { + mstudiobodyparts_t *pbodyparts = hdr->pBodypart(0); + cSubmodel->removeAll(); + + for (int i = 0; i < pbodyparts[index].nummodels; i++) + { + char str[64]; + sprintf (str, "Submodel %d", i + 1); + cSubmodel->add (str); + } + cSubmodel->select (0); + //models->GetActiveStudioModel()->SetBodygroup (index, 0); + } + } +} + + + +void +ControlPanel::setSubmodel (int index) +{ + models->GetActiveStudioModel()->SetBodygroup (cBodypart->getSelectedIndex(), index); +} + + + +void +ControlPanel::initBoneControllerChoices() +{ + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if (hdr) + { + cController->setEnabled (hdr->numbonecontrollers() > 0); + slController->setEnabled (hdr->numbonecontrollers() > 0); + cController->removeAll(); + + for (int i = 0; i < hdr->numbonecontrollers(); i++) + { + mstudiobonecontroller_t *pbonecontroller = hdr->pBonecontroller(i); + char str[32]; + sprintf (str, "Controller %d", pbonecontroller->inputfield); + cController->add (str); + } + + if (hdr->numbonecontrollers() > 0) + { + mstudiobonecontroller_t *pbonecontrollers = hdr->pBonecontroller(0); + cController->select (0); + slController->setRange (pbonecontrollers->start, pbonecontrollers->end); + slController->setValue (0); + } + } +} + + + +void +ControlPanel::setBoneController (int index) +{ + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if (hdr) + { + mstudiobonecontroller_t *pbonecontroller = hdr->pBonecontroller(index); + slController->setRange ( pbonecontroller->start, pbonecontroller->end); + slController->setValue (0); + } +} + + + +void +ControlPanel::setBoneControllerValue (int index, float value) +{ + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if (hdr) + { + mstudiobonecontroller_t *pbonecontrollers = hdr->pBonecontroller(index); + models->GetActiveStudioModel()->SetController (pbonecontrollers->inputfield, value); + } +} + + + +void +ControlPanel::initPoseParameters() +{ + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if (hdr) + { + for (int i = 0; i < hdr->GetNumPoseParameters(); i++) + { + setBlend( i, 0.0 ); + } + } +} + +void +ControlPanel::initSkinChoices() +{ + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if (hdr) + { + cSkin->setEnabled (hdr->numskinfamilies() > 0); + cSkin->removeAll(); + + for (int i = 0; i < hdr->numskinfamilies(); i++) + { + char str[32]; + sprintf (str, "Skin %d", i + 1); + cSkin->add (str); + } + + cSkin->select (0); + models->GetActiveStudioModel()->SetSkin (0); + g_viewerSettings.skin = 0; + } +} + + + +void +ControlPanel::setModelInfo() +{ + static char str[2048]; + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + + if (!hdr) + return; + + int hbcount = 0; + for ( int s = 0; s < hdr->numhitboxsets(); s++ ) + { + hbcount += hdr->iHitboxCount( s ); + } + + sprintf (str, + "Bones: %d\n" + "Bone Controllers: %d\n" + "Hit Boxes: %d in %d sets\n" + "Sequences: %d\n", + hdr->numbones(), + hdr->numbonecontrollers(), + hbcount, + hdr->numhitboxsets(), + hdr->GetNumSeq() + ); + + lModelInfo1->setLabel (str); + + sprintf (str, + "Textures: %d\n" + "Skin Families: %d\n" + "Bodyparts: %d\n" + "Attachments: %d\n", + hdr->numtextures(), + hdr->numskinfamilies(), + hdr->numbodyparts(), + hdr->GetNumAttachments()); + + lModelInfo2->setLabel (str); +} + + +void ControlPanel::CenterOnFace( void ) +{ + if ( !models->GetActiveStudioModel() ) + return; + + StudioModel *mdl = models->GetActiveStudioModel(); + if ( !mdl ) + return; + + CStudioHdr *hdr = mdl->GetStudioHdr(); + if ( !hdr ) + return; + + setSpeed( 1.0f ); + + int oldSeq = models->GetActiveStudioModel()->GetSequence(); + + int seq = models->GetActiveStudioModel()->LookupSequence( "idle_suble" ); + if ( seq == -1 ) + seq = 0; + + if ( seq != oldSeq ) + { + Con_Printf( "Centering changed model sequence # to %d\n", seq ); + } + + setSequence( seq ); + initPoseParameters( ); + + mdl->m_angles.Init(); + mdl->m_origin.Init(); + + Vector size; + VectorSubtract( hdr->hull_max(), hdr->hull_min(), size ); + + float eyeheight = hdr->hull_min().z + 0.9 * size.z; + + if ( hdr->GetNumAttachments() > 0 ) + { + for (int i = 0; i < hdr->GetNumAttachments(); i++) + { + const mstudioattachment_t &attachment = hdr->pAttachment( i ); + int iBone = hdr->GetAttachmentBone( i ); + + if ( Q_stricmp( attachment.pszName(), "eyes" ) ) + continue; + + mstudiobone_t *bone = hdr->pBone( iBone ); + if ( !bone ) + continue; + + matrix3x4_t boneToPose; + MatrixInvert( bone->poseToBone, boneToPose ); + + matrix3x4_t attachmentPoseToLocal; + ConcatTransforms( boneToPose, attachment.local, attachmentPoseToLocal ); + + Vector localSpaceEyePosition; + VectorITransform( vec3_origin, attachmentPoseToLocal, localSpaceEyePosition ); + + // Not sure why this must be negative? + eyeheight = -localSpaceEyePosition.z + hdr->hull_min().z; + break; + } + } + + KeyValues *seqKeyValues = new KeyValues(""); + if ( seqKeyValues->LoadFromBuffer( mdl->GetFileName( ), mdl->GetKeyValueText( seq ) ) ) + { + // Do we have a build point section? + KeyValues *pkvAllFaceposer = seqKeyValues->FindKey("faceposer"); + if ( pkvAllFaceposer ) + { + float flEyeheight = pkvAllFaceposer->GetFloat( "eye_height", -9999.0f ); + if ( flEyeheight != -9999.0f ) + { + eyeheight = flEyeheight; + } + } + } + + seqKeyValues->deleteThis(); + + mdl->m_origin.x = size.z * .65f; + mdl->m_origin.z += eyeheight; + + CUtlVector< StudioModel * > modellist; + + modellist.AddToTail( models->GetActiveStudioModel() ); + + int i; + if ( models->CountVisibleModels() > 0 ) + { + modellist.RemoveAll(); + for ( i = 0; i < models->Count(); i++ ) + { + if ( models->IsModelShownIn3DView( i ) ) + { + modellist.AddToTail( models->GetStudioModel( i ) ); + } + } + } + + int modelcount = modellist.Count(); + int countover2 = modelcount / 2; + int ydelta = GetModelGap(); + int yoffset = -countover2 * ydelta; + for ( i = 0 ; i < modelcount; i++ ) + { + if ( models->GetStudioHeader( i ) == hdr ) + { + mdl->m_origin.y = -yoffset; + } + yoffset += ydelta; + } + + g_pMatSysWindow->redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float ControlPanel::GetModelGap( void ) +{ + return slModelGap->getValue(); +} + +void +ControlPanel::centerView() +{ + StudioModel *pModel = models->GetActiveStudioModel(); + if ( !pModel ) + return; + + Vector min, max; + models->GetActiveStudioModel()->ExtractBbox (min, max); + + float dx = max[0] - min[0]; + float dy = max[1] - min[1]; + float dz = max[2] - min[2]; + float d = dx; + if (dy > d) + d = dy; + if (dz > d) + d = dz; + pModel->m_origin[0] = d * 1.0f; + pModel->m_origin[1] = 0; + pModel->m_origin[2] = min[2] + dz / 2; + pModel->m_angles[0] = 0.0f; + pModel->m_angles[1] = 0.0f; + pModel->m_angles[2] = 0.0f; + g_viewerSettings.lightrot.x = 0.f; + g_viewerSettings.lightrot.y = -180.0f; + g_viewerSettings.lightrot.z = 0.0f; + g_pMatSysWindow->redraw(); +} + +bool ControlPanel::Close() +{ + int index = g_pExpressionClass->getSelectedIndex(); + CExpClass *cl = expressions->GetClass( index ); + if ( !cl ) + return true; + + return expressions->CloseClass( cl ); + +} + +bool ControlPanel::Closeall() +{ + bool retval = true; + + while ( expressions->GetNumClasses() > 0 ) + { + CExpClass *cl = expressions->GetClass( 0 ); + if ( !cl ) + break; + + if ( !expressions->CloseClass( cl ) ) + { + return false; + } + } + return retval; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ControlPanel::Copy( void ) +{ + g_pFlexPanel->CopyControllerSettings(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ControlPanel::Paste( void ) +{ + g_pFlexPanel->PasteControllerSettings(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ControlPanel::Undo( void ) +{ + CExpClass *active = expressions->GetActiveClass(); + if ( !active ) + return; + int index = active->GetSelectedExpression(); + if ( index != -1 ) + { + UndoExpression( index ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ControlPanel::Redo( void ) +{ + CExpClass *active = expressions->GetActiveClass(); + if ( !active ) + return; + int index = active->GetSelectedExpression(); + if ( index != -1 ) + { + RedoExpression( index ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ControlPanel::UndoExpression( int index ) +{ + if ( index == -1 ) + return; + + CExpClass *active = expressions->GetActiveClass(); + if ( !active ) + return; + + CExpression *exp = active->GetExpression( index ); + if ( exp ) + { + exp->Undo(); + // Show the updated data + active->SelectExpression( index ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ControlPanel::RedoExpression( int index ) +{ + if ( index == -1 ) + return; + + CExpClass *active = expressions->GetActiveClass(); + if ( !active ) + return; + + CExpression *exp = active->GetExpression( index ); + if ( exp ) + { + exp->Redo(); + // Show the updated data + active->SelectExpression( index ); + } +} + +void ControlPanel::DeleteExpression( int index ) +{ + CExpClass *active = expressions->GetActiveClass(); + if ( !active ) + return; + + CExpression *exp = active->GetExpression( index ); + if ( exp ) + { + Con_Printf( "Deleting expression %s : %s\n", exp->name, exp->description ); + + g_pFlexPanel->DeleteExpression( index ); + + active->SelectExpression( max( 0, index - 1 ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dt - +//----------------------------------------------------------------------------- +void ControlPanel::Think( float dt ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ControlPanel::AllToolsDriveSpeech( void ) +{ + return cbAllWindowsDriveSpeech->isChecked(); +} diff --git a/utils/hlfaceposer/controlpanel.h b/utils/hlfaceposer/controlpanel.h new file mode 100644 index 0000000..7382051 --- /dev/null +++ b/utils/hlfaceposer/controlpanel.h @@ -0,0 +1,161 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef CONTROLPANEL_H +#define CONTROLPANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#ifndef INCLUDED_MXWINDOW +#include <mxtk/mxWindow.h> +#endif + +#include "faceposertoolwindow.h" + +#define IDC_TAB 1901 +#define IDC_RENDERMODE 2001 +#define IDC_GROUND 2003 +#define IDC_MOVEMENT 2004 +#define IDC_BACKGROUND 2005 +#define IDC_HITBOXES 2006 +#define IDC_BONES 2007 +#define IDC_ATTACHMENTS 2008 +#define IDC_PHYSICSMODEL 2009 +#define IDC_PHYSICSHIGHLIGHT 2010 +#define IDC_MODELSPACING 2011 +#define IDC_TOOLSDRIVEMOUTH 2012 + +#define IDC_SEQUENCE 3001 +#define IDC_SPEEDSCALE 3002 +#define IDC_PRIMARYBLEND 3003 +#define IDC_SECONDARYBLEND 3004 + +#define IDC_BODYPART 4001 +#define IDC_SUBMODEL 4002 +#define IDC_CONTROLLER 4003 +#define IDC_CONTROLLERVALUE 4004 +#define IDC_SKINS 4005 + +#define IDC_EXPRESSIONCLASS 5001 +#define IDC_EXPRESSIONTRAY 5002 +#define IDC_ANIMATIONBROWSER 5003 + +class mxTab; +class mxChoice; +class mxCheckBox; +class mxSlider; +class mxLineEdit; +class mxLabel; +class mxButton; +class MatSysWindow; +class TextureWindow; +class mxExpressionTray; +class FlexPanel; +class PhonemeEditor; +class mxExpressionTab; +class mxExpressionSlider; +class ExpressionTool; +class CChoreoView; + + +class ControlPanel : public mxWindow, public IFacePoserToolWindow +{ + typedef mxWindow BaseClass; + + mxTab *tab; + mxChoice *cRenderMode; + mxCheckBox *cbGround, *cbMovement, *cbBackground; + mxChoice *cSequence; + mxSlider *slSpeedScale; + mxLabel *lSpeedScale; + mxChoice *cBodypart, *cController, *cSubmodel; + mxSlider *slController; + mxChoice *cSkin; + mxLabel *lModelInfo1, *lModelInfo2; + + mxLineEdit *leMeshScale, *leBoneScale; + mxSlider *slModelGap; + + mxCheckBox *cbAllWindowsDriveSpeech; + +public: + // CREATORS + ControlPanel (mxWindow *parent); + virtual ~ControlPanel (); + + // MANIPULATORS + + virtual int handleEvent (mxEvent *event); + virtual void redraw(); + + virtual void OnDelete(); + virtual bool CanClose(); + + virtual void Think( float dt ); + + void dumpModelInfo (); + void ChangeModel( const char *filename ); + + void setRenderMode (int mode); + void setShowGround (bool b); + void setShowMovement (bool b); + void setShowBackground (bool b); + void setHighlightBone( int index ); + + void initSequenceChoices( ); + void setSequence (int index); + + void setSpeed( float value ); + + void initPoseParameters (); + void setBlend(int index, float value ); + + void initBodypartChoices(); + void setBodypart (int index); + void setSubmodel (int index); + + void initBoneControllerChoices(); + void setBoneController (int index); + void setBoneControllerValue (int index, float value); + + void initSkinChoices(); + + void setModelInfo (); + + void centerView (); + + void fullscreen (); + + void CenterOnFace( void ); + + void PositionControls( int width, int height ); + + bool CloseClass( int classindex ); + + bool Close(); + bool Closeall(); + + void Copy( void ); + void Paste( void ); + + void Undo( void ); + void Redo( void ); + + void UndoExpression( int index ); + void RedoExpression( int index ); + + void DeleteExpression( int index ); + + float GetModelGap( void ); + + bool AllToolsDriveSpeech( void ); + +}; + +extern ControlPanel *g_pControlPanel; + +#endif // CONTROLPANEL_H diff --git a/utils/hlfaceposer/curveeditorhelpers.h b/utils/hlfaceposer/curveeditorhelpers.h new file mode 100644 index 0000000..7a3e22e --- /dev/null +++ b/utils/hlfaceposer/curveeditorhelpers.h @@ -0,0 +1,370 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef CURVEEDITORHELPERS_H +#define CURVEEDITORHELPERS_H +#ifdef _WIN32 +#pragma once +#endif + +#include "mxtk/mx.h" + +struct CExpressionSample; + +template< class T > +class CCurveEditorHelper +{ +public: + CCurveEditorHelper( T *outer ); + + int GetBestCurveTypeForSelectedSamples( bool reflect ); + int CountSelected( bool reflect ); + void ChangeCurveType( bool forward, bool shiftdown, bool altdown ); + void SetCurveTypeForSelectedSamples( bool reflect, int curvetype ); + void SetCurveTypeForSample( int curvetype, CExpressionSample *sample ); + void ToggleHoldTypeForSelectedSamples( bool reflect ); + void ToggleHoldTypeForSample( CExpressionSample *sample ); + + bool HelperHandleEvent( mxEvent *event ); + +private: + T *GetOuter(); + +private: + + T *m_pOuter; +}; + +template< class T > +CCurveEditorHelper<T>::CCurveEditorHelper( T *pOuter ) : + m_pOuter( pOuter ) +{ + Assert( pOuter ); +} + +template< class T > +T *CCurveEditorHelper<T>::GetOuter() +{ + return m_pOuter; +} + +template< class T > +int CCurveEditorHelper<T>::GetBestCurveTypeForSelectedSamples( bool reflect ) +{ + int numSelected = CountSelected( reflect ); + if ( !numSelected ) + return CURVE_DEFAULT; + + CUtlMap< int, int > counts( 0, 0, DefLessFunc( int ) ); + + CUtlVector< T * > workList; + GetOuter()->GetWorkList( reflect, workList ); + + for ( int w = 0; w < workList.Count(); ++w ) + { + int numSamples = workList[ w ]->NumSamples(); + if ( !numSamples ) + continue; + + for ( int i = numSamples - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = workList[ w ]->GetSample( i ); + if ( !sample->selected ) + continue; + + int curveType = sample->GetCurveType(); + int idx = counts.Find( curveType ); + if ( idx == counts.InvalidIndex() ) + { + idx = counts.Insert( curveType, 0 ); + } + + counts[ idx ]++; + } + } + + int maxType = CURVE_DEFAULT; + int maxCount = -1; + + for ( int i = counts.FirstInorder(); i != counts.InvalidIndex(); i = counts.NextInorder( i ) ) + { + if ( counts[ i ] > maxType ) + { + maxCount = counts[ i ]; + maxType = counts.Key( i ); + } + } + + return maxType; +} + +template< class T > +int CCurveEditorHelper<T>::CountSelected( bool reflect ) +{ + int numSelected = 0; + + CUtlVector< T * > workList; + GetOuter()->GetWorkList( reflect, workList ); + + for ( int w = 0; w < workList.Count(); ++w ) + { + int numSamples = workList[ w ]->NumSamples(); + if ( !numSamples ) + continue; + + for ( int i = 0 ; i < numSamples; ++i ) + { + CExpressionSample *sample = workList[ w ]->GetSample( i ); + if ( !sample || !sample->selected ) + continue; + + ++numSelected; + } + } + + return numSelected; +} + +template< class T > +void CCurveEditorHelper<T>::ChangeCurveType( bool forward, bool shiftdown, bool altdown ) +{ + // If holding ctrl and shift, only do inbound + bool inbound = shiftdown; + // if holding ctrl, shift + alt, do both inbound and outbound + bool outbound = !shiftdown || altdown; + // if holding ctrl + alt, do outbound + // if holding just ctrl, do outbound + + int numSelected = CountSelected( false ); + if ( !numSelected ) + return; + + int curveType = GetBestCurveTypeForSelectedSamples( false ); + + int sides[ 2 ]; + Interpolator_CurveInterpolatorsForType( curveType, sides[ 0 ], sides[ 1 ] ); + + int dir = forward ? 1 : -1; + for ( int i = 0; i < 2; ++i ) + { + if ( i == 0 && !inbound ) + continue; + if ( i == 1 && !outbound ) + continue; + + sides[ i ] += dir; + if ( sides[ i ] < 0 ) + { + sides[ i ] = NUM_INTERPOLATE_TYPES - 1; + } + else if ( sides[ i ] >= NUM_INTERPOLATE_TYPES ) + { + sides[ i ] = INTERPOLATE_DEFAULT; + } + } + + curveType = MAKE_CURVE_TYPE( sides[ 0 ], sides[ 1 ] ); + SetCurveTypeForSelectedSamples( false, curveType ); +} + +template< class T > +void CCurveEditorHelper<T>::SetCurveTypeForSelectedSamples( bool reflect, int curvetype ) +{ + int numSelected = CountSelected( reflect ); + if ( !numSelected ) + return; + + GetOuter()->PreDataChanged( "Set curve type" ); + + CUtlVector< T * > workList; + GetOuter()->GetWorkList( reflect, workList ); + + for ( int w = 0; w < workList.Count(); ++w ) + { + int numSamples = workList[ w ]->NumSamples(); + + for ( int i = 0 ; i < numSamples; ++i ) + { + CExpressionSample *sample = workList[ w ]->GetSample( i ); + if ( !sample->selected ) + continue; + + sample->SetCurveType( curvetype ); + } + } + + GetOuter()->PostDataChanged( "Set curve type" ); +} + +template< class T > +void CCurveEditorHelper<T>::SetCurveTypeForSample( int curvetype, CExpressionSample *sample ) +{ + GetOuter()->PreDataChanged( "Set curve type" ); + + sample->SetCurveType( curvetype ); + + GetOuter()->PostDataChanged( "Set curve type" ); +} + +template< class T > +void CCurveEditorHelper<T>::ToggleHoldTypeForSelectedSamples( bool reflect ) +{ + int numSelected = CountSelected( reflect ); + if ( !numSelected ) + return; + + GetOuter()->PreDataChanged( "Set hold out value" ); + + CUtlVector< T * > workList; + GetOuter()->GetWorkList( reflect, workList ); + + for ( int w = 0; w < workList.Count(); ++w ) + { + int numSamples = workList[ w ]->NumSamples(); + + int newValue = -1; + + for ( int i = 0 ; i < numSamples; ++i ) + { + CExpressionSample *sample = workList[ w ]->GetSample( i ); + if ( !sample->selected ) + continue; + + // First one controls setting + int l, r; + Interpolator_CurveInterpolatorsForType( sample->GetCurveType(), l, r ); + + if ( newValue == -1 ) + { + newValue = ( r == INTERPOLATE_HOLD ) ? 0 : 1; + } + + int newCurveType = MAKE_CURVE_TYPE( l, newValue == 1 ? INTERPOLATE_HOLD : l ); + sample->SetCurveType( newCurveType ); + } + } + + GetOuter()->PostDataChanged( "Set hold out value" ); +} + +template< class T > +void CCurveEditorHelper<T>::ToggleHoldTypeForSample( CExpressionSample *sample ) +{ + GetOuter()->PreDataChanged( "Set hold out value" ); + + int l, r; + Interpolator_CurveInterpolatorsForType( sample->GetCurveType(), l, r ); + + if ( r == INTERPOLATE_HOLD ) + { + r = l; + } + else + { + r = INTERPOLATE_HOLD; + } + + int newCurveType = MAKE_CURVE_TYPE( l, r ); + sample->SetCurveType( newCurveType ); + + GetOuter()->PostDataChanged( "Set hold out value" ); +} + +template< class T > +bool CCurveEditorHelper<T>::HelperHandleEvent( mxEvent *event ) +{ + bool handled = false; + + switch ( event->event ) + { + case mxEvent::KeyDown: + { + switch ( event->key ) + { + default: + // Hotkey pressed + if ( event->key >= '0' && + event->key <= '9' ) + { + bool shiftdown = GetAsyncKeyState( VK_SHIFT ) ? true : false; + + handled = true; + // Get curve type + int curveType = Interpolator_CurveTypeForHotkey( event->key ); + if ( curveType >= 0 ) + { + if ( CountSelected( shiftdown ) <= 0 ) + { + GetOuter()->SetMousePositionForEvent( event ); + + CExpressionSample *hover = GetOuter()->GetSampleUnderMouse( event->x, event->y, 0.0f ); + + // Deal with highlighted item + if ( hover ) + { + SetCurveTypeForSample( curveType, hover ); + } + } + else + { + SetCurveTypeForSelectedSamples( shiftdown, curveType ); + } + } + } + break; + case 'H': + { + handled = true; + + bool shiftdown = GetAsyncKeyState( VK_SHIFT ) ? true : false; + + if ( CountSelected( shiftdown ) <= 0 ) + { + GetOuter()->SetMousePositionForEvent( event ); + + CExpressionSample *hover = GetOuter()->GetSampleUnderMouse( event->x, event->y, 0.0f ); + + // Deal with highlighted item + if ( hover ) + { + ToggleHoldTypeForSample( hover ); + } + } + else + { + ToggleHoldTypeForSelectedSamples( shiftdown ); + } + } + break; + case VK_UP: + { + bool shiftdown = GetAsyncKeyState( VK_SHIFT ) ? true : false; + bool altdown = GetAsyncKeyState( VK_MENU ) ? true : false; + if ( GetAsyncKeyState( VK_CONTROL ) ) + { + ChangeCurveType( false, shiftdown, altdown ); + } + } + break; + case VK_DOWN: + { + bool shiftdown = GetAsyncKeyState( VK_SHIFT ) ? true : false; + bool altdown = GetAsyncKeyState( VK_MENU ) ? true : false; + if ( GetAsyncKeyState( VK_CONTROL ) ) + { + ChangeCurveType( true, shiftdown, altdown ); + } + } + break; + } + } + } + + return handled; +} + +#endif // CURVEEDITORHELPERS_H + diff --git a/utils/hlfaceposer/eventproperties.cpp b/utils/hlfaceposer/eventproperties.cpp new file mode 100644 index 0000000..a85b73c --- /dev/null +++ b/utils/hlfaceposer/eventproperties.cpp @@ -0,0 +1,657 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include <mxtk/mx.h> +#include <stdio.h> +#include "resource.h" +#include "EventProperties.h" +#include "mdlviewer.h" +#include "choreoevent.h" +#include "choreoscene.h" +#include "mathlib/mathlib.h" +#include "choreochannel.h" +#include "choreoactor.h" +#include "filesystem.h" +#include "scriplib.h" + +#include "eventproperties_expression.h" +#include "eventproperties_face.h" +#include "eventproperties_firetrigger.h" +#include "eventproperties_flexanimation.h" +#include "eventproperties_generic.h" +#include "eventproperties_gesture.h" +#include "eventproperties_interrupt.h" +#include "eventproperties_lookat.h" +#include "eventproperties_moveto.h" +#include "eventproperties_permitresponses.h" +#include "eventproperties_sequence.h" +#include "eventproperties_speak.h" +#include "eventproperties_subscene.h" + +void CBaseEventPropertiesDialog::PopulateTagList( CEventParams *params ) +{ + CChoreoScene *scene = params->m_pScene; + if ( !scene ) + return; + + HWND control = GetControl( IDC_TAGS ); + if ( control ) + { + SendMessage( control, CB_RESETCONTENT, 0, 0 ); + SendMessage( control, WM_SETTEXT , 0, (LPARAM)va( "\"%s\" \"%s\"", params->m_szTagName, params->m_szTagWav ) ); + + for ( int i = 0; i < scene->GetNumActors(); i++ ) + { + CChoreoActor *a = scene->GetActor( i ); + if ( !a ) + continue; + + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannel *c = a->GetChannel( j ); + if ( !c ) + continue; + + for ( int k = 0 ; k < c->GetNumEvents(); k++ ) + { + CChoreoEvent *e = c->GetEvent( k ); + if ( !e ) + continue; + + if ( e->GetNumRelativeTags() <= 0 ) + continue; + + // add each tag to combo box + for ( int t = 0; t < e->GetNumRelativeTags(); t++ ) + { + CEventRelativeTag *tag = e->GetRelativeTag( t ); + if ( !tag ) + continue; + + SendMessage( control, CB_ADDSTRING, 0, (LPARAM)va( "\"%s\" \"%s\"", tag->GetName(), e->GetParameters() ) ); + } + } + } + } + } +} + +#include "mapentities.h" +#include "utldict.h" + +struct CMapEntityData +{ + CMapEntityData() + { + origin.Init(); + angles.Init(); + } + + Vector origin; + QAngle angles; +}; + +class CMapEntities : public IMapEntities +{ +public: + CMapEntities(); + ~CMapEntities(); + + virtual void CheckUpdateMap( char const *mapname ); + virtual bool LookupOrigin( char const *name, Vector& origin, QAngle& angles ) + { + int idx = FindNamedEntity( name ); + if ( idx == -1 ) + { + origin.Init(); + angles.Init(); + return false; + } + + CMapEntityData *e = &m_Entities[ idx ]; + Assert( e ); + origin = e->origin; + angles = e->angles; + return true; + } + + virtual int Count( void ); + virtual char const *GetName( int number ); + + int FindNamedEntity( char const *name ); + +private: + char m_szCurrentMap[ 1024 ]; + + CUtlDict< CMapEntityData, int > m_Entities; +}; + +static CMapEntities g_MapEntities; +// Expose to rest of tool +IMapEntities *mapentities = &g_MapEntities; + +CMapEntities::CMapEntities() +{ + m_szCurrentMap[ 0 ] = 0; +} + +CMapEntities::~CMapEntities() +{ + m_Entities.RemoveAll(); +} + +int CMapEntities::FindNamedEntity( char const *name ) +{ + char lowername[ 128 ]; + strcpy( lowername, name ); + _strlwr( lowername ); + + int index = m_Entities.Find( lowername ); + if ( index == m_Entities.InvalidIndex() ) + return -1; + + return index; +} + +#include "bspfile.h" + +void CMapEntities::CheckUpdateMap( char const *mapname ) +{ + if ( !mapname || !mapname[ 0 ] ) + return; + + if ( !stricmp( mapname, m_szCurrentMap ) ) + return; + + // Latch off the name of the map + Q_strncpy( m_szCurrentMap, mapname, sizeof( m_szCurrentMap ) ); + + // Load names from map + m_Entities.RemoveAll(); + + FileHandle_t hfile = filesystem->Open( mapname, "rb" ); + if ( hfile == FILESYSTEM_INVALID_HANDLE ) + return; + + dheader_t header; + filesystem->Read( &header, sizeof( header ), hfile ); + + // Check the header + if ( header.ident != IDBSPHEADER || + header.version < MINBSPVERSION || header.version > BSPVERSION ) + { + Con_ErrorPrintf( "BSP file %s is wrong version (%i), expected (%i)\n", + mapname, + header.version, + BSPVERSION ); + + filesystem->Close( hfile ); + return; + } + + // Find the LUMP_PAKFILE offset + lump_t *entlump = &header.lumps[ LUMP_ENTITIES ]; + if ( entlump->filelen <= 0 ) + { + Con_ErrorPrintf( "BSP file %s is missing entity lump\n", mapname ); + + // It's empty or only contains a file header ( so there are no entries ), so don't add to search paths + filesystem->Close( hfile ); + return; + } + + // Seek to correct position + filesystem->Seek( hfile, entlump->fileofs, FILESYSTEM_SEEK_HEAD ); + + char *buffer = new char[ entlump->filelen + 1 ]; + Assert( buffer ); + + filesystem->Read( buffer, entlump->filelen, hfile ); + + filesystem->Close( hfile ); + + buffer[ entlump->filelen ] = 0; + + // Now we have entity buffer, now parse it + ParseFromMemory( buffer, entlump->filelen ); + + while ( 1 ) + { + if (!GetToken (true)) + break; + + if (Q_stricmp (token, "{") ) + Error ("ParseEntity: { not found"); + + char name[ 256 ]; + char origin[ 256 ]; + char angles[ 256 ]; + + name[ 0 ] = 0; + origin[ 0 ] = 0; + angles[ 0 ] = 0; + + do + { + char key[ 256 ]; + char value[ 256 ]; + + if (!GetToken (true)) + { + Error ("ParseEntity: EOF without closing brace"); + } + + if (!Q_stricmp (token, "}") ) + break; + + Q_strncpy( key, token, sizeof( key ) ); + + GetToken (false); + + Q_strncpy( value, token, sizeof( value ) ); + + // Con_Printf( "Parsed %s -- %s\n", key, value ); + + if ( !Q_stricmp( key, "name" ) ) + { + Q_strncpy( name, value, sizeof( name ) ); + } + if ( !Q_stricmp( key, "targetname" ) ) + { + Q_strncpy( name, value, sizeof( name ) ); + } + if ( !Q_stricmp( key, "origin" ) ) + { + Q_strncpy( origin, value, sizeof( origin ) ); + } + if ( !Q_stricmp( key, "angles" ) ) + { + Q_strncpy( angles, value, sizeof( angles ) ); + } + + } while (1); + + if ( name[ 0 ] ) + { + if ( FindNamedEntity( name ) == - 1 ) + { + CMapEntityData ent; + + float org[3]; + if ( origin[ 0 ] ) + { + if ( 3 == sscanf( origin, "%f %f %f", &org[ 0 ], &org[ 1 ], &org[ 2 ] ) ) + { + ent.origin = Vector( org[ 0 ], org[ 1 ], org[ 2 ] ); + + // Con_Printf( "read %f %f %f for entity %s\n", org[0], org[1], org[2], name ); + } + } + if ( angles[ 0 ] ) + { + if ( 3 == sscanf( angles, "%f %f %f", &org[ 0 ], &org[ 1 ], &org[ 2 ] ) ) + { + ent.angles = QAngle( org[ 0 ], org[ 1 ], org[ 2 ] ); + + // Con_Printf( "read %f %f %f for entity %s\n", org[0], org[1], org[2], name ); + } + } + + m_Entities.Insert( name, ent ); + } + } + } + + delete[] buffer; +} + +int CMapEntities::Count( void ) +{ + return m_Entities.Count(); +} + +char const *CMapEntities::GetName( int number ) +{ + if ( number < 0 || number >= (int)m_Entities.Count() ) + return NULL; + + return m_Entities.GetElementName( number ); +} + +bool NameLessFunc( const char *const& name1, const char *const& name2 ) +{ + if ( Q_stricmp( name1, name2 ) < 0 ) + return true; + return false; +} + +void CBaseEventPropertiesDialog::SetDialogTitle( CEventParams *params, char const *eventname, char const *desc ) +{ + char sz[ 256 ]; + Q_snprintf( sz, sizeof( sz ), " : %s", eventname ); + Q_strncat( params->m_szDialogTitle, sz, sizeof( params->m_szDialogTitle ), COPY_ALL_CHARACTERS ); + Q_snprintf( sz, sizeof( sz ), "%s:", desc ); + + // Set dialog title + SetWindowText( m_hDialog, params->m_szDialogTitle ); + // Set type name field + SetDlgItemText( m_hDialog, IDC_TYPENAME, sz ); + // Set event name + SetDlgItemText( m_hDialog, IDC_EVENTNAME, params->m_szName ); +} + +void CBaseEventPropertiesDialog::ShowControlsForEventType( CEventParams *params ) +{ + // Special processing for various settings + if ( !params->m_bHasEndTime ) + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_HIDE ); + } + + if ( params->m_bFixedLength ) + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_HIDE ); + ShowWindow( GetControl( IDC_CHECK_ENDTIME ), SW_HIDE ); + } +} + +void CBaseEventPropertiesDialog::InitControlData( CEventParams *params ) +{ + SetDlgItemText( m_hDialog, IDC_STARTTIME, va( "%f", params->m_flStartTime ) ); + SetDlgItemText( m_hDialog, IDC_ENDTIME, va( "%f", params->m_flEndTime ) ); + + SendMessage( GetControl( IDC_CHECK_ENDTIME ), BM_SETCHECK, + ( WPARAM ) params->m_bHasEndTime ? BST_CHECKED : BST_UNCHECKED, + ( LPARAM )0 ); + + if ( GetControl( IDC_CHECK_RESUMECONDITION ) != (HWND)0 ) + { + SendMessage( GetControl( IDC_CHECK_RESUMECONDITION ), BM_SETCHECK, + ( WPARAM ) params->m_bResumeCondition ? BST_CHECKED : BST_UNCHECKED, + ( LPARAM )0 ); + } + + SendMessage( GetControl( IDC_CHECK_DISABLED ), BM_SETCHECK, + ( WPARAM ) params->m_bDisabled ? BST_CHECKED : BST_UNCHECKED, + ( LPARAM )0 ); + + PopulateTagList( params ); +} + +BOOL CBaseEventPropertiesDialog::InternalHandleMessage( CEventParams *params, HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam, bool& handled ) +{ + handled = false; + switch(uMsg) + { + default: + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + default: + break; + case IDC_CHECK_DISABLED: + { + params->m_bDisabled = SendMessage( GetControl( IDC_CHECK_DISABLED ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + handled = true; + return TRUE; + } + break; + } + } + return FALSE; +} + +void CBaseEventPropertiesDialog::PopulateNamedActorList( HWND wnd, CEventParams *params ) +{ + int i; + + char const *mapname = NULL; + if ( params->m_pScene ) + { + mapname = params->m_pScene->GetMapname(); + } + + CUtlRBTree< char const *, int > m_SortedNames( 0, 0, NameLessFunc ); + + if ( mapname ) + { + g_MapEntities.CheckUpdateMap( mapname ); + + for ( i = 0; i < g_MapEntities.Count(); i++ ) + { + char const *name = g_MapEntities.GetName( i ); + if ( name && name[ 0 ] ) + { + m_SortedNames.Insert( name ); + } + } + } + + for ( i = 0 ; i < params->m_pScene->GetNumActors() ; i++ ) + { + CChoreoActor *actor = params->m_pScene->GetActor( i ); + if ( actor && actor->GetName() && actor->GetName()[0] ) + { + if ( m_SortedNames.Find( actor->GetName() ) == m_SortedNames.InvalidIndex() ) + { + m_SortedNames.Insert( actor->GetName() ); + } + } + } + + i = m_SortedNames.FirstInorder(); + while ( i != m_SortedNames.InvalidIndex() ) + { + char const *name = m_SortedNames[ i ]; + if ( name && name[ 0 ] ) + { + SendMessage( wnd, CB_ADDSTRING, 0, (LPARAM)name ); + } + + i = m_SortedNames.NextInorder( i ); + } + + /* + // Note have to do this here, after posting data to the control, since we are storing a raw string pointer in m_SortedNames!!! + if ( allActors ) + { + allActors->deleteThis(); + } + */ + + // These events can also be directed at another player or named target, too + SendMessage( wnd, CB_ADDSTRING, 0, (LPARAM)"!player" ); + SendMessage( wnd, CB_ADDSTRING, 0, (LPARAM)"!enemy" ); + SendMessage( wnd, CB_ADDSTRING, 0, (LPARAM)"!self" ); + SendMessage( wnd, CB_ADDSTRING, 0, (LPARAM)"!friend" ); + SendMessage( wnd, CB_ADDSTRING, 0, (LPARAM)"!speechtarget" ); + SendMessage( wnd, CB_ADDSTRING, 0, (LPARAM)"!target1" ); + SendMessage( wnd, CB_ADDSTRING, 0, (LPARAM)"!target2" ); + SendMessage( wnd, CB_ADDSTRING, 0, (LPARAM)"!target3" ); + SendMessage( wnd, CB_ADDSTRING, 0, (LPARAM)"!target4" ); + SendMessage( wnd, CB_ADDSTRING, 0, (LPARAM)"!target5" ); + SendMessage( wnd, CB_ADDSTRING, 0, (LPARAM)"!target6" ); + SendMessage( wnd, CB_ADDSTRING, 0, (LPARAM)"!target7" ); + SendMessage( wnd, CB_ADDSTRING, 0, (LPARAM)"!target8" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : wnd - +// *params - +// Output : static void +//----------------------------------------------------------------------------- +void CBaseEventPropertiesDialog::ParseTags( CEventParams *params ) +{ + strcpy( params->m_szTagName, "" ); + strcpy( params->m_szTagWav, "" ); + + if ( params->m_bUsesTag ) + { + // Parse out the two tokens + char selectedText[ 512 ]; + selectedText[ 0 ] = 0; + HWND control = GetControl( IDC_TAGS ); + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( selectedText ), (LPARAM)selectedText ); + } + + ParseFromMemory( selectedText, strlen( selectedText ) ); + if ( TokenAvailable() ) + { + GetToken( false ); + char tagname[ 256 ]; + strcpy( tagname, token ); + if ( TokenAvailable() ) + { + GetToken( false ); + char wavename[ 256 ]; + strcpy( wavename, token ); + + // Valid + strcpy( params->m_szTagName, tagname ); + strcpy( params->m_szTagWav, wavename ); + + } + else + { + params->m_bUsesTag = false; + } + } + else + { + params->m_bUsesTag = false; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : wnd - +// *params - +// Output : static void +//----------------------------------------------------------------------------- +void CBaseEventPropertiesDialog::UpdateTagRadioButtons( CEventParams *params ) +{ + if ( params->m_bUsesTag ) + { + SendMessage( GetControl( IDC_RELATIVESTART ), BM_SETCHECK, ( WPARAM )BST_CHECKED, (LPARAM)0 ); + SendMessage( GetControl( IDC_ABSOLUTESTART ), BM_SETCHECK, ( WPARAM )BST_UNCHECKED, (LPARAM)0 ); + } + else + { + SendMessage( GetControl( IDC_ABSOLUTESTART ), BM_SETCHECK, ( WPARAM )BST_CHECKED, (LPARAM)0 ); + SendMessage( GetControl( IDC_RELATIVESTART ), BM_SETCHECK, ( WPARAM )BST_UNCHECKED, (LPARAM)0 ); + } +} + +void CBaseEventPropertiesDialog::GetSplineRect( HWND placeholder, RECT& rcOut ) +{ + GetWindowRect( placeholder, &rcOut ); + RECT rcDlg; + GetWindowRect( m_hDialog, &rcDlg ); + + OffsetRect( &rcOut, -rcDlg.left, -rcDlg.top ); +} + +void CBaseEventPropertiesDialog::DrawSpline( HDC hdc, HWND placeholder, CChoreoEvent *e ) +{ + RECT rcOut; + + GetSplineRect( placeholder, rcOut ); + + HBRUSH bg = CreateSolidBrush( GetSysColor( COLOR_BTNFACE ) ); + FillRect( hdc, &rcOut, bg ); + DeleteObject( bg ); + + if ( !e ) + return; + + // Draw spline + float range = ( float )( rcOut.right - rcOut.left ); + if ( range <= 1.0f ) + return; + + float height = ( float )( rcOut.bottom - rcOut.top ); + + HPEN pen = CreatePen( PS_SOLID, 1, GetSysColor( COLOR_BTNTEXT ) ); + HPEN oldPen = (HPEN)SelectObject( hdc, pen ); + + float duration = e->GetDuration(); + float starttime = e->GetStartTime(); + + for ( int i = 0; i < (int)range; i++ ) + { + float frac = ( float )i / ( range - 1 ); + + float scale = 1.0f - e->GetIntensity( starttime + frac * duration ); + + int h = ( int ) ( scale * ( height - 1 ) ); + + if ( i == 0 ) + { + MoveToEx( hdc, rcOut.left + i, rcOut.top + h, NULL ); + } + else + { + LineTo( hdc, rcOut.left + i, rcOut.top + h ); + } + } + + SelectObject( hdc, oldPen ); + + HBRUSH frame = CreateSolidBrush( GetSysColor( COLOR_BTNSHADOW ) ); + InflateRect( &rcOut, 1, 1 ); + FrameRect( hdc, &rcOut, frame ); + DeleteObject( frame ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int EventProperties( CEventParams *params ) +{ + int iret = 1; + switch ( params->m_nType ) + { + default: + break; + case CChoreoEvent::EXPRESSION: + return EventProperties_Expression( params ); + case CChoreoEvent::LOOKAT: + return EventProperties_LookAt( params ); + case CChoreoEvent::MOVETO: + return EventProperties_MoveTo( params ); + case CChoreoEvent::SPEAK: + return EventProperties_Speak( params ); + case CChoreoEvent::GESTURE: + return EventProperties_Gesture( params ); + case CChoreoEvent::SEQUENCE: + return EventProperties_Sequence( params ); + case CChoreoEvent::FACE: + return EventProperties_Face( params ); + case CChoreoEvent::FIRETRIGGER: + return EventProperties_FireTrigger( params ); + case CChoreoEvent::FLEXANIMATION: + return EventProperties_FlexAnimation( params ); + case CChoreoEvent::SUBSCENE: + return EventProperties_SubScene( params ); + case CChoreoEvent::INTERRUPT: + return EventProperties_Interrupt( params ); + case CChoreoEvent::PERMIT_RESPONSES: + return EventProperties_PermitResponses( params ); + case CChoreoEvent::GENERIC: + return EventProperties_Generic( params ); + } + + return iret; +} diff --git a/utils/hlfaceposer/eventproperties.h b/utils/hlfaceposer/eventproperties.h new file mode 100644 index 0000000..24974ab --- /dev/null +++ b/utils/hlfaceposer/eventproperties.h @@ -0,0 +1,105 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef EVENTPROPERTIES_H +#define EVENTPROPERTIES_H +#ifdef _WIN32 +#pragma once +#endif + +#include "basedialogparams.h" + +class CChoreoScene; +class CChoreoEvent; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CEventParams : public CBaseDialogParams +{ +public: + // e.g. CChoreoEvent::GESTURE + int m_nType; + + // Event descriptive name + char m_szName[ 256 ]; + + // Expression name/wav name/gesture name/look at name + char m_szParameters[ 256 ]; + char m_szParameters2[ 256 ]; + char m_szParameters3[ 256 ]; + + CChoreoScene *m_pScene; + + float m_flStartTime; + float m_flEndTime; + bool m_bHasEndTime; + + CChoreoEvent *m_pEvent; + + bool m_bDisabled; + bool m_bFixedLength; + + bool m_bResumeCondition; + + bool m_bLockBodyFacing; + float m_flDistanceToTarget; + + bool m_bForceShortMovement; + + bool m_bSyncToFollowingGesture; + + bool m_bPlayOverScript; + + bool m_bUsesTag; + char m_szTagName[ 256 ]; + char m_szTagWav[ 256 ]; + + // For Lookat events + int pitch; + int yaw; + bool usepitchyaw; + + // For speak + bool m_bCloseCaptionNoAttenuate; + +}; + +int EventProperties( CEventParams *params ); + +class CBaseEventPropertiesDialog +{ +public: + virtual void InitDialog( HWND hwndDlg ) = 0; + virtual BOOL HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) = 0; + virtual void SetTitle() = 0; + + HWND GetControl( int id ) { return GetDlgItem( m_hDialog, id ); } + + virtual void ShowControlsForEventType( CEventParams *params ); + virtual void InitControlData( CEventParams *params ); + +protected: + virtual BOOL InternalHandleMessage( CEventParams *params, HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam, bool& handled ); + + void SetDialogTitle( CEventParams *params, char const *eventname, char const *desc ); + + void UpdateTagRadioButtons( CEventParams *params ); + void PopulateTagList( CEventParams *params ); + void ParseTags( CEventParams *params ); + + void PopulateNamedActorList( HWND wnd, CEventParams *params ); + + void GetSplineRect( HWND placeholder, RECT& rcOut ); + void DrawSpline( HDC hdc, HWND placeholder, CChoreoEvent *e ); + +protected: + + HWND m_hDialog; +}; + +#endif // EVENTPROPERTIES_H diff --git a/utils/hlfaceposer/eventproperties_expression.cpp b/utils/hlfaceposer/eventproperties_expression.cpp new file mode 100644 index 0000000..b35097f --- /dev/null +++ b/utils/hlfaceposer/eventproperties_expression.cpp @@ -0,0 +1,298 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include <mxtk/mx.h> +#include <stdio.h> +#include "resource.h" +#include "EventProperties.h" +#include "mdlviewer.h" +#include "choreoevent.h" +#include "expressions.h" +#include "expclass.h" + +static CEventParams g_Params; + +class CEventPropertiesExpressionDialog : public CBaseEventPropertiesDialog +{ + typedef CBaseEventPropertiesDialog BaseClass; + +public: + virtual void InitDialog( HWND hwndDlg ); + virtual BOOL HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ); + virtual void SetTitle(); + virtual void ShowControlsForEventType( CEventParams *params ); + virtual void InitControlData( CEventParams *params ); +private: + + void PopulateExpressionList( HWND wnd ); + void PopulateExpressionClass( HWND control, CEventParams *params ); +}; + +void CEventPropertiesExpressionDialog::SetTitle() +{ + SetDialogTitle( &g_Params, "Expression", "Expression" ); +} + +void CEventPropertiesExpressionDialog::PopulateExpressionList( HWND wnd ) +{ + for ( int i = 0 ; i < expressions->GetNumClasses() ; i++ ) + { + CExpClass *cl = expressions->GetClass( i ); + if( !cl ) + continue; + + // add text to combo box + SendMessage( wnd, CB_ADDSTRING, 0, (LPARAM)cl->GetName() ); + } +} + +void CEventPropertiesExpressionDialog::InitControlData( CEventParams *params ) +{ + BaseClass::InitControlData( params ); + + HWND choices1 = GetControl( IDC_EVENTCHOICES ); + SendMessage( choices1, CB_RESETCONTENT, 0, 0 ); + SendMessage( choices1, WM_SETTEXT , 0, (LPARAM)params->m_szParameters ); + + HWND choices2 = GetControl( IDC_EVENTCHOICES2 ); + SendMessage( choices2, CB_RESETCONTENT, 0, 0 ); + SendMessage( choices2, WM_SETTEXT , 0, (LPARAM)params->m_szParameters2 ); + + PopulateExpressionList( choices1 ); + SendMessage( GetControl( IDC_CHOICES2PROMPT ), WM_SETTEXT, 0, (LPARAM)"Name:" ); + PopulateExpressionClass( choices2, params ); +} + +void CEventPropertiesExpressionDialog::InitDialog( HWND hwndDlg ) +{ + m_hDialog = hwndDlg; + + g_Params.PositionSelf( m_hDialog ); + + // Set working title for dialog, etc. + SetTitle(); + + // Show/Hide dialog controls + ShowControlsForEventType( &g_Params ); + InitControlData( &g_Params ); + + UpdateTagRadioButtons( &g_Params ); + + SetFocus( GetControl( IDC_EVENTNAME ) ); +} + +static CEventPropertiesExpressionDialog g_EventPropertiesExpressionDialog; + +void CEventPropertiesExpressionDialog::PopulateExpressionClass( HWND control, CEventParams *params ) +{ + // Find parameter 1 + for ( int c = 0; c < expressions->GetNumClasses(); c++ ) + { + CExpClass *cl = expressions->GetClass( c ); + if ( !cl ) + continue; + + if ( Q_stricmp( cl->GetName(), params->m_szParameters ) ) + continue; + + for ( int i = 0 ; i < cl->GetNumExpressions() ; i++ ) + { + CExpression *exp = cl->GetExpression( i ); + if ( exp ) + { + // add text to combo box + SendMessage( control, CB_ADDSTRING, 0, (LPARAM)exp->name ); + } + } + + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : wnd - +// *params - +// Output : static +//----------------------------------------------------------------------------- + +void CEventPropertiesExpressionDialog::ShowControlsForEventType( CEventParams *params ) +{ + BaseClass::ShowControlsForEventType( params ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK EventPropertiesExpressionDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + return g_EventPropertiesExpressionDialog.HandleMessage( hwndDlg, uMsg, wParam, lParam ); +}; + +BOOL CEventPropertiesExpressionDialog::HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + m_hDialog = hwndDlg; + + bool handled = false; + BOOL bret = InternalHandleMessage( &g_Params, hwndDlg, uMsg, wParam, lParam, handled ); + if ( handled ) + return bret; + + switch(uMsg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc; + + hdc = BeginPaint(hwndDlg, &ps); + DrawSpline( hdc, GetControl( IDC_STATIC_SPLINE ), g_Params.m_pEvent ); + EndPaint(hwndDlg, &ps); + + return FALSE; + } + break; + case WM_VSCROLL: + { + RECT rcOut; + GetSplineRect( GetControl( IDC_STATIC_SPLINE ), rcOut ); + + InvalidateRect( hwndDlg, &rcOut, TRUE ); + UpdateWindow( hwndDlg ); + return FALSE; + } + break; + case WM_INITDIALOG: + { + InitDialog( hwndDlg ); + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + HWND control = GetControl( IDC_EVENTCHOICES ); + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters ), (LPARAM)g_Params.m_szParameters ); + } + + GetDlgItemText( m_hDialog, IDC_EVENTNAME, g_Params.m_szName, sizeof( g_Params.m_szName ) ); + + if ( !g_Params.m_szName[ 0 ] ) + { + Q_snprintf( g_Params.m_szName, sizeof( g_Params.m_szName ), "%s/%s", g_Params.m_szParameters, g_Params.m_szParameters2 ); + } + + char szTime[ 32 ]; + GetDlgItemText( m_hDialog, IDC_STARTTIME, szTime, sizeof( szTime ) ); + g_Params.m_flStartTime = atof( szTime ); + GetDlgItemText( m_hDialog, IDC_ENDTIME, szTime, sizeof( szTime ) ); + g_Params.m_flEndTime = atof( szTime ); + + // Parse tokens from tags + ParseTags( &g_Params ); + + EndDialog( hwndDlg, 1 ); + } + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + case IDC_CHECK_ENDTIME: + { + g_Params.m_bHasEndTime = SendMessage( GetControl( IDC_CHECK_ENDTIME ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + if ( !g_Params.m_bHasEndTime ) + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_HIDE ); + } + else + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_RESTORE ); + } + } + break; + case IDC_CHECK_RESUMECONDITION: + { + g_Params.m_bResumeCondition = SendMessage( GetControl( IDC_CHECK_RESUMECONDITION ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + } + break; + case IDC_EVENTCHOICES: + { + HWND control = (HWND)lParam; + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters ), (LPARAM)g_Params.m_szParameters ); + + PopulateExpressionClass( GetControl( IDC_EVENTCHOICES2 ), &g_Params ); + } + } + break; + case IDC_EVENTCHOICES2: + { + HWND control = (HWND)lParam; + if ( control ) + { + if ( g_Params.m_nType != CChoreoEvent::MOVETO ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters2 ), (LPARAM)g_Params.m_szParameters2 ); + } + else + { + char buf1[ 256 ]; + + SendMessage( GetControl( IDC_EVENTCHOICES2 ), WM_GETTEXT, (WPARAM)sizeof( buf1 ), (LPARAM)buf1 ); + + Q_snprintf( g_Params.m_szParameters2, sizeof( g_Params.m_szParameters2 ), "%s", buf1 ); + } + } + } + break; + case IDC_ABSOLUTESTART: + { + g_Params.m_bUsesTag = false; + UpdateTagRadioButtons( &g_Params ); + } + break; + case IDC_RELATIVESTART: + { + g_Params.m_bUsesTag = true; + UpdateTagRadioButtons( &g_Params ); + } + break; + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int EventProperties_Expression( CEventParams *params ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_EVENTPROPERTIES_EXPRESSION ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)EventPropertiesExpressionDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/eventproperties_expression.h b/utils/hlfaceposer/eventproperties_expression.h new file mode 100644 index 0000000..ad52561 --- /dev/null +++ b/utils/hlfaceposer/eventproperties_expression.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef EVENTPROPERTIES_EXPRESSION_H +#define EVENTPROPERTIES_EXPRESSION_H +#ifdef _WIN32 +#pragma once +#endif + +class CEventParams; + +int EventProperties_Expression( CEventParams *params ); + +#endif // EVENTPROPERTIES_EXPRESSION_H diff --git a/utils/hlfaceposer/eventproperties_face.cpp b/utils/hlfaceposer/eventproperties_face.cpp new file mode 100644 index 0000000..54e6038 --- /dev/null +++ b/utils/hlfaceposer/eventproperties_face.cpp @@ -0,0 +1,234 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include <mxtk/mx.h> +#include <stdio.h> +#include "resource.h" +#include "EventProperties.h" +#include "mdlviewer.h" + +static CEventParams g_Params; + +class CEventPropertiesFaceDialog : public CBaseEventPropertiesDialog +{ + typedef CBaseEventPropertiesDialog BaseClass; + +public: + virtual void InitDialog( HWND hwndDlg ); + virtual BOOL HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ); + virtual void SetTitle(); + virtual void ShowControlsForEventType( CEventParams *params ); + virtual void InitControlData( CEventParams *params ); +}; + +void CEventPropertiesFaceDialog::SetTitle() +{ + SetDialogTitle( &g_Params, "Face", "Face Actor" ); +} + +void CEventPropertiesFaceDialog::InitControlData( CEventParams *params ) +{ + BaseClass::InitControlData( params ); + + HWND choices1 = GetControl( IDC_EVENTCHOICES ); + SendMessage( choices1, CB_RESETCONTENT, 0, 0 ); + SendMessage( choices1, WM_SETTEXT , 0, (LPARAM)params->m_szParameters ); + + PopulateNamedActorList( choices1, params ); + + SendMessage( GetControl( IDC_CHECK_LOCKBODYFACING ), BM_SETCHECK, + ( WPARAM ) params->m_bLockBodyFacing ? BST_CHECKED : BST_UNCHECKED, + ( LPARAM )0 ); + + +} + +void CEventPropertiesFaceDialog::InitDialog( HWND hwndDlg ) +{ + m_hDialog = hwndDlg; + + g_Params.PositionSelf( m_hDialog ); + + // Set working title for dialog, etc. + SetTitle(); + + // Show/Hide dialog controls + ShowControlsForEventType( &g_Params ); + InitControlData( &g_Params ); + + UpdateTagRadioButtons( &g_Params ); + + SetFocus( GetControl( IDC_EVENTNAME ) ); +} + +static CEventPropertiesFaceDialog g_EventPropertiesFaceDialog; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : wnd - +// *params - +// Output : static +//----------------------------------------------------------------------------- + +void CEventPropertiesFaceDialog::ShowControlsForEventType( CEventParams *params ) +{ + BaseClass::ShowControlsForEventType( params ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK EventPropertiesFaceDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + return g_EventPropertiesFaceDialog.HandleMessage( hwndDlg, uMsg, wParam, lParam ); +}; + +BOOL CEventPropertiesFaceDialog::HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + m_hDialog = hwndDlg; + + bool handled = false; + BOOL bret = InternalHandleMessage( &g_Params, hwndDlg, uMsg, wParam, lParam, handled ); + if ( handled ) + return bret; + + switch(uMsg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc; + + hdc = BeginPaint(hwndDlg, &ps); + DrawSpline( hdc, GetControl( IDC_STATIC_SPLINE ), g_Params.m_pEvent ); + EndPaint(hwndDlg, &ps); + + return FALSE; + } + break; + case WM_VSCROLL: + { + RECT rcOut; + GetSplineRect( GetControl( IDC_STATIC_SPLINE ), rcOut ); + + InvalidateRect( hwndDlg, &rcOut, TRUE ); + UpdateWindow( hwndDlg ); + return FALSE; + } + break; + case WM_INITDIALOG: + { + InitDialog( hwndDlg ); + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + HWND control = GetControl( IDC_EVENTCHOICES ); + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters ), (LPARAM)g_Params.m_szParameters ); + } + GetDlgItemText( m_hDialog, IDC_EVENTNAME, g_Params.m_szName, sizeof( g_Params.m_szName ) ); + + if ( !g_Params.m_szName[ 0 ] ) + { + Q_snprintf( g_Params.m_szName, sizeof( g_Params.m_szName ), "Face %s", g_Params.m_szParameters ); + } + + char szTime[ 32 ]; + GetDlgItemText( m_hDialog, IDC_STARTTIME, szTime, sizeof( szTime ) ); + g_Params.m_flStartTime = atof( szTime ); + GetDlgItemText( m_hDialog, IDC_ENDTIME, szTime, sizeof( szTime ) ); + g_Params.m_flEndTime = atof( szTime ); + + // Parse tokens from tags + ParseTags( &g_Params ); + + EndDialog( hwndDlg, 1 ); + } + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + case IDC_CHECK_ENDTIME: + { + g_Params.m_bHasEndTime = SendMessage( GetControl( IDC_CHECK_ENDTIME ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + if ( !g_Params.m_bHasEndTime ) + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_HIDE ); + } + else + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_RESTORE ); + } + } + break; + case IDC_CHECK_RESUMECONDITION: + { + g_Params.m_bResumeCondition = SendMessage( GetControl( IDC_CHECK_RESUMECONDITION ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + } + break; + case IDC_CHECK_LOCKBODYFACING: + { + g_Params.m_bLockBodyFacing = SendMessage( GetControl( IDC_CHECK_LOCKBODYFACING ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + } + break; + case IDC_EVENTCHOICES: + { + HWND control = (HWND)lParam; + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters ), (LPARAM)g_Params.m_szParameters ); + } + } + break; + case IDC_ABSOLUTESTART: + { + g_Params.m_bUsesTag = false; + UpdateTagRadioButtons( &g_Params ); + } + break; + case IDC_RELATIVESTART: + { + g_Params.m_bUsesTag = true; + UpdateTagRadioButtons( &g_Params ); + } + break; + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int EventProperties_Face( CEventParams *params ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_EVENTPROPERTIES_FACE ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)EventPropertiesFaceDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/eventproperties_face.h b/utils/hlfaceposer/eventproperties_face.h new file mode 100644 index 0000000..95f827b --- /dev/null +++ b/utils/hlfaceposer/eventproperties_face.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef EVENTPROPERTIES_FACE_H +#define EVENTPROPERTIES_FACE_H +#ifdef _WIN32 +#pragma once +#endif + +class CEventParams; + +int EventProperties_Face( CEventParams *params ); + +#endif // EVENTPROPERTIES_FACE_H diff --git a/utils/hlfaceposer/eventproperties_firetrigger.cpp b/utils/hlfaceposer/eventproperties_firetrigger.cpp new file mode 100644 index 0000000..78e8894 --- /dev/null +++ b/utils/hlfaceposer/eventproperties_firetrigger.cpp @@ -0,0 +1,240 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include <mxtk/mx.h> +#include <stdio.h> +#include "resource.h" +#include "EventProperties.h" +#include "mdlviewer.h" + +static CEventParams g_Params; + +class CEventPropertiesFireTriggerDialog : public CBaseEventPropertiesDialog +{ + typedef CBaseEventPropertiesDialog BaseClass; + +public: + virtual void InitDialog( HWND hwndDlg ); + virtual BOOL HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ); + virtual void SetTitle(); + virtual void ShowControlsForEventType( CEventParams *params ); + virtual void InitControlData( CEventParams *params ); + +private: + + void PopulateTriggerList( HWND wnd ); +}; + +void CEventPropertiesFireTriggerDialog::SetTitle() +{ + SetDialogTitle( &g_Params, "FireTrigger", "Scene Trigger" ); +} + +void CEventPropertiesFireTriggerDialog::PopulateTriggerList( HWND wnd ) +{ + for ( int i = 0 ; i < 16; i++ ) + { + char szName[256]; + + sprintf( szName, "%d", i + 1 ); + + // add text to combo box + SendMessage( wnd, CB_ADDSTRING, 0, (LPARAM)szName ); + } +} + +void CEventPropertiesFireTriggerDialog::InitControlData( CEventParams *params ) +{ + BaseClass::InitControlData( params ); + + HWND choices1 = GetControl( IDC_EVENTCHOICES ); + SendMessage( choices1, CB_RESETCONTENT, 0, 0 ); + SendMessage( choices1, WM_SETTEXT , 0, (LPARAM)params->m_szParameters ); + + PopulateTriggerList( choices1 ); +} + +void CEventPropertiesFireTriggerDialog::InitDialog( HWND hwndDlg ) +{ + m_hDialog = hwndDlg; + + g_Params.PositionSelf( m_hDialog ); + + // Set working title for dialog, etc. + SetTitle(); + + // Show/Hide dialog controls + ShowControlsForEventType( &g_Params ); + InitControlData( &g_Params ); + + UpdateTagRadioButtons( &g_Params ); + + SetFocus( GetControl( IDC_EVENTNAME ) ); +} + +static CEventPropertiesFireTriggerDialog g_EventPropertiesFireTriggerDialog; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : wnd - +// *params - +// Output : static +//----------------------------------------------------------------------------- + +void CEventPropertiesFireTriggerDialog::ShowControlsForEventType( CEventParams *params ) +{ + BaseClass::ShowControlsForEventType( params ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK EventPropertiesFireTriggerDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + return g_EventPropertiesFireTriggerDialog.HandleMessage( hwndDlg, uMsg, wParam, lParam ); +}; + +BOOL CEventPropertiesFireTriggerDialog::HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + m_hDialog = hwndDlg; + + bool handled = false; + BOOL bret = InternalHandleMessage( &g_Params, hwndDlg, uMsg, wParam, lParam, handled ); + if ( handled ) + return bret; + + switch(uMsg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc; + + hdc = BeginPaint(hwndDlg, &ps); + DrawSpline( hdc, GetControl( IDC_STATIC_SPLINE ), g_Params.m_pEvent ); + EndPaint(hwndDlg, &ps); + + return FALSE; + } + break; + case WM_VSCROLL: + { + RECT rcOut; + GetSplineRect( GetControl( IDC_STATIC_SPLINE ), rcOut ); + + InvalidateRect( hwndDlg, &rcOut, TRUE ); + UpdateWindow( hwndDlg ); + return FALSE; + } + break; + case WM_INITDIALOG: + { + InitDialog( hwndDlg ); + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + HWND control = GetControl( IDC_EVENTCHOICES ); + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters ), (LPARAM)g_Params.m_szParameters ); + } + GetDlgItemText( m_hDialog, IDC_EVENTNAME, g_Params.m_szName, sizeof( g_Params.m_szName ) ); + + if ( !g_Params.m_szName[ 0 ] ) + { + Q_snprintf( g_Params.m_szName, sizeof( g_Params.m_szName ), "Firetrigger %s", g_Params.m_szParameters ); + } + + char szTime[ 32 ]; + GetDlgItemText( m_hDialog, IDC_STARTTIME, szTime, sizeof( szTime ) ); + g_Params.m_flStartTime = atof( szTime ); + GetDlgItemText( m_hDialog, IDC_ENDTIME, szTime, sizeof( szTime ) ); + g_Params.m_flEndTime = atof( szTime ); + + // Parse tokens from tags + ParseTags( &g_Params ); + + EndDialog( hwndDlg, 1 ); + } + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + case IDC_CHECK_ENDTIME: + { + g_Params.m_bHasEndTime = SendMessage( GetControl( IDC_CHECK_ENDTIME ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + if ( !g_Params.m_bHasEndTime ) + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_HIDE ); + } + else + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_RESTORE ); + } + } + break; + case IDC_CHECK_RESUMECONDITION: + { + g_Params.m_bResumeCondition = SendMessage( GetControl( IDC_CHECK_RESUMECONDITION ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + } + break; + case IDC_EVENTCHOICES: + { + HWND control = (HWND)lParam; + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters ), (LPARAM)g_Params.m_szParameters ); + } + } + break; + case IDC_ABSOLUTESTART: + { + g_Params.m_bUsesTag = false; + UpdateTagRadioButtons( &g_Params ); + } + break; + case IDC_RELATIVESTART: + { + g_Params.m_bUsesTag = true; + UpdateTagRadioButtons( &g_Params ); + } + break; + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int EventProperties_FireTrigger( CEventParams *params ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_EVENTPROPERTIES_FIRETRIGGER ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)EventPropertiesFireTriggerDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/eventproperties_firetrigger.h b/utils/hlfaceposer/eventproperties_firetrigger.h new file mode 100644 index 0000000..24b19c9 --- /dev/null +++ b/utils/hlfaceposer/eventproperties_firetrigger.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef EVENTPROPERTIES_FIRETRIGGER_H +#define EVENTPROPERTIES_FIRETRIGGER_H +#ifdef _WIN32 +#pragma once +#endif + +class CEventParams; + +int EventProperties_FireTrigger( CEventParams *params ); + +#endif // EVENTPROPERTIES_FIRETRIGGER_H diff --git a/utils/hlfaceposer/eventproperties_flexanimation.cpp b/utils/hlfaceposer/eventproperties_flexanimation.cpp new file mode 100644 index 0000000..08c21ae --- /dev/null +++ b/utils/hlfaceposer/eventproperties_flexanimation.cpp @@ -0,0 +1,203 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include <mxtk/mx.h> +#include <stdio.h> +#include "resource.h" +#include "EventProperties.h" +#include "mdlviewer.h" + +static CEventParams g_Params; + +class CEventPropertiesFlexAnimationDialog : public CBaseEventPropertiesDialog +{ + typedef CBaseEventPropertiesDialog BaseClass; + +public: + virtual void InitDialog( HWND hwndDlg ); + virtual BOOL HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ); + virtual void SetTitle(); + virtual void ShowControlsForEventType( CEventParams *params ); + virtual void InitControlData( CEventParams *params ); +}; + +void CEventPropertiesFlexAnimationDialog::SetTitle() +{ + SetDialogTitle( &g_Params, "FlexAnimation", "Flex Controller Animation" ); +} + +void CEventPropertiesFlexAnimationDialog::InitControlData( CEventParams *params ) +{ + BaseClass::InitControlData( params ); +} + +void CEventPropertiesFlexAnimationDialog::InitDialog( HWND hwndDlg ) +{ + m_hDialog = hwndDlg; + + g_Params.PositionSelf( m_hDialog ); + + // Set working title for dialog, etc. + SetTitle(); + + // Show/Hide dialog controls + ShowControlsForEventType( &g_Params ); + InitControlData( &g_Params ); + + UpdateTagRadioButtons( &g_Params ); + + SetFocus( GetControl( IDC_EVENTNAME ) ); +} + +static CEventPropertiesFlexAnimationDialog g_EventPropertiesFlexAnimationDialog; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : wnd - +// *params - +// Output : static +//----------------------------------------------------------------------------- + +void CEventPropertiesFlexAnimationDialog::ShowControlsForEventType( CEventParams *params ) +{ + BaseClass::ShowControlsForEventType( params ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK EventPropertiesFlexAnimationDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + return g_EventPropertiesFlexAnimationDialog.HandleMessage( hwndDlg, uMsg, wParam, lParam ); +}; + +BOOL CEventPropertiesFlexAnimationDialog::HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + m_hDialog = hwndDlg; + + bool handled = false; + BOOL bret = InternalHandleMessage( &g_Params, hwndDlg, uMsg, wParam, lParam, handled ); + if ( handled ) + return bret; + + switch(uMsg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc; + + hdc = BeginPaint(hwndDlg, &ps); + DrawSpline( hdc, GetControl( IDC_STATIC_SPLINE ), g_Params.m_pEvent ); + EndPaint(hwndDlg, &ps); + + return FALSE; + } + break; + case WM_VSCROLL: + { + RECT rcOut; + GetSplineRect( GetControl( IDC_STATIC_SPLINE ), rcOut ); + + InvalidateRect( hwndDlg, &rcOut, TRUE ); + UpdateWindow( hwndDlg ); + return FALSE; + } + break; + case WM_INITDIALOG: + { + InitDialog( hwndDlg ); + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + GetDlgItemText( m_hDialog, IDC_EVENTNAME, g_Params.m_szName, sizeof( g_Params.m_szName ) ); + + if ( !g_Params.m_szName[ 0 ] ) + { + Q_strncpy( g_Params.m_szName, "Facial Animation", sizeof( g_Params.m_szName ) ); + } + + char szTime[ 32 ]; + GetDlgItemText( m_hDialog, IDC_STARTTIME, szTime, sizeof( szTime ) ); + g_Params.m_flStartTime = atof( szTime ); + GetDlgItemText( m_hDialog, IDC_ENDTIME, szTime, sizeof( szTime ) ); + g_Params.m_flEndTime = atof( szTime ); + + // Parse tokens from tags + ParseTags( &g_Params ); + + EndDialog( hwndDlg, 1 ); + } + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + case IDC_CHECK_ENDTIME: + { + g_Params.m_bHasEndTime = SendMessage( GetControl( IDC_CHECK_ENDTIME ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + if ( !g_Params.m_bHasEndTime ) + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_HIDE ); + } + else + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_RESTORE ); + } + } + break; + case IDC_CHECK_RESUMECONDITION: + { + g_Params.m_bResumeCondition = SendMessage( GetControl( IDC_CHECK_RESUMECONDITION ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + } + break; + case IDC_ABSOLUTESTART: + { + g_Params.m_bUsesTag = false; + UpdateTagRadioButtons( &g_Params ); + } + break; + case IDC_RELATIVESTART: + { + g_Params.m_bUsesTag = true; + UpdateTagRadioButtons( &g_Params ); + } + break; + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int EventProperties_FlexAnimation( CEventParams *params ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_EVENTPROPERTIES_FLEXANIMATION ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)EventPropertiesFlexAnimationDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/eventproperties_flexanimation.h b/utils/hlfaceposer/eventproperties_flexanimation.h new file mode 100644 index 0000000..ecf86e5 --- /dev/null +++ b/utils/hlfaceposer/eventproperties_flexanimation.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef EVENTPROPERTIES_FLEXANIMATION_H +#define EVENTPROPERTIES_FLEXANIMATION_H +#ifdef _WIN32 +#pragma once +#endif + +class CEventParams; + +int EventProperties_FlexAnimation( CEventParams *params ); + +#endif // EVENTPROPERTIES_FLEXANIMATION_H diff --git a/utils/hlfaceposer/eventproperties_generic.cpp b/utils/hlfaceposer/eventproperties_generic.cpp new file mode 100644 index 0000000..6cc5888 --- /dev/null +++ b/utils/hlfaceposer/eventproperties_generic.cpp @@ -0,0 +1,272 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include <mxtk/mx.h> +#include <stdio.h> +#include "resource.h" +#include "EventProperties.h" +#include "mdlviewer.h" +#include "choreoevent.h" + +static CEventParams g_Params; + +class CEventPropertiesGenericDialog : public CBaseEventPropertiesDialog +{ + typedef CBaseEventPropertiesDialog BaseClass; + +public: + virtual void InitDialog( HWND hwndDlg ); + virtual BOOL HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ); + virtual void SetTitle(); + virtual void ShowControlsForEventType( CEventParams *params ); + virtual void InitControlData( CEventParams *params ); + +private: + + void PopulateAIGeneric( HWND control, CEventParams *params ); +}; + +void CEventPropertiesGenericDialog::SetTitle() +{ + SetDialogTitle( &g_Params, "Generic", "Generic AI Event (text)" ); +} + +void CEventPropertiesGenericDialog::InitControlData( CEventParams *params ) +{ + BaseClass::InitControlData( params ); + + HWND choices1 = GetControl( IDC_EVENTCHOICES ); + SendMessage( choices1, CB_RESETCONTENT, 0, 0 ); + SendMessage( choices1, WM_SETTEXT , 0, (LPARAM)params->m_szParameters ); + + HWND choices2 = GetControl( IDC_EVENTCHOICES2 ); + SendMessage( choices2, CB_RESETCONTENT, 0, 0 ); + SendMessage( choices2, WM_SETTEXT , 0, (LPARAM)params->m_szParameters2 ); + + SendMessage( GetControl( IDC_FILENAME ), WM_SETTEXT, sizeof( params->m_szParameters ), (LPARAM)params->m_szParameters ); + + PopulateAIGeneric( choices1, params ); + PopulateNamedActorList( choices2, params ); +} + +void CEventPropertiesGenericDialog::InitDialog( HWND hwndDlg ) +{ + m_hDialog = hwndDlg; + + g_Params.PositionSelf( m_hDialog ); + + // Set working title for dialog, etc. + SetTitle(); + + // Show/Hide dialog controls + ShowControlsForEventType( &g_Params ); + InitControlData( &g_Params ); + + UpdateTagRadioButtons( &g_Params ); + + SetFocus( GetControl( IDC_EVENTNAME ) ); +} + +static CEventPropertiesGenericDialog g_EventPropertiesGenericDialog; + +void CEventPropertiesGenericDialog::PopulateAIGeneric( HWND control, CEventParams *params ) +{ + // FIXME: this should load from a config file + SendMessage( control, CB_ADDSTRING, 0, (LPARAM)"AI_BLINK" ); + SendMessage( control, CB_ADDSTRING, 0, (LPARAM)"AI_HOLSTER" ); + SendMessage( control, CB_ADDSTRING, 0, (LPARAM)"AI_UNHOLSTER" ); + SendMessage( control, CB_ADDSTRING, 0, (LPARAM)"AI_AIM" ); + SendMessage( control, CB_ADDSTRING, 0, (LPARAM)"AI_RANDOMLOOK" ); + SendMessage( control, CB_ADDSTRING, 0, (LPARAM)"AI_RANDOMFACEFLEX" ); + SendMessage( control, CB_ADDSTRING, 0, (LPARAM)"AI_RANDOMHEADFLEX" ); + SendMessage( control, CB_ADDSTRING, 0, (LPARAM)"AI_IGNORECOLLISION" ); + SendMessage( control, CB_ADDSTRING, 0, (LPARAM)"AI_DISABLEAI" ); + + SendMessage( control, WM_SETTEXT , 0, (LPARAM)params->m_szParameters ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : wnd - +// *params - +// Output : static +//----------------------------------------------------------------------------- + +void CEventPropertiesGenericDialog::ShowControlsForEventType( CEventParams *params ) +{ + BaseClass::ShowControlsForEventType( params ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK EventPropertiesGenericDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + return g_EventPropertiesGenericDialog.HandleMessage( hwndDlg, uMsg, wParam, lParam ); +}; + +BOOL CEventPropertiesGenericDialog::HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + m_hDialog = hwndDlg; + + bool handled = false; + BOOL bret = InternalHandleMessage( &g_Params, hwndDlg, uMsg, wParam, lParam, handled ); + if ( handled ) + return bret; + + switch(uMsg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc; + + hdc = BeginPaint(hwndDlg, &ps); + DrawSpline( hdc, GetControl( IDC_STATIC_SPLINE ), g_Params.m_pEvent ); + EndPaint(hwndDlg, &ps); + + return FALSE; + } + break; + case WM_VSCROLL: + { + RECT rcOut; + GetSplineRect( GetControl( IDC_STATIC_SPLINE ), rcOut ); + + InvalidateRect( hwndDlg, &rcOut, TRUE ); + UpdateWindow( hwndDlg ); + return FALSE; + } + break; + case WM_INITDIALOG: + { + InitDialog( hwndDlg ); + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + HWND control = GetControl( IDC_EVENTCHOICES ); + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters ), (LPARAM)g_Params.m_szParameters ); + } + + GetDlgItemText( m_hDialog, IDC_EVENTNAME, g_Params.m_szName, sizeof( g_Params.m_szName ) ); + + if ( !g_Params.m_szName[ 0 ] ) + { + Q_snprintf( g_Params.m_szName, sizeof( g_Params.m_szName ), "%s to %s", g_Params.m_szParameters, g_Params.m_szParameters2 ); + } + + char szTime[ 32 ]; + GetDlgItemText( m_hDialog, IDC_STARTTIME, szTime, sizeof( szTime ) ); + g_Params.m_flStartTime = atof( szTime ); + GetDlgItemText( m_hDialog, IDC_ENDTIME, szTime, sizeof( szTime ) ); + g_Params.m_flEndTime = atof( szTime ); + + // Parse tokens from tags + ParseTags( &g_Params ); + + EndDialog( hwndDlg, 1 ); + } + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + case IDC_CHECK_ENDTIME: + { + g_Params.m_bHasEndTime = SendMessage( GetControl( IDC_CHECK_ENDTIME ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + if ( !g_Params.m_bHasEndTime ) + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_HIDE ); + } + else + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_RESTORE ); + } + } + break; + case IDC_CHECK_RESUMECONDITION: + { + g_Params.m_bResumeCondition = SendMessage( GetControl( IDC_CHECK_RESUMECONDITION ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + } + break; + case IDC_EVENTCHOICES: + { + HWND control = (HWND)lParam; + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters ), (LPARAM)g_Params.m_szParameters ); + } + } + break; + case IDC_EVENTCHOICES2: + { + HWND control = (HWND)lParam; + if ( control ) + { + if ( g_Params.m_nType != CChoreoEvent::MOVETO ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters2 ), (LPARAM)g_Params.m_szParameters2 ); + } + else + { + char buf1[ 256 ]; + + SendMessage( GetControl( IDC_EVENTCHOICES2 ), WM_GETTEXT, (WPARAM)sizeof( buf1 ), (LPARAM)buf1 ); + + Q_snprintf( g_Params.m_szParameters2, sizeof( g_Params.m_szParameters2 ), "%s", buf1 ); + } + } + } + break; + case IDC_ABSOLUTESTART: + { + g_Params.m_bUsesTag = false; + UpdateTagRadioButtons( &g_Params ); + } + break; + case IDC_RELATIVESTART: + { + g_Params.m_bUsesTag = true; + UpdateTagRadioButtons( &g_Params ); + } + break; + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int EventProperties_Generic( CEventParams *params ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_EVENTPROPERTIES_GENERIC ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)EventPropertiesGenericDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/eventproperties_generic.h b/utils/hlfaceposer/eventproperties_generic.h new file mode 100644 index 0000000..01391ff --- /dev/null +++ b/utils/hlfaceposer/eventproperties_generic.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef EVENTPROPERTIES_GENERIC_H +#define EVENTPROPERTIES_GENERIC_H +#ifdef _WIN32 +#pragma once +#endif + +class CEventParams; + +int EventProperties_Generic( CEventParams *params ); + +#endif // EVENTPROPERTIES_GENERIC_H diff --git a/utils/hlfaceposer/eventproperties_gesture.cpp b/utils/hlfaceposer/eventproperties_gesture.cpp new file mode 100644 index 0000000..f02f320 --- /dev/null +++ b/utils/hlfaceposer/eventproperties_gesture.cpp @@ -0,0 +1,297 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include <mxtk/mx.h> +#include <stdio.h> +#include "resource.h" +#include "EventProperties.h" +#include "mdlviewer.h" +#include "choreoevent.h" +#include "StudioModel.h" +#include "faceposer_models.h" +#include "KeyValues.h" + +static CEventParams g_Params; + +class CEventPropertiesGestureDialog : public CBaseEventPropertiesDialog +{ + typedef CBaseEventPropertiesDialog BaseClass; + +public: + virtual void InitDialog( HWND hwndDlg ); + virtual BOOL HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ); + virtual void SetTitle(); + virtual void ShowControlsForEventType( CEventParams *params ); + virtual void InitControlData( CEventParams *params ); + +private: + void PopulateGestureList( HWND wnd ); + + bool CheckSequenceType( StudioModel *model, int iSequence, char *szType ); +}; + +void CEventPropertiesGestureDialog::SetTitle() +{ + SetDialogTitle( &g_Params, "Gesture", "Gesture" ); +} + +void CEventPropertiesGestureDialog::PopulateGestureList( HWND wnd ) +{ + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if (hdr) + { + int i; + for (i = 0; i < hdr->GetNumSeq(); i++) + { + if (CheckSequenceType( models->GetActiveStudioModel(), i, "gesture" )) + { + SendMessage( wnd, CB_ADDSTRING, 0, (LPARAM)hdr->pSeqdesc(i).pszLabel() ); + } + } + for (i = 0; i < hdr->GetNumSeq(); i++) + { + if (CheckSequenceType( models->GetActiveStudioModel(), i, "posture" )) + { + SendMessage( wnd, CB_ADDSTRING, 0, (LPARAM)hdr->pSeqdesc(i).pszLabel() ); + } + } + } +} + +void CEventPropertiesGestureDialog::InitControlData( CEventParams *params ) +{ + BaseClass::InitControlData( params ); + + HWND choices1 = GetControl( IDC_EVENTCHOICES ); + SendMessage( choices1, CB_RESETCONTENT, 0, 0 ); + SendMessage( choices1, WM_SETTEXT , 0, (LPARAM)params->m_szParameters ); + + SendMessage( GetControl( IDC_CHECK_SYNCTOFOLLOWINGGESTURE ), BM_SETCHECK, + ( WPARAM ) g_Params.m_bSyncToFollowingGesture ? BST_CHECKED : BST_UNCHECKED, + ( LPARAM )0 ); + + PopulateGestureList( choices1 ); +} + +void CEventPropertiesGestureDialog::InitDialog( HWND hwndDlg ) +{ + m_hDialog = hwndDlg; + + g_Params.PositionSelf( m_hDialog ); + + // Set working title for dialog, etc. + SetTitle(); + + // Show/Hide dialog controls + ShowControlsForEventType( &g_Params ); + InitControlData( &g_Params ); + + UpdateTagRadioButtons( &g_Params ); + + SetFocus( GetControl( IDC_EVENTNAME ) ); +} + +static CEventPropertiesGestureDialog g_EventPropertiesGestureDialog; + +bool CEventPropertiesGestureDialog::CheckSequenceType( StudioModel *model, int iSequence, char *szType ) +{ + KeyValues *seqKeyValues = new KeyValues(""); + bool isType = false; + if ( seqKeyValues->LoadFromBuffer( model->GetFileName( ), model->GetKeyValueText( iSequence ) ) ) + { + // Do we have a build point section? + KeyValues *pkvAllFaceposer = seqKeyValues->FindKey("faceposer"); + if ( pkvAllFaceposer ) + { + KeyValues *pkvType = pkvAllFaceposer->FindKey("type"); + + if (pkvType) + { + isType = (stricmp( pkvType->GetString(), szType ) == 0) ? true : false; + } + } + } + + seqKeyValues->deleteThis(); + + return isType; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : wnd - +// *params - +// Output : static +//----------------------------------------------------------------------------- + +void CEventPropertiesGestureDialog::ShowControlsForEventType( CEventParams *params ) +{ + BaseClass::ShowControlsForEventType( params ); + + // NULL Gesture doesn't have these controls either + if ( g_Params.m_nType == CChoreoEvent::GESTURE && + !Q_stricmp( g_Params.m_szName, "NULL" ) ) + { + ShowWindow( GetControl( IDC_EVENTNAME ), SW_HIDE ); + ShowWindow( GetControl( IDC_TAGS ), SW_HIDE ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK EventPropertiesGestureDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + return g_EventPropertiesGestureDialog.HandleMessage( hwndDlg, uMsg, wParam, lParam ); +}; + +BOOL CEventPropertiesGestureDialog::HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + m_hDialog = hwndDlg; + + bool handled = false; + BOOL bret = InternalHandleMessage( &g_Params, hwndDlg, uMsg, wParam, lParam, handled ); + if ( handled ) + return bret; + + switch(uMsg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc; + + hdc = BeginPaint(hwndDlg, &ps); + DrawSpline( hdc, GetControl( IDC_STATIC_SPLINE ), g_Params.m_pEvent ); + EndPaint(hwndDlg, &ps); + + return FALSE; + } + break; + case WM_VSCROLL: + { + RECT rcOut; + GetSplineRect( GetControl( IDC_STATIC_SPLINE ), rcOut ); + + InvalidateRect( hwndDlg, &rcOut, TRUE ); + UpdateWindow( hwndDlg ); + return FALSE; + } + break; + case WM_INITDIALOG: + { + InitDialog( hwndDlg ); + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + HWND control = GetControl( IDC_EVENTCHOICES ); + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters ), (LPARAM)g_Params.m_szParameters ); + } + + GetDlgItemText( m_hDialog, IDC_EVENTNAME, g_Params.m_szName, sizeof( g_Params.m_szName ) ); + + if ( !g_Params.m_szName[ 0 ] ) + { + Q_strncpy( g_Params.m_szName, g_Params.m_szParameters, sizeof( g_Params.m_szName ) ); + } + + char szTime[ 32 ]; + GetDlgItemText( m_hDialog, IDC_STARTTIME, szTime, sizeof( szTime ) ); + g_Params.m_flStartTime = atof( szTime ); + GetDlgItemText( m_hDialog, IDC_ENDTIME, szTime, sizeof( szTime ) ); + g_Params.m_flEndTime = atof( szTime ); + + // Parse tokens from tags + ParseTags( &g_Params ); + + EndDialog( hwndDlg, 1 ); + } + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + case IDC_CHECK_ENDTIME: + { + g_Params.m_bHasEndTime = SendMessage( GetControl( IDC_CHECK_ENDTIME ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + if ( !g_Params.m_bHasEndTime ) + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_HIDE ); + } + else + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_RESTORE ); + } + } + break; + case IDC_CHECK_RESUMECONDITION: + { + g_Params.m_bResumeCondition = SendMessage( GetControl( IDC_CHECK_RESUMECONDITION ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + } + break; + case IDC_EVENTCHOICES: + { + HWND control = (HWND)lParam; + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters ), (LPARAM)g_Params.m_szParameters ); + } + } + break; + case IDC_ABSOLUTESTART: + { + g_Params.m_bUsesTag = false; + UpdateTagRadioButtons( &g_Params ); + } + break; + case IDC_RELATIVESTART: + { + g_Params.m_bUsesTag = true; + UpdateTagRadioButtons( &g_Params ); + } + break; + case IDC_CHECK_SYNCTOFOLLOWINGGESTURE: + { + g_Params.m_bSyncToFollowingGesture = SendMessage( GetControl( IDC_CHECK_SYNCTOFOLLOWINGGESTURE ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + } + break; + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int EventProperties_Gesture( CEventParams *params ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_EVENTPROPERTIES_GESTURE ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)EventPropertiesGestureDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/eventproperties_gesture.h b/utils/hlfaceposer/eventproperties_gesture.h new file mode 100644 index 0000000..f0cf6f6 --- /dev/null +++ b/utils/hlfaceposer/eventproperties_gesture.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef EVENTPROPERTIES_GESTURE_H +#define EVENTPROPERTIES_GESTURE_H +#ifdef _WIN32 +#pragma once +#endif + +class CEventParams; + +int EventProperties_Gesture( CEventParams *params ); + +#endif // EVENTPROPERTIES_GESTURE_H diff --git a/utils/hlfaceposer/eventproperties_interrupt.cpp b/utils/hlfaceposer/eventproperties_interrupt.cpp new file mode 100644 index 0000000..7280f86 --- /dev/null +++ b/utils/hlfaceposer/eventproperties_interrupt.cpp @@ -0,0 +1,217 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include <mxtk/mx.h> +#include <stdio.h> +#include "resource.h" +#include "EventProperties.h" +#include "mdlviewer.h" + +static CEventParams g_Params; + +class CEventPropertiesInterruptDialog : public CBaseEventPropertiesDialog +{ + typedef CBaseEventPropertiesDialog BaseClass; + +public: + virtual void InitDialog( HWND hwndDlg ); + virtual BOOL HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ); + virtual void SetTitle(); + virtual void ShowControlsForEventType( CEventParams *params ); + virtual void InitControlData( CEventParams *params ); +}; + +void CEventPropertiesInterruptDialog::SetTitle() +{ + SetDialogTitle( &g_Params, "Interrupt", "Interrupt" ); +} + +void CEventPropertiesInterruptDialog::InitControlData( CEventParams *params ) +{ + BaseClass::InitControlData( params ); + + HWND choices1 = GetControl( IDC_EVENTCHOICES ); + SendMessage( choices1, CB_RESETCONTENT, 0, 0 ); + SendMessage( choices1, WM_SETTEXT , 0, (LPARAM)params->m_szParameters ); +} + +void CEventPropertiesInterruptDialog::InitDialog( HWND hwndDlg ) +{ + m_hDialog = hwndDlg; + + g_Params.PositionSelf( m_hDialog ); + + // Set working title for dialog, etc. + SetTitle(); + + // Show/Hide dialog controls + ShowControlsForEventType( &g_Params ); + InitControlData( &g_Params ); + + UpdateTagRadioButtons( &g_Params ); + + SetFocus( GetControl( IDC_EVENTNAME ) ); +} + +static CEventPropertiesInterruptDialog g_EventPropertiesInterruptDialog; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : wnd - +// *params - +// Output : static +//----------------------------------------------------------------------------- + +void CEventPropertiesInterruptDialog::ShowControlsForEventType( CEventParams *params ) +{ + BaseClass::ShowControlsForEventType( params ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK EventPropertiesInterruptDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + return g_EventPropertiesInterruptDialog.HandleMessage( hwndDlg, uMsg, wParam, lParam ); +}; + +BOOL CEventPropertiesInterruptDialog::HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + m_hDialog = hwndDlg; + + bool handled = false; + BOOL bret = InternalHandleMessage( &g_Params, hwndDlg, uMsg, wParam, lParam, handled ); + if ( handled ) + return bret; + + switch(uMsg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc; + + hdc = BeginPaint(hwndDlg, &ps); + DrawSpline( hdc, GetControl( IDC_STATIC_SPLINE ), g_Params.m_pEvent ); + EndPaint(hwndDlg, &ps); + + return FALSE; + } + break; + case WM_VSCROLL: + { + RECT rcOut; + GetSplineRect( GetControl( IDC_STATIC_SPLINE ), rcOut ); + + InvalidateRect( hwndDlg, &rcOut, TRUE ); + UpdateWindow( hwndDlg ); + return FALSE; + } + break; + case WM_INITDIALOG: + { + InitDialog( hwndDlg ); + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + HWND control = GetControl( IDC_EVENTCHOICES ); + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters ), (LPARAM)g_Params.m_szParameters ); + } + + GetDlgItemText( m_hDialog, IDC_EVENTNAME, g_Params.m_szName, sizeof( g_Params.m_szName ) ); + + if ( !g_Params.m_szName[ 0 ] ) + { + Q_strncpy( g_Params.m_szName, "Interrupt", sizeof( g_Params.m_szName ) ); + } + + char szTime[ 32 ]; + GetDlgItemText( m_hDialog, IDC_STARTTIME, szTime, sizeof( szTime ) ); + g_Params.m_flStartTime = atof( szTime ); + GetDlgItemText( m_hDialog, IDC_ENDTIME, szTime, sizeof( szTime ) ); + g_Params.m_flEndTime = atof( szTime ); + + // Parse tokens from tags + ParseTags( &g_Params ); + + EndDialog( hwndDlg, 1 ); + } + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + case IDC_CHECK_ENDTIME: + { + g_Params.m_bHasEndTime = SendMessage( GetControl( IDC_CHECK_ENDTIME ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + if ( !g_Params.m_bHasEndTime ) + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_HIDE ); + } + else + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_RESTORE ); + } + } + break; + case IDC_EVENTCHOICES: + { + HWND control = (HWND)lParam; + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters ), (LPARAM)g_Params.m_szParameters ); + } + } + break; + case IDC_ABSOLUTESTART: + { + g_Params.m_bUsesTag = false; + UpdateTagRadioButtons( &g_Params ); + } + break; + case IDC_RELATIVESTART: + { + g_Params.m_bUsesTag = true; + UpdateTagRadioButtons( &g_Params ); + } + break; + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int EventProperties_Interrupt( CEventParams *params ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_EVENTPROPERTIES_INTERRUPT ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)EventPropertiesInterruptDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/eventproperties_interrupt.h b/utils/hlfaceposer/eventproperties_interrupt.h new file mode 100644 index 0000000..d1e2254 --- /dev/null +++ b/utils/hlfaceposer/eventproperties_interrupt.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef EVENTPROPERTIES_INTERRUPT_H +#define EVENTPROPERTIES_INTERRUPT_H +#ifdef _WIN32 +#pragma once +#endif + +class CEventParams; + +int EventProperties_Interrupt( CEventParams *params ); + +#endif // EVENTPROPERTIES_INTERRUPT_H diff --git a/utils/hlfaceposer/eventproperties_lookat.cpp b/utils/hlfaceposer/eventproperties_lookat.cpp new file mode 100644 index 0000000..48097b5 --- /dev/null +++ b/utils/hlfaceposer/eventproperties_lookat.cpp @@ -0,0 +1,327 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include <mxtk/mx.h> +#include <stdio.h> +#include "resource.h" +#include "EventProperties.h" +#include "mdlviewer.h" +#include <commctrl.h> + +static CEventParams g_Params; + +class CEventPropertiesLookAtDialog : public CBaseEventPropertiesDialog +{ + typedef CBaseEventPropertiesDialog BaseClass; + +public: + virtual void InitDialog( HWND hwndDlg ); + virtual BOOL HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ); + virtual void SetTitle(); + virtual void ShowControlsForEventType( CEventParams *params ); + virtual void InitControlData( CEventParams *params ); + +private: + + void SetupLookAtControls( CEventParams *params ); + void SetPitchYawText( CEventParams *params ); +}; + +void CEventPropertiesLookAtDialog::SetTitle() +{ + SetDialogTitle( &g_Params, "LookAt", "Look At Actor" ); +} + +void CEventPropertiesLookAtDialog::SetupLookAtControls( CEventParams *params ) +{ + SetPitchYawText( params ); + + if ( params->pitch != 0 || + params->yaw != 0 ) + { + params->usepitchyaw = true; + } + else + { + params->usepitchyaw = false; + } + + HWND control = GetControl( IDC_CHECK_LOOKAT ); + SendMessage( control, BM_SETCHECK, (WPARAM) params->usepitchyaw ? BST_CHECKED : BST_UNCHECKED, 0 ); + + // Set up sliders + control = GetControl( IDC_SLIDER_PITCH ); + SendMessage( control, TBM_SETRANGE, 0, (LPARAM)MAKELONG( -100, 100 ) ); + SendMessage( control, TBM_SETPOS, 1, (LPARAM)(LONG)params->pitch ); + + control = GetControl( IDC_SLIDER_YAW ); + SendMessage( control, TBM_SETRANGE, 0, (LPARAM)MAKELONG( -100, 100 ) ); + SendMessage( control, TBM_SETPOS, 1, (LPARAM)(LONG)params->yaw ); +} + +void CEventPropertiesLookAtDialog::InitControlData( CEventParams *params ) +{ + SetDlgItemText( m_hDialog, IDC_STARTTIME, va( "%f", g_Params.m_flStartTime ) ); + SetDlgItemText( m_hDialog, IDC_ENDTIME, va( "%f", g_Params.m_flEndTime ) ); + SendMessage( GetControl( IDC_CHECK_ENDTIME ), BM_SETCHECK, + ( WPARAM ) g_Params.m_bHasEndTime ? BST_CHECKED : BST_UNCHECKED, + ( LPARAM )0 ); + + SendMessage( GetControl( IDC_CHECK_RESUMECONDITION ), BM_SETCHECK, + ( WPARAM ) g_Params.m_bResumeCondition ? BST_CHECKED : BST_UNCHECKED, + ( LPARAM )0 ); + + PopulateTagList( params ); + + HWND choices1 = GetControl( IDC_EVENTCHOICES ); + SendMessage( choices1, CB_RESETCONTENT, 0, 0 ); + SendMessage( choices1, WM_SETTEXT , 0, (LPARAM)params->m_szParameters ); + + SetupLookAtControls( params ); + PopulateNamedActorList( choices1, params ); +} + +void CEventPropertiesLookAtDialog::InitDialog( HWND hwndDlg ) +{ + m_hDialog = hwndDlg; + + g_Params.PositionSelf( m_hDialog ); + + // Set working title for dialog, etc. + SetTitle(); + + // Show/Hide dialog controls + ShowControlsForEventType( &g_Params ); + InitControlData( &g_Params ); + + UpdateTagRadioButtons( &g_Params ); + + SetFocus( GetControl( IDC_EVENTNAME ) ); +} + +static CEventPropertiesLookAtDialog g_EventPropertiesLookAtDialog; + +void CEventPropertiesLookAtDialog::SetPitchYawText( CEventParams *params ) +{ + HWND control; + + control = GetControl( IDC_STATIC_PITCHVAL ); + SendMessage( control, WM_SETTEXT , 0, (LPARAM)va( "%i", params->pitch ) ); + control = GetControl( IDC_STATIC_YAWVAL ); + SendMessage( control, WM_SETTEXT , 0, (LPARAM)va( "%i", params->yaw ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : wnd - +// *params - +// Output : static +//----------------------------------------------------------------------------- + +void CEventPropertiesLookAtDialog::ShowControlsForEventType( CEventParams *params ) +{ + BaseClass::ShowControlsForEventType( params ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK EventPropertiesLookAtDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + return g_EventPropertiesLookAtDialog.HandleMessage( hwndDlg, uMsg, wParam, lParam ); +}; + +BOOL CEventPropertiesLookAtDialog::HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + m_hDialog = hwndDlg; + + bool handled = false; + BOOL bret = InternalHandleMessage( &g_Params, hwndDlg, uMsg, wParam, lParam, handled ); + if ( handled ) + return bret; + + switch(uMsg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc; + + hdc = BeginPaint(hwndDlg, &ps); + DrawSpline( hdc, GetControl( IDC_STATIC_SPLINE ), g_Params.m_pEvent ); + EndPaint(hwndDlg, &ps); + + return FALSE; + } + break; + case WM_VSCROLL: + { + RECT rcOut; + GetSplineRect( GetControl( IDC_STATIC_SPLINE ), rcOut ); + + InvalidateRect( hwndDlg, &rcOut, TRUE ); + UpdateWindow( hwndDlg ); + return FALSE; + } + break; + case WM_INITDIALOG: + { + InitDialog( hwndDlg ); + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + HWND control = GetControl( IDC_EVENTCHOICES ); + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters ), (LPARAM)g_Params.m_szParameters ); + } + + GetDlgItemText( m_hDialog, IDC_EVENTNAME, g_Params.m_szName, sizeof( g_Params.m_szName ) ); + + if ( !g_Params.m_szName[ 0 ] ) + { + Q_snprintf( g_Params.m_szName, sizeof( g_Params.m_szName ), "Look at %s", g_Params.m_szParameters ); + } + + char szTime[ 32 ]; + GetDlgItemText( m_hDialog, IDC_STARTTIME, szTime, sizeof( szTime ) ); + g_Params.m_flStartTime = atof( szTime ); + GetDlgItemText( m_hDialog, IDC_ENDTIME, szTime, sizeof( szTime ) ); + g_Params.m_flEndTime = atof( szTime ); + + // Parse tokens from tags + ParseTags( &g_Params ); + + EndDialog( hwndDlg, 1 ); + } + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + case IDC_CHECK_ENDTIME: + { + g_Params.m_bHasEndTime = SendMessage( GetControl( IDC_CHECK_ENDTIME ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + if ( !g_Params.m_bHasEndTime ) + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_HIDE ); + } + else + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_RESTORE ); + } + } + break; + case IDC_CHECK_RESUMECONDITION: + { + g_Params.m_bResumeCondition = SendMessage( GetControl( IDC_CHECK_RESUMECONDITION ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + } + break; + case IDC_EVENTCHOICES: + { + HWND control = (HWND)lParam; + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters ), (LPARAM)g_Params.m_szParameters ); + } + } + break; + case IDC_ABSOLUTESTART: + { + g_Params.m_bUsesTag = false; + UpdateTagRadioButtons( &g_Params ); + } + break; + case IDC_RELATIVESTART: + { + g_Params.m_bUsesTag = true; + UpdateTagRadioButtons( &g_Params ); + } + break; + case IDC_CHECK_LOOKAT: + { + HWND control = GetControl( IDC_CHECK_LOOKAT ); + bool checked = SendMessage( control, BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + if ( !checked ) + { + g_Params.yaw = 0; + g_Params.pitch = 0; + + SetPitchYawText( &g_Params ); + + control = GetControl( IDC_SLIDER_PITCH ); + SendMessage( control, TBM_SETPOS, 1, (LPARAM)(LONG)g_Params.pitch ); + + control = GetControl( IDC_SLIDER_YAW ); + SendMessage( control, TBM_SETPOS, 1, (LPARAM)(LONG)g_Params.yaw ); + } + + } + break; + } + return TRUE; + case WM_HSCROLL: + { + HWND control = (HWND)lParam; + if ( control == GetControl( IDC_SLIDER_YAW ) || + control == GetControl( IDC_SLIDER_PITCH ) ) + { + g_Params.yaw = (float)SendMessage( GetControl( IDC_SLIDER_YAW ), TBM_GETPOS, 0, 0 ); + g_Params.pitch = (float)SendMessage( GetControl( IDC_SLIDER_PITCH ), TBM_GETPOS, 0, 0 ); + + SetPitchYawText( &g_Params ); + + control = GetControl( IDC_CHECK_LOOKAT ); + if ( g_Params.pitch != 0 || + g_Params.yaw != 0 ) + { + g_Params.usepitchyaw = true; + } + else + { + g_Params.usepitchyaw = false; + } + + SendMessage( control, BM_SETCHECK, (WPARAM) g_Params.usepitchyaw ? BST_CHECKED : BST_UNCHECKED, 0 ); + + return TRUE; + } + } + return FALSE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int EventProperties_LookAt( CEventParams *params ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_EVENTPROPERTIES_LOOKAT ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)EventPropertiesLookAtDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/eventproperties_lookat.h b/utils/hlfaceposer/eventproperties_lookat.h new file mode 100644 index 0000000..9cef9d8 --- /dev/null +++ b/utils/hlfaceposer/eventproperties_lookat.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef EVENTPROPERTIES_LOOKAT_H +#define EVENTPROPERTIES_LOOKAT_H +#ifdef _WIN32 +#pragma once +#endif + +class CEventParams; + +int EventProperties_LookAt( CEventParams *params ); + +#endif // EVENTPROPERTIES_LOOKAT_H diff --git a/utils/hlfaceposer/eventproperties_moveto.cpp b/utils/hlfaceposer/eventproperties_moveto.cpp new file mode 100644 index 0000000..34d049d --- /dev/null +++ b/utils/hlfaceposer/eventproperties_moveto.cpp @@ -0,0 +1,368 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include <mxtk/mx.h> +#include <stdio.h> +#include "resource.h" +#include "EventProperties.h" +#include "mdlviewer.h" +#include "choreoevent.h" +#include "filesystem.h" +#include <commctrl.h> +#include "scriplib.h" + + +static CEventParams g_Params; + +class CEventPropertiesMoveToDialog : public CBaseEventPropertiesDialog +{ + typedef CBaseEventPropertiesDialog BaseClass; + +public: + virtual void InitDialog( HWND hwndDlg ); + virtual BOOL HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ); + virtual void SetTitle(); + virtual void ShowControlsForEventType( CEventParams *params ); + virtual void InitControlData( CEventParams *params ); + +private: + + void PopulateMovementStyle( HWND control, CEventParams *params ); + void SetDistanceToTargetText( CEventParams *params ); +}; + +void CEventPropertiesMoveToDialog::SetTitle() +{ + SetDialogTitle( &g_Params, "MoveTo", "Move To Actor" ); +} + +void CEventPropertiesMoveToDialog::InitControlData( CEventParams *params ) +{ + SetDlgItemText( m_hDialog, IDC_STARTTIME, va( "%f", g_Params.m_flStartTime ) ); + SetDlgItemText( m_hDialog, IDC_ENDTIME, va( "%f", g_Params.m_flEndTime ) ); + SendMessage( GetControl( IDC_CHECK_ENDTIME ), BM_SETCHECK, + ( WPARAM ) g_Params.m_bHasEndTime ? BST_CHECKED : BST_UNCHECKED, + ( LPARAM )0 ); + + SendMessage( GetControl( IDC_CHECK_RESUMECONDITION ), BM_SETCHECK, + ( WPARAM ) g_Params.m_bResumeCondition ? BST_CHECKED : BST_UNCHECKED, + ( LPARAM )0 ); + + PopulateTagList( params ); + + HWND choices1 = GetControl( IDC_EVENTCHOICES ); + SendMessage( choices1, CB_RESETCONTENT, 0, 0 ); + SendMessage( choices1, WM_SETTEXT , 0, (LPARAM)params->m_szParameters ); + + HWND choices2 = GetControl( IDC_EVENTCHOICES2 ); + SendMessage( choices2, CB_RESETCONTENT, 0, 0 ); + SendMessage( choices2, WM_SETTEXT , 0, (LPARAM)params->m_szParameters2 ); + + HWND choices3 = GetControl( IDC_EVENTCHOICES3 ); + SendMessage( choices3, CB_RESETCONTENT, 0, 0 ); + SendMessage( choices3, WM_SETTEXT , 0, (LPARAM)params->m_szParameters3 ); + + HWND control = GetControl( IDC_SLIDER_DISTANCE ); + SendMessage( control, TBM_SETRANGE, 0, (LPARAM)MAKELONG( 0, 200 ) ); + SendMessage( control, TBM_SETPOS, 1, (LPARAM)(LONG)params->m_flDistanceToTarget ); + + SendMessage( GetControl( IDC_CHECK_FORCESHORTMOVEMENT ), BM_SETCHECK, + ( WPARAM ) g_Params.m_bForceShortMovement ? BST_CHECKED : BST_UNCHECKED, + ( LPARAM )0 ); + + PopulateNamedActorList( choices1, params ); + + SendMessage( GetControl( IDC_CHOICES2PROMPT ), WM_SETTEXT, 0, (LPARAM)"Movement Style:" ); + + PopulateMovementStyle( choices2, params ); + + if (strlen( params->m_szParameters3 ) != 0) + { + // make sure blank is a valid choice + SendMessage( choices3, CB_ADDSTRING, 0, (LPARAM)"" ); + } + PopulateNamedActorList( choices3, params ); + + SetDistanceToTargetText( params ); +} + +void CEventPropertiesMoveToDialog::InitDialog( HWND hwndDlg ) +{ + m_hDialog = hwndDlg; + + g_Params.PositionSelf( m_hDialog ); + + // Set working title for dialog, etc. + SetTitle(); + + // Show/Hide dialog controls + ShowControlsForEventType( &g_Params ); + InitControlData( &g_Params ); + + UpdateTagRadioButtons( &g_Params ); + + SetFocus( GetControl( IDC_EVENTNAME ) ); +} + +static CEventPropertiesMoveToDialog g_EventPropertiesMoveToDialog; + +void CEventPropertiesMoveToDialog::PopulateMovementStyle( HWND control, CEventParams *params ) +{ + char movement_style[ 256 ]; + char distance_to_target[ 256 ]; + + movement_style[0] = 0; + distance_to_target[0]= 0; + + ParseFromMemory( params->m_szParameters2, strlen( params->m_szParameters2 ) ); + if ( TokenAvailable() ) + { + GetToken( false ); + strcpy( movement_style, token ); + if ( TokenAvailable() ) + { + GetToken( false ); + strcpy( distance_to_target, token ); + } + } + + SendMessage( control, CB_ADDSTRING, 0, (LPARAM)"Walk" ); + SendMessage( control, CB_ADDSTRING, 0, (LPARAM)"Run" ); + SendMessage( control, CB_ADDSTRING, 0, (LPARAM)"CrouchWalk" ); + + SendMessage( control, WM_SETTEXT , 0, (LPARAM)movement_style ); +} + + +void CEventPropertiesMoveToDialog::SetDistanceToTargetText( CEventParams *params ) +{ + HWND control; + + control = GetControl( IDC_STATIC_DISTANCEVAL ); + SendMessage( control, WM_SETTEXT , 0, (LPARAM)va( "%i", (int)params->m_flDistanceToTarget ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : wnd - +// *params - +// Output : static +//----------------------------------------------------------------------------- + +void CEventPropertiesMoveToDialog::ShowControlsForEventType( CEventParams *params ) +{ + BaseClass::ShowControlsForEventType( params ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK EventPropertiesMoveToDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + return g_EventPropertiesMoveToDialog.HandleMessage( hwndDlg, uMsg, wParam, lParam ); +}; + +BOOL CEventPropertiesMoveToDialog::HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + m_hDialog = hwndDlg; + + bool handled = false; + BOOL bret = InternalHandleMessage( &g_Params, hwndDlg, uMsg, wParam, lParam, handled ); + if ( handled ) + return bret; + + switch(uMsg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc; + + hdc = BeginPaint(hwndDlg, &ps); + DrawSpline( hdc, GetControl( IDC_STATIC_SPLINE ), g_Params.m_pEvent ); + EndPaint(hwndDlg, &ps); + + return FALSE; + } + break; + case WM_VSCROLL: + { + RECT rcOut; + GetSplineRect( GetControl( IDC_STATIC_SPLINE ), rcOut ); + + InvalidateRect( hwndDlg, &rcOut, TRUE ); + UpdateWindow( hwndDlg ); + return FALSE; + } + break; + case WM_INITDIALOG: + { + InitDialog( hwndDlg ); + } + return FALSE; + + case WM_HSCROLL: + { + HWND control = (HWND)lParam; + if ( control == GetControl( IDC_SLIDER_DISTANCE )) + { + g_Params.m_flDistanceToTarget = (float)SendMessage( GetControl( IDC_SLIDER_DISTANCE ), TBM_GETPOS, 0, 0 ); + + SetDistanceToTargetText( &g_Params ); + return TRUE; + } + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + HWND control = GetControl( IDC_EVENTCHOICES ); + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters ), (LPARAM)g_Params.m_szParameters ); + } + + GetDlgItemText( m_hDialog, IDC_EVENTNAME, g_Params.m_szName, sizeof( g_Params.m_szName ) ); + + if ( !g_Params.m_szName[ 0 ] ) + { + Q_snprintf( g_Params.m_szName, sizeof( g_Params.m_szName ), "Moveto %s", g_Params.m_szParameters ); + } + + char szTime[ 32 ]; + GetDlgItemText( m_hDialog, IDC_STARTTIME, szTime, sizeof( szTime ) ); + g_Params.m_flStartTime = atof( szTime ); + GetDlgItemText( m_hDialog, IDC_ENDTIME, szTime, sizeof( szTime ) ); + g_Params.m_flEndTime = atof( szTime ); + + // Parse tokens from tags + ParseTags( &g_Params ); + + EndDialog( hwndDlg, 1 ); + } + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + case IDC_CHECK_ENDTIME: + { + g_Params.m_bHasEndTime = SendMessage( GetControl( IDC_CHECK_ENDTIME ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + if ( !g_Params.m_bHasEndTime ) + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_HIDE ); + } + else + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_RESTORE ); + } + } + break; + case IDC_CHECK_RESUMECONDITION: + { + g_Params.m_bResumeCondition = SendMessage( GetControl( IDC_CHECK_RESUMECONDITION ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + } + break; + case IDC_EVENTCHOICES: + { + HWND control = (HWND)lParam; + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters ), (LPARAM)g_Params.m_szParameters ); + } + } + break; + case IDC_EVENTCHOICES2: + { + HWND control = (HWND)lParam; + if ( control ) + { + if ( g_Params.m_nType != CChoreoEvent::MOVETO ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters2 ), (LPARAM)g_Params.m_szParameters2 ); + } + else + { + char buf1[ 256 ]; + SendMessage( GetControl( IDC_EVENTCHOICES2 ), WM_GETTEXT, (WPARAM)sizeof( buf1 ), (LPARAM)buf1 ); + + Q_snprintf( g_Params.m_szParameters2, sizeof( g_Params.m_szParameters2 ), "%s", buf1 ); + } + } + } + break; + case IDC_EVENTCHOICES3: + { + HWND control = (HWND)lParam; + if ( control ) + { + if ( g_Params.m_nType != CChoreoEvent::MOVETO ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters3 ), (LPARAM)g_Params.m_szParameters3 ); + } + else + { + char buf1[ 256 ]; + SendMessage( GetControl( IDC_EVENTCHOICES3 ), WM_GETTEXT, (WPARAM)sizeof( buf1 ), (LPARAM)buf1 ); + + Q_snprintf( g_Params.m_szParameters3, sizeof( g_Params.m_szParameters3 ), "%s", buf1 ); + } + } + } + break; + case IDC_ABSOLUTESTART: + { + g_Params.m_bUsesTag = false; + UpdateTagRadioButtons( &g_Params ); + } + break; + case IDC_RELATIVESTART: + { + g_Params.m_bUsesTag = true; + UpdateTagRadioButtons( &g_Params ); + } + break; + case IDC_CHECK_FORCESHORTMOVEMENT: + { + g_Params.m_bForceShortMovement = SendMessage( GetControl( IDC_CHECK_FORCESHORTMOVEMENT ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + } + break; + default: + return FALSE; + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int EventProperties_MoveTo( CEventParams *params ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_EVENTPROPERTIES_MOVETO ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)EventPropertiesMoveToDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/eventproperties_moveto.h b/utils/hlfaceposer/eventproperties_moveto.h new file mode 100644 index 0000000..9dc683d --- /dev/null +++ b/utils/hlfaceposer/eventproperties_moveto.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef EVENTPROPERTIES_MOVETO_H +#define EVENTPROPERTIES_MOVETO_H +#ifdef _WIN32 +#pragma once +#endif + +class CEventParams; + +int EventProperties_MoveTo( CEventParams *params ); + +#endif // EVENTPROPERTIES_MOVETO_H diff --git a/utils/hlfaceposer/eventproperties_permitresponses.cpp b/utils/hlfaceposer/eventproperties_permitresponses.cpp new file mode 100644 index 0000000..c4220a1 --- /dev/null +++ b/utils/hlfaceposer/eventproperties_permitresponses.cpp @@ -0,0 +1,217 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include <mxtk/mx.h> +#include <stdio.h> +#include "resource.h" +#include "EventProperties.h" +#include "mdlviewer.h" + +static CEventParams g_Params; + +class CEventPropertiesPermitResponsesDialog : public CBaseEventPropertiesDialog +{ + typedef CBaseEventPropertiesDialog BaseClass; + +public: + virtual void InitDialog( HWND hwndDlg ); + virtual BOOL HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ); + virtual void SetTitle(); + virtual void ShowControlsForEventType( CEventParams *params ); + virtual void InitControlData( CEventParams *params ); +}; + +void CEventPropertiesPermitResponsesDialog::SetTitle() +{ + SetDialogTitle( &g_Params, "Permit Responses", "Permit Responses" ); +} + +void CEventPropertiesPermitResponsesDialog::InitControlData( CEventParams *params ) +{ + BaseClass::InitControlData( params ); + + HWND choices1 = GetControl( IDC_EVENTCHOICES ); + SendMessage( choices1, CB_RESETCONTENT, 0, 0 ); + SendMessage( choices1, WM_SETTEXT , 0, (LPARAM)params->m_szParameters ); +} + +void CEventPropertiesPermitResponsesDialog::InitDialog( HWND hwndDlg ) +{ + m_hDialog = hwndDlg; + + g_Params.PositionSelf( m_hDialog ); + + // Set working title for dialog, etc. + SetTitle(); + + // Show/Hide dialog controls + ShowControlsForEventType( &g_Params ); + InitControlData( &g_Params ); + + UpdateTagRadioButtons( &g_Params ); + + SetFocus( GetControl( IDC_EVENTNAME ) ); +} + +static CEventPropertiesPermitResponsesDialog g_EventPropertiesPermitResponsesDialog; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : wnd - +// *params - +// Output : static +//----------------------------------------------------------------------------- + +void CEventPropertiesPermitResponsesDialog::ShowControlsForEventType( CEventParams *params ) +{ + BaseClass::ShowControlsForEventType( params ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK EventPropertiesPermitResponsesDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + return g_EventPropertiesPermitResponsesDialog.HandleMessage( hwndDlg, uMsg, wParam, lParam ); +}; + +BOOL CEventPropertiesPermitResponsesDialog::HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + m_hDialog = hwndDlg; + + bool handled = false; + BOOL bret = InternalHandleMessage( &g_Params, hwndDlg, uMsg, wParam, lParam, handled ); + if ( handled ) + return bret; + + switch(uMsg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc; + + hdc = BeginPaint(hwndDlg, &ps); + DrawSpline( hdc, GetControl( IDC_STATIC_SPLINE ), g_Params.m_pEvent ); + EndPaint(hwndDlg, &ps); + + return FALSE; + } + break; + case WM_VSCROLL: + { + RECT rcOut; + GetSplineRect( GetControl( IDC_STATIC_SPLINE ), rcOut ); + + InvalidateRect( hwndDlg, &rcOut, TRUE ); + UpdateWindow( hwndDlg ); + return FALSE; + } + break; + case WM_INITDIALOG: + { + InitDialog( hwndDlg ); + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + HWND control = GetControl( IDC_EVENTCHOICES ); + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters ), (LPARAM)g_Params.m_szParameters ); + } + + GetDlgItemText( m_hDialog, IDC_EVENTNAME, g_Params.m_szName, sizeof( g_Params.m_szName ) ); + + if ( !g_Params.m_szName[ 0 ] ) + { + Q_strncpy( g_Params.m_szName, "Permit Responses", sizeof( g_Params.m_szName ) ); + } + + char szTime[ 32 ]; + GetDlgItemText( m_hDialog, IDC_STARTTIME, szTime, sizeof( szTime ) ); + g_Params.m_flStartTime = atof( szTime ); + GetDlgItemText( m_hDialog, IDC_ENDTIME, szTime, sizeof( szTime ) ); + g_Params.m_flEndTime = atof( szTime ); + + // Parse tokens from tags + ParseTags( &g_Params ); + + EndDialog( hwndDlg, 1 ); + } + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + case IDC_CHECK_ENDTIME: + { + g_Params.m_bHasEndTime = SendMessage( GetControl( IDC_CHECK_ENDTIME ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + if ( !g_Params.m_bHasEndTime ) + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_HIDE ); + } + else + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_RESTORE ); + } + } + break; + case IDC_EVENTCHOICES: + { + HWND control = (HWND)lParam; + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters ), (LPARAM)g_Params.m_szParameters ); + } + } + break; + case IDC_ABSOLUTESTART: + { + g_Params.m_bUsesTag = false; + UpdateTagRadioButtons( &g_Params ); + } + break; + case IDC_RELATIVESTART: + { + g_Params.m_bUsesTag = true; + UpdateTagRadioButtons( &g_Params ); + } + break; + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int EventProperties_PermitResponses( CEventParams *params ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_EVENTPROPERTIES_PERMITRESPONSES ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)EventPropertiesPermitResponsesDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/eventproperties_permitresponses.h b/utils/hlfaceposer/eventproperties_permitresponses.h new file mode 100644 index 0000000..81195d8 --- /dev/null +++ b/utils/hlfaceposer/eventproperties_permitresponses.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef EVENTPROPERTIES_PERMITRESPONSES_H +#define EVENTPROPERTIES_PERMITRESPONSES_H +#ifdef _WIN32 +#pragma once +#endif + +class CEventParams; + +int EventProperties_PermitResponses( CEventParams *params ); + +#endif // EVENTPROPERTIES_PERMITRESPONSES_H diff --git a/utils/hlfaceposer/eventproperties_sequence.cpp b/utils/hlfaceposer/eventproperties_sequence.cpp new file mode 100644 index 0000000..56f1cbf --- /dev/null +++ b/utils/hlfaceposer/eventproperties_sequence.cpp @@ -0,0 +1,251 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include <mxtk/mx.h> +#include <stdio.h> +#include "resource.h" +#include "EventProperties.h" +#include "mdlviewer.h" +#include "StudioModel.h" +#include "faceposer_models.h" + +static CEventParams g_Params; + +class CEventPropertiesSequenceDialog : public CBaseEventPropertiesDialog +{ + typedef CBaseEventPropertiesDialog BaseClass; + +public: + virtual void InitDialog( HWND hwndDlg ); + virtual BOOL HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ); + virtual void SetTitle(); + virtual void ShowControlsForEventType( CEventParams *params ); + virtual void InitControlData( CEventParams *params ); + +private: + + void PopulateSequenceList( HWND wnd ); +}; + +void CEventPropertiesSequenceDialog::SetTitle() +{ + SetDialogTitle( &g_Params, "Sequence", "Sequence" ); +} + +void CEventPropertiesSequenceDialog::PopulateSequenceList( HWND wnd ) +{ + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if (hdr) + { + for (int i = 0; i < hdr->GetNumSeq(); i++) + { + SendMessage( wnd, CB_ADDSTRING, 0, (LPARAM)hdr->pSeqdesc(i).pszLabel() ); + } + } +} + +void CEventPropertiesSequenceDialog::InitControlData( CEventParams *params ) +{ + BaseClass::InitControlData( params ); + + HWND choices1 = GetControl( IDC_EVENTCHOICES ); + SendMessage( choices1, CB_RESETCONTENT, 0, 0 ); + SendMessage( choices1, WM_SETTEXT , 0, (LPARAM)params->m_szParameters ); + + SendMessage( GetControl( IDC_CHECK_PLAYOVERSCRIPT ), BM_SETCHECK, + ( WPARAM ) g_Params.m_bPlayOverScript ? BST_CHECKED : BST_UNCHECKED, + ( LPARAM )0 ); + + PopulateSequenceList( choices1 ); +} + +void CEventPropertiesSequenceDialog::InitDialog( HWND hwndDlg ) +{ + m_hDialog = hwndDlg; + + g_Params.PositionSelf( m_hDialog ); + + // Set working title for dialog, etc. + SetTitle(); + + // Show/Hide dialog controls + ShowControlsForEventType( &g_Params ); + InitControlData( &g_Params ); + + UpdateTagRadioButtons( &g_Params ); + + SetFocus( GetControl( IDC_EVENTNAME ) ); +} + +static CEventPropertiesSequenceDialog g_EventPropertiesSequenceDialog; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : wnd - +// *params - +// Output : static +//----------------------------------------------------------------------------- + +void CEventPropertiesSequenceDialog::ShowControlsForEventType( CEventParams *params ) +{ + BaseClass::ShowControlsForEventType( params ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK EventPropertiesSequenceDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + return g_EventPropertiesSequenceDialog.HandleMessage( hwndDlg, uMsg, wParam, lParam ); +}; + +BOOL CEventPropertiesSequenceDialog::HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + m_hDialog = hwndDlg; + + bool handled = false; + BOOL bret = InternalHandleMessage( &g_Params, hwndDlg, uMsg, wParam, lParam, handled ); + if ( handled ) + return bret; + + switch(uMsg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc; + + hdc = BeginPaint(hwndDlg, &ps); + DrawSpline( hdc, GetControl( IDC_STATIC_SPLINE ), g_Params.m_pEvent ); + EndPaint(hwndDlg, &ps); + + return FALSE; + } + break; + case WM_VSCROLL: + { + RECT rcOut; + GetSplineRect( GetControl( IDC_STATIC_SPLINE ), rcOut ); + + InvalidateRect( hwndDlg, &rcOut, TRUE ); + UpdateWindow( hwndDlg ); + return FALSE; + } + break; + case WM_INITDIALOG: + { + InitDialog( hwndDlg ); + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + HWND control = GetControl( IDC_EVENTCHOICES ); + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters ), (LPARAM)g_Params.m_szParameters ); + } + + GetDlgItemText( m_hDialog, IDC_EVENTNAME, g_Params.m_szName, sizeof( g_Params.m_szName ) ); + + if ( !g_Params.m_szName[ 0 ] ) + { + Q_strncpy( g_Params.m_szName, g_Params.m_szParameters, sizeof( g_Params.m_szName ) ); + } + + char szTime[ 32 ]; + GetDlgItemText( m_hDialog, IDC_STARTTIME, szTime, sizeof( szTime ) ); + g_Params.m_flStartTime = atof( szTime ); + GetDlgItemText( m_hDialog, IDC_ENDTIME, szTime, sizeof( szTime ) ); + g_Params.m_flEndTime = atof( szTime ); + + // Parse tokens from tags + ParseTags( &g_Params ); + + EndDialog( hwndDlg, 1 ); + } + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + case IDC_CHECK_ENDTIME: + { + g_Params.m_bHasEndTime = SendMessage( GetControl( IDC_CHECK_ENDTIME ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + if ( !g_Params.m_bHasEndTime ) + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_HIDE ); + } + else + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_RESTORE ); + } + } + break; + case IDC_CHECK_RESUMECONDITION: + { + g_Params.m_bResumeCondition = SendMessage( GetControl( IDC_CHECK_RESUMECONDITION ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + } + break; + case IDC_CHECK_PLAYOVERSCRIPT: + { + g_Params.m_bPlayOverScript = SendMessage( GetControl( IDC_CHECK_PLAYOVERSCRIPT ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + } + break; + case IDC_EVENTCHOICES: + { + HWND control = (HWND)lParam; + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters ), (LPARAM)g_Params.m_szParameters ); + } + } + break; + case IDC_ABSOLUTESTART: + { + g_Params.m_bUsesTag = false; + UpdateTagRadioButtons( &g_Params ); + } + break; + case IDC_RELATIVESTART: + { + g_Params.m_bUsesTag = true; + UpdateTagRadioButtons( &g_Params ); + } + break; + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int EventProperties_Sequence( CEventParams *params ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_EVENTPROPERTIES_SEQUENCE ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)EventPropertiesSequenceDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/eventproperties_sequence.h b/utils/hlfaceposer/eventproperties_sequence.h new file mode 100644 index 0000000..ed45e02 --- /dev/null +++ b/utils/hlfaceposer/eventproperties_sequence.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef EVENTPROPERTIES_SEQUENCE_H +#define EVENTPROPERTIES_SEQUENCE_H +#ifdef _WIN32 +#pragma once +#endif + +class CEventParams; + +int EventProperties_Sequence( CEventParams *params ); + +#endif // EVENTPROPERTIES_SEQUENCE_H diff --git a/utils/hlfaceposer/eventproperties_speak.cpp b/utils/hlfaceposer/eventproperties_speak.cpp new file mode 100644 index 0000000..363c77f --- /dev/null +++ b/utils/hlfaceposer/eventproperties_speak.cpp @@ -0,0 +1,673 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include <mxtk/mx.h> +#include <stdio.h> +#include "resource.h" +#include "EventProperties.h" +#include "mdlviewer.h" +#include "choreoevent.h" +#include "filesystem.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "AddSoundEntry.h" +#include "SoundLookup.h" +#include "ifaceposersound.h" +#include "MatSysWin.h" + +static CEventParams g_Params; + +class CEventPropertiesSpeakDialog : public CBaseEventPropertiesDialog +{ + typedef CBaseEventPropertiesDialog BaseClass; + +public: + + CEventPropertiesSpeakDialog() + { + m_bShowAll = false; + m_szLastFilter[ 0 ] = 0; + m_Timer = 0; + m_flLastFilterUpdateTime; + } + + virtual void InitDialog( HWND hwndDlg ); + virtual BOOL HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ); + virtual void SetTitle(); + virtual void ShowControlsForEventType( CEventParams *params ); + virtual void InitControlData( CEventParams *params ); + +private: + + void PopulateFilterList( bool resetCurrent ); + void OnCheckFilterUpdate(); + + void AddFilterToHistory( char const *filter ); + + void OnSoundSelected( CEventParams *params ); + + void PopulateSoundList( char const *current, HWND wnd ); + void PopulateVolumeLevels( HWND control, CEventParams *params ); + + void FindWaveInSoundEntries( CUtlVector< int >& entryList, char const *search ); + void OnCheckChangedVolumeLevel( CEventParams *params ); + + bool m_bShowAll; + + CUtlVector< CUtlSymbol > m_FilterHistory; + CUtlSymbolTable m_Symbols; + + enum + { + TIMER_ID = 100, + }; + + UINT m_Timer; + char m_szLastFilter[ 256 ]; + float m_flLastFilterUpdateTime; +}; + +void CEventPropertiesSpeakDialog::AddFilterToHistory( char const *filter ) +{ + CUtlSymbol sym = m_Symbols.AddString( filter ); + // Move it to front of list... + m_FilterHistory.FindAndRemove( sym ); + m_FilterHistory.AddToHead( sym ); + + PopulateFilterList( false ); + + // Apply filter + + PopulateSoundList( g_Params.m_szParameters, GetControl( IDC_SOUNDLIST ) ); +} + +void CEventPropertiesSpeakDialog::PopulateFilterList( bool resetCurrent ) +{ + HWND control = GetControl( IDC_FILTER ); + + char oldf[ 256 ]; + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( oldf ), (LPARAM)oldf ); + + SendMessage( control, CB_RESETCONTENT, 0, 0 ); + + int c = m_FilterHistory.Count(); + if ( c == 0 ) + return; + + for ( int i = 0; i < c; ++i ) + { + char const *str = m_Symbols.String( m_FilterHistory[ i ] ); + SendMessage( control, CB_ADDSTRING, 0, (LPARAM)str ); + } + + char const *first = m_Symbols.String( m_FilterHistory[ 0 ] ); + if ( resetCurrent && first ) + { + SendMessage( control, WM_SETTEXT , 0, (LPARAM)first ); + SendMessage( control, CB_SETEDITSEL , 0, MAKELPARAM( Q_strlen(first), -1 ) ); + } + else + { + SendMessage( control, WM_SETTEXT , 0, (LPARAM)oldf ); + SendMessage( control, CB_SETEDITSEL , 0, MAKELPARAM( Q_strlen(oldf), -1 ) ); + } +} + +void CEventPropertiesSpeakDialog::OnCheckFilterUpdate() +{ + char curfilter[ 256 ]; + HWND control = GetControl( IDC_FILTER ); + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( curfilter), (LPARAM)curfilter ); + + if ( Q_stricmp( curfilter, m_szLastFilter ) ) + { + Q_strncpy( m_szLastFilter, curfilter, sizeof( m_szLastFilter ) ); + + AddFilterToHistory( m_szLastFilter ); + } +} + +void CEventPropertiesSpeakDialog::SetTitle() +{ + SetDialogTitle( &g_Params, "Speak", "Speak Sound" ); +} + +void CEventPropertiesSpeakDialog::InitControlData( CEventParams *params ) +{ + BaseClass::InitControlData( params ); + + m_flLastFilterUpdateTime = (float)mx::getTickCount() / 1000.0f; + + m_Timer = SetTimer( m_hDialog, TIMER_ID, 1, 0 ); + + HWND choices1 = GetControl( IDC_SOUNDLIST ); + SendMessage( choices1, LB_RESETCONTENT, 0, 0 ); + + HWND choices2 = GetControl( IDC_EVENTCHOICES2 ); + SendMessage( choices2, CB_RESETCONTENT, 0, 0 ); + SendMessage( choices2, WM_SETTEXT , 0, (LPARAM)params->m_szParameters2 ); + + HWND attenuate = GetControl( IDC_CAPTION_ATTENUATION ); + SendMessage( attenuate, BM_SETCHECK, (WPARAM) params->m_bCloseCaptionNoAttenuate ? BST_CHECKED : BST_UNCHECKED, 0 ); + + PopulateSoundList( params->m_szParameters, choices1 ); + + OnSoundSelected( params ); + + PopulateFilterList( true ); +} + +void CEventPropertiesSpeakDialog::InitDialog( HWND hwndDlg ) +{ + m_hDialog = hwndDlg; + + g_Params.PositionSelf( m_hDialog ); + + // Set working title for dialog, etc. + SetTitle(); + + // Show/Hide dialog controls + ShowControlsForEventType( &g_Params ); + InitControlData( &g_Params ); + + UpdateTagRadioButtons( &g_Params ); + + SetFocus( GetControl( IDC_EVENTNAME ) ); +} + +static CEventPropertiesSpeakDialog g_EventPropertiesSpeakDialog; + +void CEventPropertiesSpeakDialog::PopulateVolumeLevels( HWND control, CEventParams *params ) +{ + SendMessage( control, CB_RESETCONTENT, 0, 0 ); + + // Assume uneditable + SendMessage( control, CB_ADDSTRING, 0, (LPARAM)"VOL_NORM" ); + + SendMessage( control, WM_SETTEXT , 0, (LPARAM)"VOL_NORM" ); + + bool enabled = false; + + if ( !Q_stristr( params->m_szParameters, ".wav" ) ) + { + // Look up the sound level from the soundemitter system + int soundindex = soundemitter->GetSoundIndex( params->m_szParameters ); + if ( soundindex >= 0 ) + { + // Look up the sound level from the soundemitter system + CSoundParametersInternal *params = soundemitter->InternalGetParametersForSound( soundindex ); + if ( params ) + { + // Found it + SendMessage( control, WM_SETTEXT , 0, (LPARAM)params->VolumeToString() ); + + // + // See if the .txt file is writable + char const *scriptfile = soundemitter->GetSourceFileForSound( soundindex ); + if ( scriptfile ) + { + // See if it's writable + if ( filesystem->FileExists( scriptfile ) && + filesystem->IsFileWritable( scriptfile ) ) + { + enabled = true; + } + } + } + } + } + + EnableWindow( control, enabled ? TRUE : FALSE ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : wnd - +// Output : static void +//----------------------------------------------------------------------------- +void CEventPropertiesSpeakDialog::PopulateSoundList( char const *current, HWND wnd ) +{ + extern bool NameLessFunc( const char *const& name1, const char *const& name2 ); + + CUtlRBTree< char const *, int > m_SortedNames( 0, 0, NameLessFunc ); + + int c = soundemitter->GetSoundCount(); + for ( int i = 0; i < c; i++ ) + { + char const *name = soundemitter->GetSoundName( i ); + + if ( name && name[ 0 ] ) + { + bool add = true; + if ( !m_bShowAll ) + { + CSoundParameters params; + if ( soundemitter->GetParametersForSound( name, params, GENDER_NONE ) ) + { + if ( params.channel != CHAN_VOICE ) + { + add = false; + } + } + } + + // Apply filter + if ( m_szLastFilter[ 0 ] != 0 ) + { + if ( !Q_stristr( name, m_szLastFilter ) ) + { + add = false; + } + } + + if ( add ) + { + m_SortedNames.Insert( name ); + } + } + } + + SendMessage( wnd, WM_SETREDRAW , (WPARAM)FALSE, (LPARAM)0 ); + + // Remove all + SendMessage( wnd, LB_RESETCONTENT, 0, 0 ); + + int selectslot = 0; + + int j = m_SortedNames.FirstInorder(); + while ( j != m_SortedNames.InvalidIndex() ) + { + char const *name = m_SortedNames[ j ]; + if ( name && name[ 0 ] ) + { + int temp = SendMessage( wnd, LB_ADDSTRING, 0, (LPARAM)name ); + + if ( !Q_stricmp( name, current ) ) + { + selectslot = temp; + } + } + + j = m_SortedNames.NextInorder( j ); + } + + SendMessage( wnd, LB_SETCURSEL, (WPARAM)selectslot, 0 ); + + SendMessage( wnd, WM_SETREDRAW , (WPARAM)TRUE, (LPARAM)0 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : wnd - +// *params - +// Output : static +//----------------------------------------------------------------------------- + +void CEventPropertiesSpeakDialog::ShowControlsForEventType( CEventParams *params ) +{ + BaseClass::ShowControlsForEventType( params ); +} + +void CEventPropertiesSpeakDialog::FindWaveInSoundEntries( CUtlVector< int >& entryList, char const *search ) +{ + int c = soundemitter->GetSoundCount(); + for ( int i = 0; i < c; i++ ) + { + CSoundParametersInternal *params = soundemitter->InternalGetParametersForSound( i ); + if ( !params ) + continue; + + int waveCount = params->NumSoundNames(); + for ( int wave = 0; wave < waveCount; wave++ ) + { + char const *waveName = soundemitter->GetWaveName( params->GetSoundNames()[ wave ].symbol ); + + if ( !Q_stricmp( waveName, search ) ) + { + entryList.AddToTail( i ); + break; + } + } + } +} + +void CEventPropertiesSpeakDialog::OnCheckChangedVolumeLevel( CEventParams *params ) +{ + HWND control = GetControl( IDC_EVENTCHOICES2 ); + if ( !IsWindowEnabled( control ) ) + { + return; + } + + if ( Q_stristr( params->m_szParameters, ".wav" ) ) + { + return; + } + + int soundindex = soundemitter->GetSoundIndex( params->m_szParameters ); + if ( soundindex < 0 ) + return; + + // Look up the sound level from the soundemitter system + CSoundParametersInternal *soundparams = soundemitter->InternalGetParametersForSound( soundindex ); + if ( !params ) + { + return; + } + + // See if it's writable, if not then bail + char const *scriptfile = soundemitter->GetSourceFileForSound( soundindex ); + if ( !scriptfile || + !filesystem->FileExists( scriptfile ) || + !filesystem->IsFileWritable( scriptfile ) ) + { + return; + } + + // Copy the parameters + CSoundParametersInternal newparams; + newparams.CopyFrom( *soundparams ); + + // Get the value from the control + char newvolumelevel[ 256 ]; + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( newvolumelevel ), (LPARAM)newvolumelevel ); + + newparams.VolumeFromString( newvolumelevel ); + + // No change + if ( newparams == *soundparams ) + { + return; + } + + soundemitter->UpdateSoundParameters( params->m_szParameters , newparams ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK EventPropertiesSpeakDialog( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + return g_EventPropertiesSpeakDialog.HandleMessage( hwndDlg, uMsg, wParam, lParam ); +}; + +void CEventPropertiesSpeakDialog::OnSoundSelected( CEventParams *params ) +{ + PopulateVolumeLevels( GetControl( IDC_EVENTCHOICES2 ), params ); + SendMessage( GetControl( IDC_SOUNDNAME ), WM_SETTEXT, 0, (LPARAM)params->m_szParameters ); + + // Update script name and wavename fields + HWND scriptname = GetControl( IDC_STATIC_SCRIPTFILE ); + HWND wavename = GetControl( IDC_STATIC_WAVEFILENAME ); + + SendMessage( scriptname, WM_SETTEXT, (WPARAM)1, (LPARAM)"" ); + SendMessage( wavename, WM_SETTEXT, (WPARAM)1, (LPARAM)"" ); + + int soundindex = soundemitter->GetSoundIndex( params->m_szParameters ); + if ( soundindex >= 0 ) + { + char const *script = soundemitter->GetSourceFileForSound( soundindex ); + if ( script && script [ 0 ] ) + { + SendMessage( scriptname, WM_SETTEXT, (WPARAM)Q_strlen( script ) + 1, (LPARAM)script ); + + // Look up the sound level from the soundemitter system + CSoundParametersInternal *params = soundemitter->InternalGetParametersForSound( soundindex ); + if ( params ) + { + // Get wave name + char const *w = soundemitter->GetWaveName( params->GetSoundNames()[ 0 ].symbol ); + if ( w && w[ 0 ] ) + { + SendMessage( wavename, WM_SETTEXT, (WPARAM)Q_strlen( w ) + 1, (LPARAM)w ); + } + } + } + } +} + +BOOL CEventPropertiesSpeakDialog::HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + m_hDialog = hwndDlg; + + bool handled = false; + BOOL bret = InternalHandleMessage( &g_Params, hwndDlg, uMsg, wParam, lParam, handled ); + if ( handled ) + return bret; + + switch(uMsg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc; + + hdc = BeginPaint(hwndDlg, &ps); + DrawSpline( hdc, GetControl( IDC_STATIC_SPLINE ), g_Params.m_pEvent ); + EndPaint(hwndDlg, &ps); + + return FALSE; + } + break; + case WM_VSCROLL: + { + RECT rcOut; + GetSplineRect( GetControl( IDC_STATIC_SPLINE ), rcOut ); + + InvalidateRect( hwndDlg, &rcOut, TRUE ); + UpdateWindow( hwndDlg ); + return FALSE; + } + break; + case WM_INITDIALOG: + { + InitDialog( hwndDlg ); + } + return FALSE; + case WM_TIMER: + { + g_pMatSysWindow->Frame(); + + float curtime = (float)mx::getTickCount() / 1000.0f; + if ( curtime - m_flLastFilterUpdateTime > 0.5f ) + { + m_flLastFilterUpdateTime = curtime; + OnCheckFilterUpdate(); + } + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + char sz[ 512 ]; + GetDlgItemText( m_hDialog, IDC_SOUNDNAME, sz, sizeof( sz ) ); + + Q_FixSlashes( sz ); + + // Strip off game directory stuff + Q_strncpy( g_Params.m_szParameters, sz, sizeof( g_Params.m_szParameters ) ); + char *p = Q_strstr( sz, "\\sound\\" ); + if ( p ) + { + Q_strncpy( g_Params.m_szParameters, p + strlen( "\\sound\\" ), sizeof( g_Params.m_szParameters ) ); + } + + OnCheckChangedVolumeLevel( &g_Params ); + + GetDlgItemText( m_hDialog, IDC_EVENTNAME, g_Params.m_szName, sizeof( g_Params.m_szName ) ); + + if ( !g_Params.m_szName[ 0 ] ) + { + Q_strncpy( g_Params.m_szName, sz, sizeof( g_Params.m_szName ) ); + } + + char szTime[ 32 ]; + GetDlgItemText( m_hDialog, IDC_STARTTIME, szTime, sizeof( szTime ) ); + g_Params.m_flStartTime = atof( szTime ); + GetDlgItemText( m_hDialog, IDC_ENDTIME, szTime, sizeof( szTime ) ); + g_Params.m_flEndTime = atof( szTime ); + + // Parse tokens from tags + ParseTags( &g_Params ); + + KillTimer( m_hDialog, m_Timer ); + + EndDialog( hwndDlg, 1 ); + } + break; + case IDCANCEL: + { + KillTimer( m_hDialog, m_Timer ); + EndDialog( hwndDlg, 0 ); + } + break; + case IDC_CAPTION_ATTENUATION: + { + HWND control = GetControl( IDC_CAPTION_ATTENUATION ); + g_Params.m_bCloseCaptionNoAttenuate = SendMessage( control, BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + } + break; + case IDC_PLAY_SOUND: + { + // Get sound name from soundemitter + sound->PlaySound( + NULL, + 1.0f, + va( "sound/%s", FacePoser_TranslateSoundName( g_Params.m_szParameters ) ), + NULL ); + } + break; + case IDC_OPENSOURCE: + { + // Look up the sound level from the soundemitter system + int soundindex = soundemitter->GetSoundIndex( g_Params.m_szParameters ); + if ( soundindex >= 0 ) + { + // Look up the sound level from the soundemitter system + CSoundParametersInternal *params = soundemitter->InternalGetParametersForSound( soundindex ); + if ( params ) + { + // See if the .txt file is writable + char const *scriptfile = soundemitter->GetSourceFileForSound( soundindex ); + if ( scriptfile ) + { + char relative_path[MAX_PATH]; + Q_snprintf( relative_path, MAX_PATH, "%s", scriptfile ); + + char full_path[MAX_PATH]; + if ( filesystem->GetLocalPath( relative_path, full_path, MAX_PATH ) ) + { + ShellExecute( NULL, "open", full_path, NULL, NULL, SW_SHOWNORMAL ); + } + } + } + } + } + break; + case IDC_SHOW_ALL_SOUNDS: + { + m_bShowAll = SendMessage( GetControl( IDC_SHOW_ALL_SOUNDS ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + + PopulateSoundList( g_Params.m_szParameters, GetControl( IDC_EVENTCHOICES ) ); + } + break; + case IDC_CHECK_ENDTIME: + { + g_Params.m_bHasEndTime = SendMessage( GetControl( IDC_CHECK_ENDTIME ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + if ( !g_Params.m_bHasEndTime ) + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_HIDE ); + } + else + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_RESTORE ); + } + } + break; + case IDC_CHECK_RESUMECONDITION: + { + g_Params.m_bResumeCondition = SendMessage( GetControl( IDC_CHECK_RESUMECONDITION ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + } + break; + case IDC_SOUNDLIST: + { + HWND control = (HWND)lParam; + if ( control ) + { + int cursel = SendMessage( control, LB_GETCURSEL, 0, 0 ); + if ( cursel != LB_ERR ) + { + SendMessage( control, LB_GETTEXT, cursel, (LPARAM)g_Params.m_szParameters ); + OnSoundSelected( &g_Params ); + + if ( HIWORD( wParam ) == LBN_DBLCLK ) + { + // Get sound name from soundemitter + sound->PlaySound( + NULL, + 1.0f, + va( "sound/%s", FacePoser_TranslateSoundName( g_Params.m_szParameters ) ), + NULL ); + } + } + } + } + break; + case IDC_EVENTCHOICES2: + { + HWND control = (HWND)lParam; + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters2 ), (LPARAM)g_Params.m_szParameters2 ); + } + } + break; + case IDC_ABSOLUTESTART: + { + g_Params.m_bUsesTag = false; + UpdateTagRadioButtons( &g_Params ); + } + break; + case IDC_RELATIVESTART: + { + g_Params.m_bUsesTag = true; + UpdateTagRadioButtons( &g_Params ); + } + break; + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int EventProperties_Speak( CEventParams *params ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_EVENTPROPERTIES_SPEAK ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)EventPropertiesSpeakDialog ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/eventproperties_speak.h b/utils/hlfaceposer/eventproperties_speak.h new file mode 100644 index 0000000..2bac966 --- /dev/null +++ b/utils/hlfaceposer/eventproperties_speak.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef EVENTPROPERTIES_SPEAK_H +#define EVENTPROPERTIES_SPEAK_H +#ifdef _WIN32 +#pragma once +#endif + +class CEventParams; + +int EventProperties_Speak( CEventParams *params ); + +#endif // EVENTPROPERTIES_SPEAK_H diff --git a/utils/hlfaceposer/eventproperties_subscene.cpp b/utils/hlfaceposer/eventproperties_subscene.cpp new file mode 100644 index 0000000..9acddb4 --- /dev/null +++ b/utils/hlfaceposer/eventproperties_subscene.cpp @@ -0,0 +1,234 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include <mxtk/mx.h> +#include <stdio.h> +#include "resource.h" +#include "EventProperties.h" +#include "mdlviewer.h" +#include "filesystem.h" +#include "filesystem_init.h" + +static CEventParams g_Params; + +class CEventPropertiesSubSceneDialog : public CBaseEventPropertiesDialog +{ + typedef CBaseEventPropertiesDialog BaseClass; +public: + virtual void InitDialog( HWND hwndDlg ); + virtual BOOL HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ); + virtual void SetTitle(); + virtual void ShowControlsForEventType( CEventParams *params ); + virtual void InitControlData( CEventParams *params ); +}; + +void CEventPropertiesSubSceneDialog::SetTitle() +{ + SetDialogTitle( &g_Params, "SubScene", "Sub-scene" ); +} + +void CEventPropertiesSubSceneDialog::InitControlData( CEventParams *params ) +{ + BaseClass::InitControlData( params ); + + HWND choices1 = GetControl( IDC_EVENTCHOICES ); + SendMessage( choices1, CB_RESETCONTENT, 0, 0 ); + SendMessage( choices1, WM_SETTEXT , 0, (LPARAM)params->m_szParameters ); + + SendMessage( GetControl( IDC_FILENAME ), WM_SETTEXT, sizeof( params->m_szParameters ), (LPARAM)params->m_szParameters ); +} + +void CEventPropertiesSubSceneDialog::InitDialog( HWND hwndDlg ) +{ + m_hDialog = hwndDlg; + + g_Params.PositionSelf( m_hDialog ); + + // Set working title for dialog, etc. + SetTitle(); + + // Show/Hide dialog controls + ShowControlsForEventType( &g_Params ); + InitControlData( &g_Params ); + + UpdateTagRadioButtons( &g_Params ); + + SetFocus( GetControl( IDC_EVENTNAME ) ); +} + +static CEventPropertiesSubSceneDialog g_EventPropertiesSubSceneDialog; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : wnd - +// *params - +// Output : static +//----------------------------------------------------------------------------- + +void CEventPropertiesSubSceneDialog::ShowControlsForEventType( CEventParams *params ) +{ + BaseClass::ShowControlsForEventType( params ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK EventPropertiesSubSceneDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + return g_EventPropertiesSubSceneDialog.HandleMessage( hwndDlg, uMsg, wParam, lParam ); +}; + +BOOL CEventPropertiesSubSceneDialog::HandleMessage( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + m_hDialog = hwndDlg; + + bool handled = false; + BOOL bret = InternalHandleMessage( &g_Params, hwndDlg, uMsg, wParam, lParam, handled ); + if ( handled ) + return bret; + + switch(uMsg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc; + + hdc = BeginPaint(hwndDlg, &ps); + DrawSpline( hdc, GetControl( IDC_STATIC_SPLINE ), g_Params.m_pEvent ); + EndPaint(hwndDlg, &ps); + + return FALSE; + } + break; + case WM_VSCROLL: + { + RECT rcOut; + GetSplineRect( GetControl( IDC_STATIC_SPLINE ), rcOut ); + + InvalidateRect( hwndDlg, &rcOut, TRUE ); + UpdateWindow( hwndDlg ); + return FALSE; + } + break; + case WM_INITDIALOG: + { + InitDialog( hwndDlg ); + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + char sz[ 512 ]; + GetDlgItemText( m_hDialog, IDC_FILENAME, sz, sizeof( sz ) ); + + // Strip off game directory stuff + Q_strncpy( g_Params.m_szParameters, sz, sizeof( g_Params.m_szParameters ) ); + + GetDlgItemText( m_hDialog, IDC_EVENTNAME, g_Params.m_szName, sizeof( g_Params.m_szName ) ); + + if ( !g_Params.m_szName[ 0 ] ) + { + Q_strncpy( g_Params.m_szName, g_Params.m_szParameters, sizeof( g_Params.m_szName ) ); + } + + char szTime[ 32 ]; + GetDlgItemText( m_hDialog, IDC_STARTTIME, szTime, sizeof( szTime ) ); + g_Params.m_flStartTime = atof( szTime ); + GetDlgItemText( m_hDialog, IDC_ENDTIME, szTime, sizeof( szTime ) ); + g_Params.m_flEndTime = atof( szTime ); + + // Parse tokens from tags + ParseTags( &g_Params ); + + EndDialog( hwndDlg, 1 ); + } + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + case IDC_SELECTWAV: + { + char filename[ 512 ]; + if ( FacePoser_ShowOpenFileNameDialog( filename, sizeof( filename ), "scenes", "*.vcd" ) ) + { + SetDlgItemText( m_hDialog, IDC_FILENAME, filename ); + } + } + break; + case IDC_CHECK_ENDTIME: + { + g_Params.m_bHasEndTime = SendMessage( GetControl( IDC_CHECK_ENDTIME ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + if ( !g_Params.m_bHasEndTime ) + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_HIDE ); + } + else + { + ShowWindow( GetControl( IDC_ENDTIME ), SW_RESTORE ); + } + } + break; + case IDC_CHECK_RESUMECONDITION: + { + g_Params.m_bResumeCondition = SendMessage( GetControl( IDC_CHECK_RESUMECONDITION ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + } + break; + case IDC_EVENTCHOICES: + { + HWND control = (HWND)lParam; + if ( control ) + { + SendMessage( control, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szParameters ), (LPARAM)g_Params.m_szParameters ); + } + } + break; + case IDC_ABSOLUTESTART: + { + g_Params.m_bUsesTag = false; + UpdateTagRadioButtons( &g_Params ); + } + break; + case IDC_RELATIVESTART: + { + g_Params.m_bUsesTag = true; + UpdateTagRadioButtons( &g_Params ); + } + break; + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int EventProperties_SubScene( CEventParams *params ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_EVENTPROPERTIES_SUBSCENE ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)EventPropertiesSubSceneDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/eventproperties_subscene.h b/utils/hlfaceposer/eventproperties_subscene.h new file mode 100644 index 0000000..3556095 --- /dev/null +++ b/utils/hlfaceposer/eventproperties_subscene.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef EVENTPROPERTIES_SUBSCENE_H +#define EVENTPROPERTIES_SUBSCENE_H +#ifdef _WIN32 +#pragma once +#endif + +class CEventParams; + +int EventProperties_SubScene( CEventParams *params ); + +#endif // EVENTPROPERTIES_SUBSCENE_H diff --git a/utils/hlfaceposer/expclass.cpp b/utils/hlfaceposer/expclass.cpp new file mode 100644 index 0000000..9ed9eaa --- /dev/null +++ b/utils/hlfaceposer/expclass.cpp @@ -0,0 +1,735 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "hlfaceposer.h" +#include <mxtk/mx.h> +#include "expressions.h" +#include "expclass.h" +#include "hlfaceposer.h" +#include "StudioModel.h" +#include "filesystem.h" +#include "FlexPanel.h" +#include "ControlPanel.h" +#include "mxExpressionTray.h" +#include "UtlBuffer.h" +#include "filesystem.h" +#include "ExpressionTool.h" +#include "faceposer_models.h" +#include "mdlviewer.h" +#include "phonemeconverter.h" +#include "ProgressDialog.h" +#include "tier1/fmtstr.h" +#include "tier1/utlstring.h" +#include "tier1/utlvector.h" + + +#undef ALIGN4 +#undef ALIGN16 +#define ALIGN4( a ) a = (byte *)((int)((byte *)a + 3) & ~ 3) +#define ALIGN16( a ) a = (byte *)((int)((byte *)a + 15) & ~ 15) + +char const *GetGlobalFlexControllerName( int index ); +int GetGlobalFlexControllerCount( void ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *classname - +//----------------------------------------------------------------------------- +CExpClass::CExpClass( const char *classname ) +{ + Q_strncpy( m_szClassName, classname, sizeof( m_szClassName ) ); + Q_FileBase( m_szClassName, m_szBaseName, sizeof( m_szBaseName ) ); + m_szFileName[ 0 ] = 0; + m_bDirty = false; + m_nSelectedExpression = -1; + m_bIsPhonemeClass = Q_strstr( classname, "phonemes" ) ? true : false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CExpClass::~CExpClass( void ) +{ + m_Expressions.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *exp - +//----------------------------------------------------------------------------- +int CExpClass::FindExpressionIndex( CExpression *exp ) +{ + for ( int i = 0 ; i < GetNumExpressions(); i++ ) + { + CExpression *e = GetExpression( i ); + if ( e == exp ) + return i; + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpClass::Save( void ) +{ + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if ( !hdr ) + { + return; + } + + const char *filename = GetFileName(); + if ( !filename || !filename[ 0 ] ) + return; + + Con_Printf( "Saving changes to %s to file %s\n", GetName(), GetFileName() ); + + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + + int i, j; + + int numflexmaps = 0; + int flexmap[128]; // maps file local controlls into global controls + + CExpression *expr = NULL; + // find all used controllers + int fc = GetGlobalFlexControllerCount(); + for ( j = 0; j < fc; ++j ) + { + for (i = 0; i < GetNumExpressions(); i++) + { + expr = GetExpression( i ); + Assert( expr ); + + float *settings = expr->GetSettings(); + float *weights = expr->GetWeights(); + + if ( settings[j] != 0 || + weights[j] != 0 ) + { + flexmap[ numflexmaps++ ] = j; + break; + } + } + } + + buf.Printf( "$keys" ); + for (j = 0; j < numflexmaps; j++) + { + buf.Printf( " %s", GetGlobalFlexControllerName( flexmap[j] ) ); + } + buf.Printf( "\n" ); + + buf.Printf( "$hasweighting\n" ); + + for (i = 0; i < GetNumExpressions(); i++) + { + expr = GetExpression( i ); + + buf.Printf( "\"%s\" ", expr->name ); + + // isalpha returns non zero for ents > 256 + if (expr->index <= 'z') + { + buf.Printf( "\"%c\" ", expr->index ); + } + else + { + buf.Printf( "\"0x%04x\" ", expr->index ); + } + + float *settings = expr->GetSettings(); + float *weights = expr->GetWeights(); + Assert( settings ); + Assert( weights ); + + for (j = 0; j < numflexmaps; j++) + { + buf.Printf( "%.3f %.3f ", settings[flexmap[j]], weights[flexmap[j]] ); + } + + if ( Q_strstr( expr->name, "Right Side Smile" ) ) + { + Con_Printf( "wrote %s with checksum %s\n", + expr->name, expr->GetBitmapCheckSum() ); + } + + buf.Printf( "\"%s\"\n", expr->description ); + } + + char relative[ 512 ]; + filesystem->FullPathToRelativePath( filename, relative, sizeof( relative ) ); + + MakeFileWriteable( relative ); + FileHandle_t fh = filesystem->Open( relative, "wt" ); + if ( !fh ) + { + Con_ErrorPrintf( "Unable to write to %s (read-only?)\n", relative ); + return; + } + else + { + filesystem->Write( buf.Base(), buf.TellPut(), fh ); + filesystem->Close(fh); + } + + SetDirty( false ); + + for (i = 0; i < GetNumExpressions(); i++) + { + expr = GetExpression( i ); + if ( expr ) + { + expr->ResetUndo(); + expr->SetDirty( false ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpClass::Export( void ) +{ + char vfefilename[ 512 ]; + Q_StripExtension( GetFileName(), vfefilename, sizeof( vfefilename ) ); + Q_DefaultExtension( vfefilename, ".vfe", sizeof( vfefilename ) ); + + Con_Printf( "Exporting %s to %s\n", GetName(), vfefilename ); + + int i, j; + + int numflexmaps = 0; + int flexmap[128]; // maps file local controlls into global controls + CExpression *expr = NULL; + + // find all used controllers + int fc_count = GetGlobalFlexControllerCount(); + + for (j = 0; j < fc_count; j++) + { + int k = j; + + for (i = 0; i < GetNumExpressions(); i++) + { + expr = GetExpression( i ); + Assert( expr ); + + float *settings = expr->GetSettings(); + float *weights = expr->GetWeights(); + Assert( settings ); + Assert( weights ); + + if ( settings[k] != 0 || weights[k] != 0 ) + { + flexmap[numflexmaps++] = k; + break; + } + } + } + + byte *pData = (byte *)calloc( 1024 * 1024, 1 ); + byte *pDataStart = pData; + + flexsettinghdr_t *fhdr = (flexsettinghdr_t *)pData; + + fhdr->id = ('V' << 16) + ('F' << 8) + ('E'); + fhdr->version = 0; + V_strncpy( fhdr->name, vfefilename, sizeof( fhdr->name ) ); + + // allocate room for header + pData += sizeof( flexsettinghdr_t ); + ALIGN4( pData ); + + // store flex settings + flexsetting_t *pSetting = (flexsetting_t *)pData; + fhdr->numflexsettings = GetNumExpressions(); + fhdr->flexsettingindex = pData - pDataStart; + pData += sizeof( flexsetting_t ) * fhdr->numflexsettings; + ALIGN4( pData ); + for (i = 0; i < fhdr->numflexsettings; i++) + { + expr = GetExpression( i ); + Assert( expr ); + + pSetting[i].index = expr->index; + pSetting[i].settingindex = pData - (byte *)(&pSetting[i]); + + flexweight_t *pFlexWeights = (flexweight_t *)pData; + + float *settings = expr->GetSettings(); + float *weights = expr->GetWeights(); + Assert( settings ); + Assert( weights ); + + for (j = 0; j < numflexmaps; j++) + { + if (settings[flexmap[j]] != 0 || weights[flexmap[j]] != 0) + { + pSetting[i].numsettings++; + pFlexWeights->key = j; + pFlexWeights->weight = settings[flexmap[j]]; + pFlexWeights->influence = weights[flexmap[j]]; + pFlexWeights++; + } + pData = (byte *)pFlexWeights; + ALIGN4( pData ); + } + } + + // store indexed table + int numindexes = 1; + for (i = 0; i < fhdr->numflexsettings; i++) + { + if (pSetting[i].index >= numindexes) + numindexes = pSetting[i].index + 1; + } + + int *pIndex = (int *)pData; + fhdr->numindexes = numindexes; + fhdr->indexindex = pData - pDataStart; + pData += sizeof( int ) * numindexes; + ALIGN4( pData ); + for (i = 0; i < numindexes; i++) + { + pIndex[i] = -1; + } + for (i = 0; i < fhdr->numflexsettings; i++) + { + pIndex[pSetting[i].index] = i; + } + + // store flex setting names + for (i = 0; i < fhdr->numflexsettings; i++) + { + expr = GetExpression( i ); + pSetting[i].nameindex = pData - (byte *)(&pSetting[i]); + strcpy( (char *)pData, expr->name ); + pData += strlen( expr->name ) + 1; + } + ALIGN4( pData ); + + // store key names + char **pKeynames = (char **)pData; + fhdr->numkeys = numflexmaps; + fhdr->keynameindex = pData - pDataStart; + pData += sizeof( char *) * numflexmaps; + + for (i = 0; i < numflexmaps; i++) + { + pKeynames[i] = (char *)(pData - pDataStart); + strcpy( (char *)pData, GetGlobalFlexControllerName( flexmap[i] ) ); + pData += strlen( GetGlobalFlexControllerName( flexmap[i] ) ) + 1; + } + ALIGN4( pData ); + + // allocate room for remapping + int *keymapping = (int *)pData; + fhdr->keymappingindex = pData - pDataStart; + pData += sizeof( int ) * numflexmaps; + for (i = 0; i < numflexmaps; i++) + { + keymapping[i] = -1; + } + ALIGN4( pData ); + + fhdr->length = pData - pDataStart; + + char relative[ 512 ]; + filesystem->FullPathToRelativePath( vfefilename, relative, sizeof( relative ) ); + + MakeFileWriteable( relative ); + FileHandle_t fh = filesystem->Open( relative, "wb" ); + if ( !fh ) + { + Con_ErrorPrintf( "Unable to write to %s (read-only?)\n", relative ); + return; + } + else + { + filesystem->Write( pDataStart, fhdr->length, fh ); + filesystem->Close(fh); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CExpClass::GetBaseName( void ) const +{ + return m_szBaseName; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CExpClass::GetName( void ) const +{ + return m_szClassName; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CExpClass::GetFileName( void ) const +{ + return m_szFileName; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *filename - +//----------------------------------------------------------------------------- +void CExpClass::SetFileName( const char *filename ) +{ + strcpy( m_szFileName, filename ); +} + +bool IsUsingPerPlayerExpressions(); + +void CExpClass::ReloadBitmaps( void ) +{ + bool bUsingPerPlayerOverrides = IsUsingPerPlayerExpressions(); + + int c = models->Count(); + for ( int model = 0; model < MAX_FP_MODELS; model++ ) + { + // Only reload bitmaps for current model index + if ( bUsingPerPlayerOverrides && model != models->GetActiveModelIndex() ) + continue; + + models->ForceActiveModelIndex( model ); + + for ( int i = 0 ; i < GetNumExpressions(); i++ ) + { + CExpression *e = GetExpression( i ); + if ( !e ) + continue; + + if ( e->m_Bitmap[ model ].valid ) + { + DeleteObject( e->m_Bitmap[ model ].image ); + e->m_Bitmap[ model ].valid = false; + } + + if ( model >= c ) + continue; + + if ( !LoadBitmapFromFile( e->GetBitmapFilename( model ), e->m_Bitmap[ model ] ) ) + { + e->CreateNewBitmap( model ); + } + } + } + + models->UnForceActiveModelIndex(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +// *description - +// *flexsettings - +// selectnewitem - +// Output : CExpression +//----------------------------------------------------------------------------- +CExpression *CExpClass::AddExpression( const char *name, const char *description, float *flexsettings, float *flexweights, bool selectnewitem, bool bDirtyClass ) +{ + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if ( !hdr ) + return NULL; + + CExpression *exp = FindExpression( name ); + if ( exp ) + { + Con_ErrorPrintf( "Can't create, an expression with the name '%s' already exists.\n", name ); + return NULL; + } + + // Add to end of list + int idx = m_Expressions.AddToTail(); + + exp = &m_Expressions[ idx ]; + + float *settings = exp->GetSettings(); + float *weights = exp->GetWeights(); + Assert( settings ); + Assert( weights ); + + exp->SetExpressionClass( GetName() ); + strcpy( exp->name, name ); + strcpy( exp->description, description ); + memcpy( settings, flexsettings, GLOBAL_STUDIO_FLEX_CONTROL_COUNT * sizeof( float ) ); + memcpy( weights, flexweights, GLOBAL_STUDIO_FLEX_CONTROL_COUNT * sizeof( float ) ); + exp->index = '_'; + + if ( IsPhonemeClass() ) + { + exp->index = TextToPhoneme( name ); + } + + exp->m_Bitmap[ models->GetActiveModelIndex() ].valid = false; + if ( !LoadBitmapFromFile( exp->GetBitmapFilename( models->GetActiveModelIndex() ), exp->m_Bitmap[ models->GetActiveModelIndex() ] ) ) + { + exp->CreateNewBitmap( models->GetActiveModelIndex() ); + } + + if ( selectnewitem ) + { + SelectExpression( idx ); + } + + if ( bDirtyClass ) + { + SetDirty( true ); + } + + return exp; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +// Output : CExpression +//----------------------------------------------------------------------------- +CExpression *CExpClass::FindExpression( const char *name ) +{ + for ( int i = 0 ; i < m_Expressions.Size(); i++ ) + { + CExpression *exp = &m_Expressions[ i ]; + if ( !stricmp( exp->name, name ) ) + { + return exp; + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +void CExpClass::DeleteExpression( const char *name ) +{ + + for ( int i = 0 ; i < m_Expressions.Size(); i++ ) + { + CExpression *exp = &m_Expressions[ i ]; + if ( !stricmp( exp->name, name ) ) + { + SetDirty( true ); + + m_Expressions.Remove( i ); + return; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CExpClass::GetNumExpressions( void ) +{ + return m_Expressions.Size(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : num - +// Output : CExpression +//----------------------------------------------------------------------------- +CExpression *CExpClass::GetExpression( int num ) +{ + if ( num < 0 || num >= m_Expressions.Size() ) + { + return NULL; + } + + CExpression *exp = &m_Expressions[ num ]; + return exp; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CExpClass::GetDirty( void ) +{ + return m_bDirty; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dirty - +//----------------------------------------------------------------------------- +void CExpClass::SetDirty( bool dirty ) +{ + m_bDirty = dirty; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CExpClass::GetIndex( void ) +{ + for ( int i = 0; i < expressions->GetNumClasses(); i++ ) + { + CExpClass *cl = expressions->GetClass( i ); + if ( cl == this ) + return i; + } + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : num - +//----------------------------------------------------------------------------- +void CExpClass::SelectExpression( int num, bool deselect ) +{ + m_nSelectedExpression = num; + + g_pFlexPanel->setExpression( num ); + g_pExpressionTrayTool->Select( num, deselect ); + g_pExpressionTrayTool->redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CExpClass::GetSelectedExpression( void ) +{ + return m_nSelectedExpression; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpClass::DeselectExpression( void ) +{ + m_nSelectedExpression = -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : exp1 - +// exp2 - +//----------------------------------------------------------------------------- +void CExpClass::SwapExpressionOrder( int exp1, int exp2 ) +{ + CExpression temp1 = m_Expressions[ exp1 ]; + CExpression temp2 = m_Expressions[ exp2 ]; + + m_Expressions.Remove( exp1 ); + m_Expressions.InsertBefore( exp1, temp2 ); + m_Expressions.Remove( exp2 ); + m_Expressions.InsertBefore( exp2, temp1 ); +} + +void CExpClass::BuildValidChecksums( CUtlRBTree< CRC32_t > &tree ) +{ + for ( int i = 0; i < m_Expressions.Size(); i++ ) + { + CExpression *exp = &m_Expressions[ i ]; + if ( !exp ) + continue; + + CRC32_t crc = exp->GetBitmapCRC(); + tree.Insert( crc ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: After a class is loaded, check the class directory and delete any bmp files that aren't +// still referenced +//----------------------------------------------------------------------------- +void CExpClass::CheckBitmapConsistency( void ) +{ + char path[ 512 ]; + + Q_snprintf( path, sizeof( path ), "expressions/%s/%s/*.bmp", models->GetActiveModelName(), GetBaseName() ); + Q_FixSlashes( path ); + Q_strlower( path ); + + g_pProgressDialog->Start( CFmtStr( "%s / %s - Reconcile Expression Thumbnails", models->GetActiveModelName(), GetBaseName() ), "", true ); + + CUtlVector< CUtlString > workList; + + FileFindHandle_t hFindFile; + char const *fn = filesystem->FindFirstEx( path, "MOD", &hFindFile ); + if ( fn ) + { + while ( fn ) + { + // Don't do anything with directories + if ( !filesystem->FindIsDirectory( hFindFile ) ) + { + CUtlString s = fn; + workList.AddToTail( s ); + + + } + + fn = filesystem->FindNext( hFindFile ); + } + + filesystem->FindClose( hFindFile ); + } + + CUtlRBTree< CRC32_t > tree( 0, 0, DefLessFunc( CRC32_t ) ); + BuildValidChecksums( tree ); + + for ( int i = 0 ; i < workList.Count(); ++i ) + { + char testname[ 256 ]; + Q_StripExtension( workList[ i ].String(), testname, sizeof( testname ) ); + + g_pProgressDialog->UpdateText( "%s", testname ); + g_pProgressDialog->Update( (float)i / (float)workList.Count() ); + + CRC32_t check; + Q_hextobinary( testname, Q_strlen( testname ), (byte *)&check, sizeof( check ) ); + + if ( tree.Find( check ) == tree.InvalidIndex() ) + { + char kill[ 512 ]; + Q_snprintf( kill, sizeof( kill ), "expressions/%s/%s/%s", models->GetActiveModelName(), GetBaseName(), fn ); + Q_FixSlashes( kill ); + Q_strlower( kill ); + + // Delete it + Con_ErrorPrintf( "Removing unused bitmap file '%s'\n", kill ); + + filesystem->RemoveFile( kill, "MOD" ); + } + + if ( g_pProgressDialog->IsCancelled() ) + { + Msg( "Cancelled\n" ); + break; + } + } + + g_pProgressDialog->Finish(); +} + +//----------------------------------------------------------------------------- +// Purpose: Does this class have expression indices based on phoneme lookups +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CExpClass::IsPhonemeClass( void ) const +{ + return m_bIsPhonemeClass; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/expclass.h b/utils/hlfaceposer/expclass.h new file mode 100644 index 0000000..2a1b777 --- /dev/null +++ b/utils/hlfaceposer/expclass.h @@ -0,0 +1,74 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef EXPCLASS_H +#define EXPCLASS_H +#ifdef _WIN32 +#pragma once +#endif + +#include "utlvector.h" + +class CExpression; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CExpClass +{ +public: + + CExpClass( const char *classname ); + virtual ~CExpClass( void ); + + void Save( void ); + void Export( void ); + + void CheckBitmapConsistency( void ); + void ReloadBitmaps( void ); + + const char *GetName() const; + const char *GetBaseName() const; + const char *GetFileName() const; + void SetFileName( const char *filename ); + + CExpression *AddExpression( const char *name, const char *description, float *flexsettings, float *flexweights, bool selectnewitem, bool bDirtyClass ); + CExpression *FindExpression( const char *name ); + int FindExpressionIndex( CExpression *exp ); + void DeleteExpression( const char *name ); + + int GetNumExpressions( void ); + CExpression *GetExpression( int num ); + + bool GetDirty( void ); + void SetDirty( bool dirty ); + + void SelectExpression( int num, bool deselect = true ); + int GetSelectedExpression( void ); + void DeselectExpression( void ); + + void SwapExpressionOrder( int exp1, int exp2 ); + + // Get index of this class in the global class list + int GetIndex( void ); + + bool IsPhonemeClass( void ) const; + +private: + + void BuildValidChecksums( CUtlRBTree< CRC32_t > &tree ); + + char m_szBaseName[ 128 ]; // name w/out any subdirectory names + char m_szClassName[ 128 ]; + char m_szFileName[ 128 ]; + bool m_bDirty; + int m_nSelectedExpression; + CUtlVector < CExpression > m_Expressions; + + bool m_bIsPhonemeClass; +}; +#endif // EXPCLASS_H diff --git a/utils/hlfaceposer/expression.cpp b/utils/hlfaceposer/expression.cpp new file mode 100644 index 0000000..b710c00 --- /dev/null +++ b/utils/hlfaceposer/expression.cpp @@ -0,0 +1,472 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "hlfaceposer.h" +#include <mxtk/mx.h> +#include "expressions.h" +#include "StudioModel.h" +#include "filesystem.h" +#include "viewersettings.h" +#include "matsyswin.h" +#include "checksum_crc.h" +#include "expclass.h" +#include "ControlPanel.h" +#include "faceposer_models.h" +#include "mdlviewer.h" + +static int g_counter = 0; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CExpression::CExpression( void ) +{ + name[ 0 ] = 0; + index = 0; + description[ 0 ] = 0; + memset( setting, 0, sizeof( setting ) ); + + for ( int i = 0; i < MAX_FP_MODELS; i++ ) + { + m_Bitmap[ i ].valid = false; + } + + m_nUndoCurrent = 0; + m_bModified = false; + + m_bSelected = false; + + m_bDirty = false; + + expressionclass[ 0 ] = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Copy constructor +// Input : from - +//----------------------------------------------------------------------------- +CExpression::CExpression( const CExpression& from ) +{ + int i; + + strcpy( name, from.name ); + index = from.index; + strcpy( description, from.description ); + + for ( i = 0; i < MAX_FP_MODELS; i++ ) + { + m_Bitmap[ i ] = from.m_Bitmap[ i ]; + } + + m_bModified = from.m_bModified; + + for ( i = 0 ; i < from.undo.Size(); i++ ) + { + CExpUndoInfo *newUndo = new CExpUndoInfo(); + *newUndo = *from.undo[ i ]; + undo.AddToTail( newUndo ); + } + + m_nUndoCurrent = from.m_nUndoCurrent; + + m_bSelected = from.m_bSelected; + + m_bDirty = from.m_bDirty; + + strcpy( expressionclass, from.expressionclass ); + + memcpy( setting, from.setting, GLOBAL_STUDIO_FLEX_CONTROL_COUNT * sizeof( float ) ); + memcpy( weight, from.weight, GLOBAL_STUDIO_FLEX_CONTROL_COUNT * sizeof( float ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CExpression::~CExpression( void ) +{ + ResetUndo(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CExpression::GetDirty( void ) +{ + return m_bDirty; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dirty - +//----------------------------------------------------------------------------- +void CExpression::SetDirty( bool dirty ) +{ + m_bDirty = dirty; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float *CExpression::GetSettings( void ) +{ + return setting; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float *CExpression::GetWeights( void ) +{ + return weight; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mod - +//----------------------------------------------------------------------------- +void CExpression::SetModified( bool mod ) +{ + m_bModified = mod; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CExpression::GetModified( void ) +{ + return m_bModified; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : selected - +//----------------------------------------------------------------------------- +void CExpression::SetSelected( bool selected ) +{ + m_bSelected = selected; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CExpression::GetSelected( void ) +{ + return m_bSelected; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpression::ResetUndo( void ) +{ + CExpUndoInfo *u; + for ( int i = 0; i < undo.Size(); i++ ) + { + u = undo[ i ]; + delete u; + } + + undo.RemoveAll(); + m_nUndoCurrent = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CExpression::CanRedo( void ) +{ + if ( !undo.Size() ) + return false; + + if ( m_nUndoCurrent == 0 ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CExpression::CanUndo( void ) +{ + if ( !undo.Size() ) + return false; + + if ( m_nUndoCurrent >= undo.Size() ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CExpression::UndoLevels( void ) +{ + return undo.Size(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CExpression::UndoCurrent( void ) +{ + return m_nUndoCurrent; +} + +void ChecksumFlexControllers( bool bSpew, char const *name, CRC32_t &crc, const float *settings, const float *weights ); + +CRC32_t CExpression::GetBitmapCRC() +{ + CRC32_t crc; + + float *s = setting; + float *w = weight; + + // Note, we'll use the pristine values if this has changed + if ( undo.Size() >= 1 ) + { + s = undo[ undo.Size() - 1 ]->setting; + w = undo[ undo.Size() - 1 ]->weight; + } + + // This walks the global controllers sorted by name and only includes values with a setting or value which is != 0.0f + + ChecksumFlexControllers( false, name, crc, s, w ); + + return crc; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CExpression::GetBitmapCheckSum() +{ + CRC32_t crc = GetBitmapCRC(); + + // Create string name out of binary data + static char hex[ 9 ]; + Q_binarytohex( (byte *)&crc, sizeof( crc ), hex, sizeof( hex ) ); + return hex; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CExpression::GetBitmapFilename( int modelindex ) +{ + static char filename[ 256 ] = { 0 }; + + char const *classname = "error"; + CExpClass *cl = GetExpressionClass(); + if ( cl ) + { + classname = cl->GetBaseName(); + } + + char modelName[512], modelNameTemp[512]; + Q_strncpy( modelNameTemp, models->GetModelName( modelindex ), sizeof( modelNameTemp ) ); + + char const *in = modelNameTemp; + char *out = modelName; + + while ( *in ) + { + if ( V_isalnum( *in ) || + *in == '_' || + *in == '\\' || + *in == '/' || + *in == '.' || + *in == ':' ) + { + *out++ = *in; + } + in++; + } + *out = 0; + + + sprintf( filename, "expressions/%s/%s/%s.bmp", modelName, classname, GetBitmapCheckSum() ); + + Q_FixSlashes( filename ); + strlwr( filename ); + + CreatePath( filename ); + + return filename; +} + +void CExpression::CreateNewBitmap( int modelindex ) +{ + MatSysWindow *pWnd = g_pMatSysWindow; + if ( !pWnd ) + return; + + StudioModel *model = models->GetStudioModel( modelindex ); + if ( !model ) + return; + + CStudioHdr *hdr = models->GetStudioHeader( modelindex ); + if ( !hdr ) + return; + + char filename[ 256 ]; + V_strcpy_safe( filename, GetBitmapFilename( modelindex ) ); + if ( !Q_strstr( filename, ".bmp" ) ) + return; + + models->CreateNewBitmap( modelindex, filename, 0, 128, true, this, &m_Bitmap[ modelindex ] ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *exp - +//----------------------------------------------------------------------------- +void CExpression::PushUndoInformation( void ) +{ + SetModified( true ); + + // A real change to the data wipes out the redo counters + WipeRedoInformation(); + + CExpUndoInfo *newundo = new CExpUndoInfo; + + memcpy( newundo->setting, setting, GLOBAL_STUDIO_FLEX_CONTROL_COUNT * sizeof( float ) ); + memset( newundo->redosetting, 0, GLOBAL_STUDIO_FLEX_CONTROL_COUNT * sizeof( float ) ); + memcpy( newundo->weight, weight, GLOBAL_STUDIO_FLEX_CONTROL_COUNT * sizeof( float ) ); + memset( newundo->redoweight, 0, GLOBAL_STUDIO_FLEX_CONTROL_COUNT * sizeof( float ) ); + + newundo->counter = g_counter++; + + undo.AddToHead( newundo ); + + Assert( m_nUndoCurrent == 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *exp - +//----------------------------------------------------------------------------- +void CExpression::PushRedoInformation( void ) +{ + Assert( undo.Size() >= 1 ); + + CExpUndoInfo *redo = undo[ 0 ]; + memcpy( redo->redosetting, setting, GLOBAL_STUDIO_FLEX_CONTROL_COUNT * sizeof( float ) ); + memcpy( redo->redoweight, weight, GLOBAL_STUDIO_FLEX_CONTROL_COUNT * sizeof( float ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *exp - +//----------------------------------------------------------------------------- +void CExpression::Undo( void ) +{ + if ( !CanUndo() ) + return; + + Assert( m_nUndoCurrent < undo.Size() ); + + CExpUndoInfo *u = undo[ m_nUndoCurrent++ ]; + Assert( u ); + + memcpy( setting, u->setting, GLOBAL_STUDIO_FLEX_CONTROL_COUNT * sizeof( float ) ); + memcpy( weight, u->weight, GLOBAL_STUDIO_FLEX_CONTROL_COUNT * sizeof( float ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *exp - +//----------------------------------------------------------------------------- +void CExpression::Redo( void ) +{ + if ( !CanRedo() ) + return; + + Assert( m_nUndoCurrent >= 1 ); + Assert( m_nUndoCurrent <= undo.Size() ); + + CExpUndoInfo *u = undo[ --m_nUndoCurrent ]; + Assert( u ); + + memcpy( setting, u->redosetting, GLOBAL_STUDIO_FLEX_CONTROL_COUNT * sizeof( float ) ); + memcpy( weight, u->redoweight, GLOBAL_STUDIO_FLEX_CONTROL_COUNT * sizeof( float ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *exp - +//----------------------------------------------------------------------------- +void CExpression::WipeRedoInformation( void ) +{ + // Wipe out all stuff newer then m_nUndoCurrent + int level = 0; + while ( level < m_nUndoCurrent ) + { + CExpUndoInfo *u = undo[ 0 ]; + undo.Remove( 0 ); + Assert( u ); + delete u; + level++; + } + + m_nUndoCurrent = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Revert to last saved state +//----------------------------------------------------------------------------- +void CExpression::Revert( void ) +{ + SetDirty( false ); + + if ( undo.Size() <= 0 ) + return; + + // Go back to original data + CExpUndoInfo *u = undo[ undo.Size() - 1 ]; + Assert( u ); + + memcpy( setting, u->setting, GLOBAL_STUDIO_FLEX_CONTROL_COUNT * sizeof( float ) ); + memcpy( weight, u->weight, GLOBAL_STUDIO_FLEX_CONTROL_COUNT * sizeof( float ) ); + + ResetUndo(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CExpClass +//----------------------------------------------------------------------------- +CExpClass *CExpression::GetExpressionClass( void ) +{ + CExpClass *cl = expressions->FindClass( expressionclass, false ); + if ( !cl ) + { + Assert( cl ); + } + return cl; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *classname - +//----------------------------------------------------------------------------- +void CExpression::SetExpressionClass( char const *classname ) +{ + strcpy( expressionclass, classname ); +} + diff --git a/utils/hlfaceposer/expression.h b/utils/hlfaceposer/expression.h new file mode 100644 index 0000000..1a6d134 --- /dev/null +++ b/utils/hlfaceposer/expression.h @@ -0,0 +1,114 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef EXPRESSION_H +#define EXPRESSION_H +#ifdef _WIN32 +#pragma once +#endif + +#include "utlvector.h" +#include "mxBitmapTools.h" +#include "hlfaceposer.h" + +#define GLOBAL_STUDIO_FLEX_CONTROL_COUNT ( MAXSTUDIOFLEXCTRL * 4 ) + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CExpUndoInfo +{ +public: + float setting[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ]; + float weight[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ]; + + float redosetting[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ]; + float redoweight[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ]; + + int counter; +}; + +class CExpression; +class CExpClass; + +typedef unsigned int CRC32_t; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CExpression +{ +public: + CExpression ( void ); + ~CExpression ( void ); + + CExpression( const CExpression& from ); + + void SetModified( bool mod ); + bool GetModified( void ); + + void ResetUndo( void ); + + bool CanUndo( void ); + bool CanRedo( void ); + + int UndoLevels( void ); + int UndoCurrent( void ); + + const char *GetBitmapFilename( int modelindex ); + const char *GetBitmapCheckSum(); + CRC32_t GetBitmapCRC(); + void CreateNewBitmap( int modelindex ); + + void PushUndoInformation( void ); + void PushRedoInformation( void ); + + void Undo( void ); + void Redo( void ); + + void SetSelected( bool selected ); + bool GetSelected( void ); + + float *GetSettings( void ); + float *GetWeights( void ); + + bool GetDirty( void ); + void SetDirty( bool dirty ); + + void Revert( void ); + + CExpClass *GetExpressionClass( void ); + void SetExpressionClass( char const *classname ); + + // name of expression + char name[32]; + int index; + char description[128]; + + mxbitmapdata_t m_Bitmap[ MAX_FP_MODELS ]; + + bool m_bModified; + + // Undo information + CUtlVector< CExpUndoInfo * > undo; + int m_nUndoCurrent; + + bool m_bSelected; + + bool m_bDirty; + +private: + // settings of fields + float setting[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ]; + float weight[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ]; + + char expressionclass[ 128 ]; + + void WipeRedoInformation( void ); +}; + +#endif // EXPRESSION_H diff --git a/utils/hlfaceposer/expressionproperties.cpp b/utils/hlfaceposer/expressionproperties.cpp new file mode 100644 index 0000000..f762d4e --- /dev/null +++ b/utils/hlfaceposer/expressionproperties.cpp @@ -0,0 +1,78 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include <mxtk/mx.h> +#include "resource.h" +#include "ExpressionProperties.h" +#include "mdlviewer.h" + +static CExpressionParams g_Params; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK ExpressionPropertiesDialogProc +//----------------------------------------------------------------------------- +static BOOL CALLBACK ExpressionPropertiesDialogProc ( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + switch(uMsg) + { + case WM_INITDIALOG: + { + // Insert code here to put the string (to find and replace with) + // into the edit controls. + // ... + g_Params.PositionSelf( hwndDlg ); + + SetDlgItemText( hwndDlg, IDC_EXPRESSIONNAME, g_Params.m_szName ); + SetDlgItemText( hwndDlg, IDC_EXPRESSIONDESC, g_Params.m_szDescription ); + + SetWindowText( hwndDlg, g_Params.m_szDialogTitle ); + + SetFocus( GetDlgItem( hwndDlg, IDC_EXPRESSIONNAME ) ); + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + g_Params.m_szName[ 0 ] = 0; + GetDlgItemText( hwndDlg, IDC_EXPRESSIONNAME, g_Params.m_szName, 256 ); + GetDlgItemText( hwndDlg, IDC_EXPRESSIONDESC, g_Params.m_szDescription, 256 ); + EndDialog( hwndDlg, 1 ); + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int ExpressionProperties( CExpressionParams *params ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_EXPRESSIONPROPERTIES ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)ExpressionPropertiesDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/expressionproperties.h b/utils/hlfaceposer/expressionproperties.h new file mode 100644 index 0000000..1142797 --- /dev/null +++ b/utils/hlfaceposer/expressionproperties.h @@ -0,0 +1,27 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef EXPRESSIONPROPERTIES_H +#define EXPRESSIONPROPERTIES_H +#ifdef _WIN32 +#pragma once +#endif + +#include "basedialogparams.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +struct CExpressionParams : public CBaseDialogParams +{ + char m_szName[ 256 ]; + char m_szDescription[ 256 ]; +}; + +int ExpressionProperties( CExpressionParams *params ); + +#endif // EXPRESSIONPROPERTIES_H diff --git a/utils/hlfaceposer/expressions.cpp b/utils/hlfaceposer/expressions.cpp new file mode 100644 index 0000000..a199f20 --- /dev/null +++ b/utils/hlfaceposer/expressions.cpp @@ -0,0 +1,692 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "hlfaceposer.h" +#include "expressions.h" +#include <mxtk/mx.h> +#include "ControlPanel.h" +#include "StudioModel.h" +#include "expclass.h" +#include "mxExpressionTab.h" +#include "mxExpressionTray.h" +#include "filesystem.h" +#include "faceposer_models.h" +#include "utldict.h" +#include "scriplib.h" +#include "checksum_crc.h" + +bool Sys_Error(const char *pMsg, ...); +extern char g_appTitle[]; + +static CUtlVector< CUtlSymbol > g_GlobalFlexControllers; +static CUtlDict< int, int > g_GlobalFlexControllerLookup; + +void ChecksumFlexControllers( bool bSpew, char const *name, CRC32_t &crc, const float *settings, const float *weights ) +{ + CRC32_Init( &crc ); + + // Walk them alphabetically so that load order doesn't matter + for ( int i = g_GlobalFlexControllerLookup.First() ; + i != g_GlobalFlexControllerLookup.InvalidIndex(); + i = g_GlobalFlexControllerLookup.Next( i ) ) + { + int controllerIndex = g_GlobalFlexControllerLookup[ i ]; + char const *pszName = g_GlobalFlexControllerLookup.GetElementName( i ); + + // Only count active controllers in checksum + float s = settings[ controllerIndex ]; + float w = weights[ controllerIndex ]; + + if ( s == 0.0f && w == 0.0f ) + { + continue; + } + + CRC32_ProcessBuffer( &crc, (void *)pszName, Q_strlen( pszName ) ); + CRC32_ProcessBuffer( &crc, (void *)&s, sizeof( s ) ); + CRC32_ProcessBuffer( &crc, (void *)&w, sizeof( w ) ); + + if ( bSpew ) + { + Msg( "[%d] %s == %f %f\n", controllerIndex, pszName, s, w ); + } + } + + CRC32_Final( &crc ); + + if ( bSpew ) + { + char hex[ 17 ]; + Q_binarytohex( (const byte *)&crc, sizeof( crc ), hex, sizeof( hex ) ); + Msg( "%s checksum = %sf\n", name, hex ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : index - +// Output : char const +//----------------------------------------------------------------------------- +char const *GetGlobalFlexControllerName( int index ) +{ + return g_GlobalFlexControllers[ index ].String(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int GetGlobalFlexControllerCount( void ) +{ + return g_GlobalFlexControllers.Count(); +} +//----------------------------------------------------------------------------- +// Purpose: Accumulates throughout runtime session, oh well +// Input : *szName - +// Output : int +//----------------------------------------------------------------------------- +int AddGlobalFlexController( StudioModel *model, const char *szName ) +{ + int idx = g_GlobalFlexControllerLookup.Find( szName ); + if ( idx != g_GlobalFlexControllerLookup.InvalidIndex() ) + { + return g_GlobalFlexControllerLookup[ idx ]; + } + + CUtlSymbol sym; + sym = szName; + idx = g_GlobalFlexControllers.AddToTail( sym ); + g_GlobalFlexControllerLookup.Insert( szName, idx ); + // Con_Printf( "Added global flex controller %i %s from %s\n", idx, szName, model->GetStudioHdr()->name ); + return idx; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *model - +//----------------------------------------------------------------------------- +void SetupModelFlexcontrollerLinks( StudioModel *model ) +{ + if ( !model ) + return; + + CStudioHdr *hdr = model->GetStudioHdr(); + if ( !hdr ) + return; + + if ( hdr->numflexcontrollers() <= 0 ) + return; + + // Already set up!!! + if ( hdr->pFlexcontroller( LocalFlexController_t(0) )->localToGlobal != -1 ) + return; + + for (LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++) + { + int j = AddGlobalFlexController( model, hdr->pFlexcontroller( i )->pszName() ); + hdr->pFlexcontroller( i )->localToGlobal = j; + model->SetFlexController( i, 0.0f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CExpressionManager : public IExpressionManager +{ +public: + CExpressionManager( void ); + ~CExpressionManager( void ); + + void Reset( void ); + + void ActivateExpressionClass( CExpClass *cl ); + + // File I/O + void LoadClass( const char *filename ); + void CreateNewClass( const char *filename ); + bool CloseClass( CExpClass *cl ); + + CExpClass *AddCExpClass( const char *classname, const char *filename ); + int GetNumClasses( void ); + + CExpression *GetCopyBuffer( void ); + + bool CanClose( void ); + + CExpClass *GetActiveClass( void ); + CExpClass *GetClass( int num ); + CExpClass *FindClass( const char *classname, bool bMatchBaseNameOnly ); + +private: + // Methods + const char *GetClassnameFromFilename( const char *filename ); + +// UI + void PopulateClassCB( CExpClass *cl ); + + void RemoveCExpClass( CExpClass *cl ); + +private: + // Data + CExpClass *m_pActiveClass; + CUtlVector < CExpClass * > m_Classes; + + CExpression m_CopyBuffer; +}; + +// Expose interface +static CExpressionManager g_ExpressionManager; +IExpressionManager *expressions = &g_ExpressionManager; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CExpressionManager::CExpressionManager( void ) +{ + m_pActiveClass = NULL; + Reset(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CExpressionManager::~CExpressionManager( void ) +{ + Reset(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpressionManager::Reset( void ) +{ + while ( m_Classes.Size() > 0 ) + { + CExpClass *p = m_Classes[ 0 ]; + m_Classes.Remove( 0 ); + delete p; + } + + m_pActiveClass = NULL; + + memset( &m_CopyBuffer, 0, sizeof( m_CopyBuffer ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CExpClass *CExpressionManager::GetActiveClass( void ) +{ + return m_pActiveClass; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : num - +// Output : CExpClass +//----------------------------------------------------------------------------- +CExpClass *CExpressionManager::GetClass( int num ) +{ + return m_Classes[ num ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *classname - +// *filename - +// Output : CExpClass * +//----------------------------------------------------------------------------- +CExpClass * CExpressionManager::AddCExpClass( const char *classname, const char *filename ) +{ + Assert( !FindClass( classname, false ) ); + + CExpClass *pclass = new CExpClass( classname ); + if ( !pclass ) + return NULL; + + m_Classes.AddToTail( pclass ); + + pclass->SetFileName( filename ); + + return pclass; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *cl - +//----------------------------------------------------------------------------- +void CExpressionManager::RemoveCExpClass( CExpClass *cl ) +{ + for ( int i = 0; i < m_Classes.Size(); i++ ) + { + CExpClass *p = m_Classes[ i ]; + if ( p == cl ) + { + m_Classes.Remove( i ); + delete p; + break; + } + } + + if ( m_Classes.Size() >= 1 ) + { + ActivateExpressionClass( m_Classes[ 0 ] ); + } + else + { + ActivateExpressionClass( NULL ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *cl - +//----------------------------------------------------------------------------- +void CExpressionManager::ActivateExpressionClass( CExpClass *cl ) +{ + m_pActiveClass = cl; + int select = 0; + for ( int i = 0; i < GetNumClasses(); i++ ) + { + CExpClass *c = GetClass( i ); + if ( cl == c ) + { + select = i; + break; + } + } + + g_pExpressionClass->select( select ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CExpressionManager::GetNumClasses( void ) +{ + return m_Classes.Size(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *classname - +// Output : CExpClass +//----------------------------------------------------------------------------- +CExpClass *CExpressionManager::FindClass( const char *classname, bool bMatchBaseNameOnly ) +{ + char search[ 256 ]; + if ( bMatchBaseNameOnly ) + { + Q_FileBase( classname, search, sizeof( search ) ); + } + else + { + Q_strncpy( search, classname, sizeof( search ) ); + } + + Q_FixSlashes( search ); + Q_strlower( search ); + + for ( int i = 0; i < m_Classes.Size(); i++ ) + { + CExpClass *cl = m_Classes[ i ]; + + if ( !Q_stricmp( search, bMatchBaseNameOnly ? cl->GetBaseName() : cl->GetName() ) ) + { + return cl; + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *filename - +// Output : const char +//----------------------------------------------------------------------------- +const char *CExpressionManager::GetClassnameFromFilename( const char *filename ) +{ + char cleanname[ 256 ]; + static char classname[ 256 ]; + classname[ 0 ] = 0; + + Assert( filename && filename[ 0 ] ); + + // Strip the .txt + Q_StripExtension( filename, cleanname, sizeof( cleanname ) ); + + char *p = Q_stristr( cleanname, "expressions" ); + if ( p ) + { + Q_strncpy( classname, p + Q_strlen( "expressions" ) + 1, sizeof( classname ) ); + } + else + { + Assert( 0 ); + Q_strncpy( classname, cleanname, sizeof( classname ) ); + } + + Q_FixSlashes( classname ); + Q_strlower( classname ); + return classname; +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CExpression +//----------------------------------------------------------------------------- +CExpression *CExpressionManager::GetCopyBuffer( void ) +{ + return &m_CopyBuffer; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CExpressionManager::CanClose( void ) +{ + for ( int i = 0; i < m_Classes.Size(); i++ ) + { + CExpClass *pclass = m_Classes[ i ]; + if ( pclass->GetDirty() ) + { + return false; + } + } + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *filename - +//----------------------------------------------------------------------------- +void CExpressionManager::LoadClass( const char *inpath ) +{ + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + if ( inpath[ 0 ] == '/' || inpath[ 0 ] == '\\' ) + ++inpath; + + char filename[ 512 ]; + Q_strncpy( filename, inpath, sizeof( filename ) ); + + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if ( !hdr ) + { + Con_ErrorPrintf( "Can't load expressions from %s, must load a .mdl file first!\n", + filename ); + return; + } + + Con_Printf( "Loading expressions from %s\n", filename ); + + const char *classname = GetClassnameFromFilename( filename ); + + // Already loaded, don't do anything + if ( FindClass( classname, false ) ) + return; + + // Import actual data + LoadScriptFile( filename, SCRIPT_USE_RELATIVE_PATH ); + + CExpClass *active = AddCExpClass( classname, filename ); + if ( !active ) + return; + + ActivateExpressionClass( active ); + + int numflexmaps = 0; + int flexmap[128]; // maps file local controls into global controls + LocalFlexController_t localflexmap[128]; // maps file local controls into local controls + bool bHasWeighting = false; + bool bNormalized = false; + + EnableStickySnapshotMode( ); + + while (1) + { + GetToken (true); + if (endofscript) + break; + if (stricmp( token, "$keys" ) == 0) + { + numflexmaps = 0; + while (TokenAvailable()) + { + flexmap[numflexmaps] = -1; + localflexmap[numflexmaps] = LocalFlexController_t(-1); + + GetToken( false ); + bool bFound = false; + for (LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++) + { + if (stricmp( hdr->pFlexcontroller(i)->pszName(), token ) == 0) + { + localflexmap[numflexmaps] = i; + flexmap[numflexmaps] = AddGlobalFlexController( models->GetActiveStudioModel(), + hdr->pFlexcontroller(i)->pszName() ); + bFound = true; + break; + } + } + if ( !bFound ) + { + flexmap[ numflexmaps ] = AddGlobalFlexController( models->GetActiveStudioModel(), token ); + } + numflexmaps++; + } + } + else if ( !stricmp( token, "$hasweighting" ) ) + { + bHasWeighting = true; + } + else if ( !stricmp( token, "$normalized" ) ) + { + bNormalized = true; + } + else + { + float setting[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ]; + float weight[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ]; + char name[ 256 ]; + char desc[ 256 ]; + int index; + + memset( setting, 0, sizeof( setting ) ); + memset( weight, 0, sizeof( weight ) ); + + strcpy( name, token ); + + // phoneme index + GetToken( false ); + if (token[1] == 'x') + { + sscanf( &token[2], "%x", &index ); + } + else + { + index = (int)token[0]; + } + + // key values + for (int i = 0; i < numflexmaps; i++) + { + if (flexmap[i] > -1) + { + GetToken( false ); + setting[flexmap[i]] = atof( token ); + if (bHasWeighting) + { + GetToken( false ); + weight[flexmap[i]] = atof( token ); + } + else + { + weight[flexmap[i]] = 1.0; + } + + if ( bNormalized && localflexmap[ i ] > -1 ) + { + mstudioflexcontroller_t *pFlex = hdr->pFlexcontroller( localflexmap[i] ); + if ( pFlex->min != pFlex->max ) + { + setting[flexmap[i]] = Lerp( setting[flexmap[i]], pFlex->min, pFlex->max ); + } + } + } + else + { + GetToken( false ); + if (bHasWeighting) + { + GetToken( false ); + } + } + } + + // description + GetToken( false ); + strcpy( desc, token ); + + CExpression *exp = active->AddExpression( name, desc, setting, weight, false, false ); + if ( active->IsPhonemeClass() && exp ) + { + if ( exp->index != index ) + { + Con_Printf( "CExpressionManager::LoadClass (%s): phoneme index for %s in .txt file is wrong (expecting %i got %i), ignoring...\n", + classname, name, exp->index, index ); + } + } + } + } + + active->CheckBitmapConsistency(); + + DisableStickySnapshotMode( ); + + PopulateClassCB( active ); + + active->DeselectExpression(); + + Assert( !active->GetDirty() ); + active->SetDirty( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *filename - +//----------------------------------------------------------------------------- +void CExpressionManager::CreateNewClass( const char *filename ) +{ + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if ( !hdr ) + { + Con_ErrorPrintf( "Can't create new expression file %s, must load a .mdl file first!\n", filename ); + return; + } + + // Tell the use that the filename was loaded, expressions are empty for now + const char *classname = GetClassnameFromFilename( filename ); + + // Already loaded, don't do anything + if ( FindClass( classname, false ) ) + return; + + Con_Printf( "Creating %s\n", filename ); + + CExpClass *active = AddCExpClass( classname, filename ); + if ( !active ) + return; + + ActivateExpressionClass( active ); + + // Select the newly created class + PopulateClassCB( active ); + + // Select first expression + active->SelectExpression( 0 ); + + // Nothing has changed so far + active->SetDirty( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *cl - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CExpressionManager::CloseClass( CExpClass *cl ) +{ + if ( !cl ) + return true; + + if ( cl->GetDirty() ) + { + int retval = mxMessageBox( NULL, va( "Save changes to class '%s'?", cl->GetName() ), g_appTitle, MX_MB_YESNOCANCEL ); + if ( retval == 2 ) + { + return false; + } + if ( retval == 0 ) + { + Con_Printf( "Saving changes to %s : %s\n", cl->GetName(), cl->GetFileName() ); + cl->Save(); + } + } + + // The memory can be freed here, so be more careful + char temp[ 256 ]; + V_strcpy_safe( temp, cl->GetName() ); + + RemoveCExpClass( cl ); + + Con_Printf( "Closed expression class %s\n", temp ); + + CExpClass *active = GetActiveClass(); + if ( !active ) + { + PopulateClassCB( NULL ); + g_pExpressionTrayTool->redraw(); + return true; + } + + // Select the first remaining class + PopulateClassCB( active ); + + // Select first expression + active->DeselectExpression(); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : classnum - +//----------------------------------------------------------------------------- +void CExpressionManager::PopulateClassCB( CExpClass *current ) +{ + g_pExpressionClass->removeAll(); + int select = 0; + for ( int i = 0; i < GetNumClasses(); i++ ) + { + CExpClass *cl = GetClass( i ); + if ( !cl ) + continue; + + g_pExpressionClass->add( cl->GetName() ); + + if ( cl == current ) + { + select = i; + } + } + + g_pExpressionClass->select( select ); +}
\ No newline at end of file diff --git a/utils/hlfaceposer/expressions.h b/utils/hlfaceposer/expressions.h new file mode 100644 index 0000000..ecd184c --- /dev/null +++ b/utils/hlfaceposer/expressions.h @@ -0,0 +1,51 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#if !defined( EXPRESSIONS_H ) +#define EXPRESSIONS_H +#ifdef _WIN32 +#pragma once +#endif + +#include "studio.h" +#include "expression.h" + +class FlexPanel; +class ControlPanel; +class MatSysWindow; +class CExpClass; +class ExpressionTool; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class IExpressionManager +{ +public: + virtual void Reset( void ) = 0; + + // File i/o + virtual void LoadClass( const char *filename ) = 0; + virtual void CreateNewClass( const char *filename ) = 0; + virtual bool CloseClass( CExpClass *cl ) = 0; + virtual void ActivateExpressionClass( CExpClass *cl ) = 0; + + virtual CExpClass *AddCExpClass( const char *classname, const char *filename ) = 0; + virtual int GetNumClasses( void ) = 0; + + virtual CExpression *GetCopyBuffer( void ) = 0; + + virtual bool CanClose( void ) = 0; + + virtual CExpClass *GetActiveClass( void ) = 0; + virtual CExpClass *GetClass( int num ) = 0; + virtual CExpClass *FindClass( const char *classname, bool bMatchBaseNameOnly ) = 0; + +}; + +extern IExpressionManager *expressions; + +#endif // EXPRESSIONS_H
\ No newline at end of file diff --git a/utils/hlfaceposer/expressiontool.cpp b/utils/hlfaceposer/expressiontool.cpp new file mode 100644 index 0000000..b303e7c --- /dev/null +++ b/utils/hlfaceposer/expressiontool.cpp @@ -0,0 +1,4816 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include <stdio.h> +#include "hlfaceposer.h" +#include "ExpressionTool.h" +#include "mdlviewer.h" +#include "choreowidgetdrawhelper.h" +#include "TimelineItem.h" +#include "expressions.h" +#include "expclass.h" +#include "choreoevent.h" +#include "StudioModel.h" +#include "choreoscene.h" +#include "choreoactor.h" +#include "choreochannel.h" +#include "ChoreoView.h" +#include "InputProperties.h" +#include "ControlPanel.h" +#include "FlexPanel.h" +#include "mxExpressionTray.h" +#include "ExpressionProperties.h" +#include "tier1/strtools.h" +#include "faceposer_models.h" +#include "UtlBuffer.h" +#include "filesystem.h" +#include "iscenetokenprocessor.h" +#include "MatSysWin.h" +#include "choreoviewcolors.h" +#include "scriplib.h" +#include "EdgeProperties.h" + +ExpressionTool *g_pExpressionTool = 0; + +#define TRAY_HEIGHT 55 + +#define TRAY_ITEM_INSET 10 + +#define MAX_TIME_ZOOM 1000 +// 10% per step +#define TIME_ZOOM_STEP 2 + +void SetupFlexControllerTracks( CStudioHdr *hdr, CChoreoEvent *event ); + +class CExpressionToolWorkspace : public mxWindow +{ +public: + CExpressionToolWorkspace( mxWindow *parent ); + ~CExpressionToolWorkspace(); + + virtual int handleEvent( mxEvent *event ); + virtual void redraw( void ); + virtual bool PaintBackground( void ) + { + redraw(); + return false; + } + + void RepositionVSlider( void ); + int ComputeVPixelsNeeded( void ); + // Playback tick + void Think( float dt ); + + void LayoutItems( bool force = false ); + + void HideTimelines( void ); + void CollapseAll( TimelineItem *keepExpanded ); + + void ExpandAll( void ); + void ExpandValid( void ); + void DisableAllExcept( void ); + void EnableValid( void ); + + TimelineItem *GetItem( int number ); + TimelineItem *GetClickedItem( void ); + void ClearClickedItem( void ); + + void OnSnapAll(); + void OnDeleteColumn(); + + void MoveSelectedSamples( float dfdx, float dfdy, bool snap ); + void DeleteSelectedSamples( void ); + int CountSelectedSamples( void ); + void DeselectAll( void ); + void SelectPoints( float start, float end ); + + void DrawEventEnd( CChoreoWidgetDrawHelper& drawHelper ); + + void OnSortByUsed( void ); + void OnSortByName( void ); + +private: + + int GetItemUnderMouse( int mx, int my ); + + void MouseToToolMouse( int& mx, int& my, char *reason ); + + TimelineItem *m_pItems[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ]; + + // The scroll bars + mxScrollbar *m_pVertScrollBar; + int m_nLastVPixelsNeeded; + + int m_nTopOffset; + int m_nScrollbarHeight; + + int m_nItemGap; + int m_nFocusItem; +}; + +CExpressionToolWorkspace::CExpressionToolWorkspace( mxWindow *parent ) : + mxWindow( parent, 0, 0, 0, 0 ) +{ + HWND wnd = (HWND)getHandle(); + DWORD style = GetWindowLong( wnd, GWL_STYLE ); + style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS; + SetWindowLong( wnd, GWL_STYLE, style ); + + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + m_pItems[ i ] = new TimelineItem( this ); + } + + m_nItemGap = 2; + + m_nScrollbarHeight = 12; + m_nTopOffset = 0; + + m_nLastVPixelsNeeded = -1; + + m_pVertScrollBar = new mxScrollbar( this, 0, 0, 12, 100, IDC_EXPRESSIONTOOLVSCROLL, mxScrollbar::Vertical ); + + m_nFocusItem = -1; + + HideTimelines(); + LayoutItems(); +} + +CExpressionToolWorkspace::~CExpressionToolWorkspace() +{ +} + +void CExpressionToolWorkspace::redraw() +{ + CChoreoWidgetDrawHelper drawHelper( this ); + + DrawEventEnd( drawHelper ); + + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + if ( !item ) + continue; + + if ( !item->GetVisible() ) + continue; + + RECT rcBounds; + item->GetBounds( rcBounds ); + + if ( rcBounds.bottom < 0 ) + continue; + if ( rcBounds.top > h2() ) + continue; + + item->Draw( drawHelper ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *elem1 - +// *elem2 - +// Output : int +//----------------------------------------------------------------------------- +int SortFuncByUse(const void *elem1, const void *elem2 ) +{ + TimelineItem *item1 = *( TimelineItem ** )elem1; + TimelineItem *item2 = *( TimelineItem ** )elem2; + + if ( item1->IsValid() == item2->IsValid() ) + return 0; + + if ( !item2->IsValid() && item1->IsValid() ) + return -1; + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *elem1 - +// *elem2 - +// Output : int +//----------------------------------------------------------------------------- +int SortFuncByName(const void *elem1, const void *elem2 ) +{ + TimelineItem *item1 = *( TimelineItem ** )elem1; + TimelineItem *item2 = *( TimelineItem ** )elem2; + + CFlexAnimationTrack *track1 = item1->GetSafeTrack(); + CFlexAnimationTrack *track2 = item2->GetSafeTrack(); + + if ( !track1 || !track2 ) + { + if ( track1 ) + return -1; + if ( track2 ) + return 1; + return 0; + } + + return stricmp( track1->GetFlexControllerName(), track2->GetFlexControllerName() ); +} + +void CExpressionToolWorkspace::OnSortByUsed( void ) +{ + qsort( m_pItems, GLOBAL_STUDIO_FLEX_CONTROL_COUNT, sizeof( TimelineItem * ), SortFuncByUse ); + LayoutItems( false ); +} + +void CExpressionToolWorkspace::OnSortByName( void ) +{ + qsort( m_pItems, GLOBAL_STUDIO_FLEX_CONTROL_COUNT, sizeof( TimelineItem * ), SortFuncByName ); + LayoutItems( false ); +} + +void CExpressionToolWorkspace::DrawEventEnd( CChoreoWidgetDrawHelper& drawHelper ) +{ + if ( !g_pExpressionTool ) + return; + + CChoreoEvent *e = g_pExpressionTool->GetSafeEvent(); + if ( !e ) + return; + + float duration = e->GetDuration(); + if ( !duration ) + return; + + int leftx = g_pExpressionTool->GetPixelForTimeValue( duration ) -5; + if ( leftx >= w2() ) + return; + + RECT rcClient; + drawHelper.GetClientRect( rcClient ); + + drawHelper.DrawColoredLine( + COLOR_CHOREO_ENDTIME, PS_SOLID, 1, + leftx, rcClient.top, leftx, rcClient.bottom ); + +} + +int CExpressionToolWorkspace::GetItemUnderMouse( int mx, int my ) +{ + POINT pt; + pt.x = mx; + pt.y = my; + + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + if ( !item ) + continue; + + if ( !item->GetVisible() ) + continue; + + RECT rc; + item->GetBounds( rc ); + + if ( PtInRect( &rc, pt ) ) + { + return i; + } + } + + return -1; +} + +void CExpressionToolWorkspace::MouseToToolMouse( int& mx, int& my, char *reason ) +{ + POINT pt; + pt.x = mx; + pt.y = my; + + ClientToScreen( (HWND)getHandle(), &pt ); + ScreenToClient( (HWND)getParent()->getHandle(), &pt ); + + mx = pt.x; + my = pt.y; +} + +int CExpressionToolWorkspace::handleEvent( mxEvent *event ) +{ + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + int iret = 0; + + switch ( event->event ) + { + case mxEvent::MouseDown: + { + HWND wnd = (HWND)getParent()->getHandle(); + SetFocus( wnd ); + SetWindowPos( wnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + + { + int mx = (short)event->x; + int my = (short)event->y; + + MouseToToolMouse( mx, my, "CExpressionToolWorkspace mousedown" ); + + g_pExpressionTool->SetClickedPos( mx, my ); + g_pExpressionTool->SetMouseOverPos( mx, my ); + g_pExpressionTool->DrawMouseOverPos(); + + } + + int oldFocus = m_nFocusItem; + m_nFocusItem = GetItemUnderMouse( (short)event->x, (short)event->y ); + + if ( oldFocus != -1 && + oldFocus != m_nFocusItem ) + { + TimelineItem *item = GetItem( oldFocus ); + if ( item ) + { + item->DrawSelf(); + } + } + if ( m_nFocusItem != -1 ) + { + TimelineItem *item = GetItem( m_nFocusItem ); + if ( item ) + { + RECT rc; + item->GetBounds( rc ); + + event->x -= rc.left; + event->y -= rc.top; + + iret = item->handleEvent( event ); + } + } + + iret = 1; + } + break; + case mxEvent::MouseDrag: + case mxEvent::MouseMove: + { + // + bool handled = false; + + if ( m_nFocusItem != -1 ) + { + TimelineItem *item = GetItem( m_nFocusItem ); + if ( item ) + { + RECT rc; + item->GetBounds( rc ); + + event->x -= rc.left; + event->y -= rc.top; + + iret = item->handleEvent( event ); + + if ( event->event == mxEvent::MouseDrag ) + { + int mx, my; + + item->GetLastMouse( mx, my ); + mx += rc.left; + my += rc.top; + + MouseToToolMouse( mx, my, "CExpressionToolWorkspace mousedrag" ); + + g_pExpressionTool->SetMouseOverPos( mx, my ); + g_pExpressionTool->DrawMouseOverPos(); + handled = true; + } + } + } + + if ( !handled ) + { + int mx = (short)event->x; + int my = (short)event->y; + + mx += TRAY_ITEM_INSET; + + MouseToToolMouse( mx, my, "CExpressionToolWorkspace mousemove" ); + + g_pExpressionTool->SetMouseOverPos( mx, my ); + g_pExpressionTool->DrawMouseOverPos(); + } + } + break; + case mxEvent::MouseUp: + { + // + { + int mx = (short)event->x; + int my = (short)event->y; + + MouseToToolMouse( mx, my, "CExpressionToolWorkspace mouseup" ); + + g_pExpressionTool->SetMouseOverPos( mx, my ); + g_pExpressionTool->DrawMouseOverPos(); + + } + + if ( m_nFocusItem != -1 ) + { + TimelineItem *item = GetItem( m_nFocusItem ); + if ( item ) + { + RECT rc; + item->GetBounds( rc ); + + event->x -= rc.left; + event->y -= rc.top; + + iret = item->handleEvent( event ); + } + } + } + break; + case mxEvent::Size: + { + RepositionVSlider(); + LayoutItems(); + iret = 1; + } + break; + case mxEvent::MouseWheeled: + // Tell parent + { + if ( event->modifiers & mxEvent::KeyShift ) + { + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( scene ) + { + int tz = g_pChoreoView->GetTimeZoom( g_pExpressionTool->GetToolName() ); + + // Zoom time in / out + if ( event->height > 0 ) + { + g_pChoreoView->SetTimeZoom( g_pExpressionTool->GetToolName(), min( tz + TIME_ZOOM_STEP, MAX_TIME_ZOOM ), false ); + } + else + { + g_pChoreoView->SetTimeZoom( g_pExpressionTool->GetToolName(), min( tz - TIME_ZOOM_STEP, MAX_TIME_ZOOM ), false ); + } + g_pExpressionTool->RepositionHSlider(); + } + redraw(); + iret = 1; + return iret; + } + + int offset = 0; + int jump = 50; + + if ( event->height < 0 ) + { + offset = m_pVertScrollBar->getValue(); + offset += jump; + offset = min( offset, m_pVertScrollBar->getMaxValue() ); + } + else + { + offset = m_pVertScrollBar->getValue(); + offset -= jump; + offset = max( offset, m_pVertScrollBar->getMinValue() ); + } + + m_pVertScrollBar->setValue( offset ); + InvalidateRect( (HWND)m_pVertScrollBar->getHandle(), NULL, TRUE ); + m_nTopOffset = offset; + LayoutItems(); + iret = 1; + } + break; + case mxEvent::Action: + { + iret = 1; + switch ( event->action ) + { + default: + iret = 0; + break; + case IDC_EXPRESSIONTOOLVSCROLL: + { + int offset = 0; + bool processed = true; + + switch ( event->modifiers ) + { + case SB_THUMBTRACK: + offset = event->height; + break; + case SB_PAGEUP: + offset = m_pVertScrollBar->getValue(); + offset -= 100; + offset = max( offset, m_pVertScrollBar->getMinValue() ); + break; + case SB_PAGEDOWN: + offset = m_pVertScrollBar->getValue(); + offset += 100; + offset = min( offset, m_pVertScrollBar->getMaxValue() ); + break; + case SB_LINEDOWN: + offset = m_pVertScrollBar->getValue(); + offset += 10; + offset = min( offset, m_pVertScrollBar->getMaxValue() ); + break; + case SB_LINEUP: + offset = m_pVertScrollBar->getValue(); + offset -= 10; + offset = max( offset, m_pVertScrollBar->getMinValue() ); + break; + default: + processed = false; + break; + } + + if ( processed ) + { + m_pVertScrollBar->setValue( offset ); + InvalidateRect( (HWND)m_pVertScrollBar->getHandle(), NULL, TRUE ); + m_nTopOffset = offset; + LayoutItems(); + } + } + } + } + break; + } + return iret; +} + +void CExpressionToolWorkspace::HideTimelines( void ) +{ + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + Assert( item ); + item->SetVisible( false ); + } + + redraw(); +} + + +TimelineItem *CExpressionToolWorkspace::GetItem( int number ) +{ + if ( number < 0 || number >= GLOBAL_STUDIO_FLEX_CONTROL_COUNT ) + { + return NULL; + } + return m_pItems[ number ]; +} + +TimelineItem *CExpressionToolWorkspace::GetClickedItem( void ) +{ + return GetItem( m_nFocusItem ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpressionToolWorkspace::ClearClickedItem( void ) +{ + m_nFocusItem = -1; +} + + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : force - force vert scrollbar recomputation +//----------------------------------------------------------------------------- +void CExpressionToolWorkspace::LayoutItems( bool force /* = false */ ) +{ + int x = TRAY_ITEM_INSET; + int y = - m_nTopOffset; + int width = w2() - 2 * TRAY_ITEM_INSET - m_nScrollbarHeight; + int height; + + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + if ( !item || !item->GetVisible() ) + continue; + + height = item->GetHeight(); + + RECT rcBounds; + rcBounds.left = x; + rcBounds.top = y; + rcBounds.right = x + width; + rcBounds.bottom = y + height; + + item->SetBounds( rcBounds ); + y += height + m_nItemGap; + } + + if ( force || ( ComputeVPixelsNeeded() != m_nLastVPixelsNeeded ) ) + { + RepositionVSlider(); + } + + redraw(); +} + +int CExpressionToolWorkspace::ComputeVPixelsNeeded( void ) +{ + int pixels = 0; + + // Count visible + int c = 0; + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + if ( !item || !item->GetVisible() ) + continue; + + c += item->GetHeight(); + c += m_nItemGap; + } + + pixels += c; + + return pixels; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpressionToolWorkspace::RepositionVSlider( void ) +{ + int pixelsneeded = ComputeVPixelsNeeded(); + + if ( pixelsneeded <= ( h2() )) + { + m_pVertScrollBar->setVisible( false ); + m_nTopOffset = 0; + } + else + { + m_pVertScrollBar->setVisible( true ); + } + + m_pVertScrollBar->setBounds( + w2() - m_nScrollbarHeight, + 0, + m_nScrollbarHeight, + h2() ); + + m_nTopOffset = max( 0, m_nTopOffset ); + m_nTopOffset = min( pixelsneeded, m_nTopOffset ); + + m_pVertScrollBar->setRange( 0, pixelsneeded ); + m_pVertScrollBar->setValue( m_nTopOffset ); + m_pVertScrollBar->setPagesize( h2() ); + + m_nLastVPixelsNeeded = pixelsneeded; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpressionToolWorkspace::DisableAllExcept( void ) +{ + TimelineItem *keepExpanded = GetClickedItem(); + if ( !keepExpanded ) + return; + + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Disable All Except" ); + + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + + item->SetActive( item == keepExpanded ? true : false ); + } + + LayoutItems(); + g_pChoreoView->PushRedo( "Disable All Except" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpressionToolWorkspace::EnableValid( void ) +{ + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Enable Valid" ); + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + + item->SetActive( item->IsValid() ); + } + + LayoutItems(); + g_pChoreoView->PushRedo( "Enable Valid" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpressionToolWorkspace::CollapseAll( TimelineItem *keepExpanded ) +{ + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + + item->SetCollapsed( item == keepExpanded ? false : true ); + } + + LayoutItems(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpressionToolWorkspace::ExpandAll( void ) +{ + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + item->SetCollapsed( false ); + } + + LayoutItems(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpressionToolWorkspace::OnSnapAll() +{ + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Snap All" ); + + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + item->SnapAll(); + } + + g_pChoreoView->PushRedo( "Snap All" ); + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpressionToolWorkspace::OnDeleteColumn() +{ + float t = g_pExpressionTool->GetTimeForClickedPos(); + + float snapped = FacePoser_SnapTime( t ); + int scenefps = FacePoser_GetSceneFPS(); + + if ( scenefps <= 0 ) + { + Con_Printf( "Can't delete column, scene fps is <= 0 (%i)\n", scenefps ); + return; + } + + int clickedframe = ( int ) ( scenefps * snapped + 0.5f ); + + // One half of 1/fps on each side + float epsilon = epsilon = 0.5f / (float)scenefps; + + CInputParams params; + memset( ¶ms, 0, sizeof( params ) ); + strcpy( params.m_szDialogTitle, "Delete Column" ); + strcpy( params.m_szPrompt, "Frame(s) to delete [e.g., 82 or 81-91 ]:" ); + Q_snprintf( params.m_szInputText, sizeof( params.m_szInputText ), "%i", clickedframe ); + + if ( !InputProperties( ¶ms ) ) + return; + + int deleteframestart; + int deleteframeend; + + char *sep = Q_strstr( params.m_szInputText, "-" ); + if ( sep ) + { + *sep = 0; + deleteframestart = atoi( params.m_szInputText ); + deleteframeend = atoi( sep + 1 ); + deleteframeend = max( deleteframestart, deleteframeend ); + } + else + { + deleteframestart = atoi( params.m_szInputText ); + deleteframeend = deleteframestart; + } + + float start, end; + + start = (float)deleteframestart / (float)scenefps; + end = (float)deleteframeend / (float)scenefps; + + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Delete Column" ); + + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + item->DeletePoints( start - epsilon, end + epsilon ); + } + + g_pChoreoView->PushRedo( "Delete Column" ); + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpressionToolWorkspace::ExpandValid( void ) +{ + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + bool valid = item->IsValid(); + item->SetCollapsed( !valid ); + } + + LayoutItems(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CExpressionToolWorkspace::CountSelectedSamples( void ) +{ + int c = 0; + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = GetItem( i ); + Assert( item ); + item->CountSelected(); + c += item->GetNumSelected(); + } + return c; +} + +void CExpressionToolWorkspace::MoveSelectedSamples( float dfdx, float dfdy, bool snap ) +{ + int selecteditems = CountSelectedSamples(); + if ( !selecteditems ) + return; + + CChoreoEvent *e = g_pExpressionTool->GetSafeEvent(); + if ( !e ) + return; + + float eventduration = e->GetDuration(); + + for ( int controller = 0; controller < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; controller++ ) + { + TimelineItem *item = GetItem( controller ); + if ( !item ) + continue; + + CFlexAnimationTrack *track = item->GetSafeTrack(); + if ( !track ) + continue; + + // If the track is a combo type track, then move any underlying selected samples, too + for ( int edittype = 0; edittype <= ( track->IsComboType() ? 1 : 0 ); edittype++ ) + { + for ( int i = 0; i < (int)track->GetNumSamples( edittype ); i++ ) + { + CExpressionSample *sample = track->GetSample( i, edittype ); + if ( !sample || !sample->selected ) + continue; + + sample->time += dfdx; + sample->time = clamp( sample->time, 0.0f, eventduration ); + + if ( snap ) + { + sample->time = FacePoser_SnapTime( sample->time ); + } + + sample->value -= dfdy; + sample->value = clamp( sample->value, 0.0f, 1.0f ); + } + } + + track->Resort(); + + item->DrawSelf(); + } +} + +void CExpressionToolWorkspace::DeleteSelectedSamples( void ) +{ + int i, t; + + int selecteditems = CountSelectedSamples(); + if ( !selecteditems ) + return; + + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Delete points" ); + + for ( int controller = 0; controller < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; controller++ ) + { + TimelineItem *item = GetItem( controller ); + if ( !item ) + continue; + + CFlexAnimationTrack *track = item->GetSafeTrack(); + if ( !track ) + continue; + + for ( t = 0; t < 2; t++ ) + { + for ( i = track->GetNumSamples( t ) - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = track->GetSample( i, t ); + if ( !sample->selected ) + continue; + + track->RemoveSample( i, t ); + } + } + + item->DrawSelf(); + } + + g_pChoreoView->PushRedo( "Delete points" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CExpressionToolWorkspace::DeselectAll( void ) +{ + int i, t; + + int selecteditems = CountSelectedSamples(); + if ( !selecteditems ) + return; + + for ( int controller = 0; controller < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; controller++ ) + { + TimelineItem *item = GetItem( controller ); + if ( !item ) + continue; + + CFlexAnimationTrack *track = item->GetSafeTrack(); + if ( !track ) + continue; + + for ( t = 0; t < 2; t++ ) + { + for ( i = track->GetNumSamples( t ) - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = track->GetSample( i, t ); + sample->selected = false; + } + } + + item->DrawSelf(); + } +} + +void CExpressionToolWorkspace::SelectPoints( float start, float end ) +{ + int i, t; + + for ( int controller = 0; controller < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; controller++ ) + { + TimelineItem *item = GetItem( controller ); + if ( !item ) + continue; + + CFlexAnimationTrack *track = item->GetSafeTrack(); + if ( !track ) + continue; + + for ( t = 0; t < 2; t++ ) + { + for ( i = track->GetNumSamples( t ) - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = track->GetSample( i, t ); + bool inrange = ( sample->time >= start && sample->time <= end ); + sample->selected = inrange; + } + } + + item->DrawSelf(); + } +} + +ExpressionTool::ExpressionTool( mxWindow *parent ) +: IFacePoserToolWindow( "ExpressionTool", "Flex Animation" ), mxWindow( parent, 0, 0, 0, 0 ) +{ + m_bSuppressLayout = false; + + SetAutoProcess( true ); + + m_pWorkspace = new CExpressionToolWorkspace( this ); + + m_nFocusEventGlobalID = -1; + + m_flScrub = 0.0f; + m_flScrubTarget = 0.0f; + m_nDragType = DRAGTYPE_NONE; + + m_nClickedX = 0; + m_nClickedY = 0; + + m_hPrevCursor = 0; + + m_nStartX = 0; + m_nStartY = 0; + + m_nMinX = 0; + m_nMaxX = 0; + m_bUseBounds = false; + + m_pLastEvent = NULL; + + m_nMousePos[ 0 ] = m_nMousePos[ 1 ] = 0; + + m_flSelection[ 0 ] = m_flSelection[ 1 ] = 0.0f; + m_bSelectionActive = false; + + m_bLayoutIsValid = false; + m_flPixelsPerSecond = 500.0f; + + m_flLastDuration = 0.0f; + m_nScrollbarHeight = 12; + m_flLeftOffset = 0.0f; + m_nLastHPixelsNeeded = -1; + m_pHorzScrollBar = new mxScrollbar( this, 0, 0, 18, 100, IDC_FLEXHSCROLL, mxScrollbar::Horizontal ); + m_pHorzScrollBar->setVisible( false ); + + m_bInSetEvent = false; + m_flScrubberTimeOffset = 0.0f; +} + +ExpressionTool::~ExpressionTool( void ) +{ +} + +void ExpressionTool::DoTrackLookup( CChoreoEvent *event ) +{ + if ( !event || !models->GetActiveStudioModel() ) + return; + + //if ( event->GetTrackLookupSet() ) + // return; + + // Force recompute + SetEvent( event ); +} + +#pragma optimize( "g", off ) + +void ExpressionTool::SetEvent( CChoreoEvent *event ) +{ + if ( m_bInSetEvent ) + return; + + m_bInSetEvent = true; + + if ( event == m_pLastEvent ) + { + if ( event ) + { + float dur = event->GetDuration(); + if ( dur != m_flLastDuration ) + { + m_flLastDuration = dur; + m_nLastHPixelsNeeded = -1; + m_flLeftOffset = 0.0f; + InvalidateLayout(); + } + + m_nFocusEventGlobalID = event->GetGlobalID(); + } + m_bInSetEvent = false; + return; + } + + m_pLastEvent = event; + + m_pWorkspace->HideTimelines(); + + m_nFocusEventGlobalID = -1; + if ( event ) + { + m_nFocusEventGlobalID = event->GetGlobalID(); + + if ( models->GetActiveStudioModel() ) + { + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if ( hdr ) + { + // Force re-lookup + event->SetTrackLookupSet( false ); + + SetupFlexControllerTracks( hdr, event ); + + int itemCount = 0; + + for ( int i = 0; i < event->GetNumFlexAnimationTracks(); i++ ) + { + CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i ); + Assert( track ); + if ( !track ) + continue; + + TimelineItem *item = m_pWorkspace->GetItem( itemCount++ ); + item->SetExpressionInfo( track, track->GetFlexControllerIndex( 0 ) ); + item->SetCollapsed( track->GetNumSamples( 0 ) <= 0 ); + item->SetVisible( true ); + } + + m_pWorkspace->LayoutItems( true ); + } + } + } + + DeselectAll(); + + if ( event ) + { + m_flLastDuration = event->GetDuration(); + } + else + { + m_flLastDuration = 0.0f; + } + + m_flLeftOffset = 0.0f; + m_nLastHPixelsNeeded = -1; + InvalidateLayout(); + + m_bInSetEvent = false; +} + +#pragma optimize( "g", on ) + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ExpressionTool::HasCopyData( void ) +{ + return ( m_CopyData[0].Size() != 0 ) ? true : false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *source - +//----------------------------------------------------------------------------- +void ExpressionTool::Copy( CFlexAnimationTrack *source ) +{ + for ( int t = 0; t < 2; t++ ) + { + m_CopyData[ t ].RemoveAll(); + + if ( t == 0 || source->IsComboType() ) + { + for ( int i = 0 ; i < source->GetNumSamples( t ); i++ ) + { + CExpressionSample *s = source->GetSample( i, t ); + m_CopyData[ t ].AddToTail( *s ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *destination - +//----------------------------------------------------------------------------- +void ExpressionTool::Paste( CFlexAnimationTrack *destination ) +{ + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Paste" ); + + destination->Clear(); + + for ( int t = 0; t < 2; t++ ) + { + for ( int i = 0; i < m_CopyData[ t ].Size() ; i++ ) + { + CExpressionSample *s = &m_CopyData[ t ][ i ]; + + if ( t == 0 || destination->IsComboType() ) + { + destination->AddSample( s->time, s->value, t ); + } + } + + destination->Resort( t ); + } + g_pChoreoView->PushRedo( "Paste" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CChoreoEvent *ExpressionTool::GetSafeEvent( void ) +{ + if ( m_nFocusEventGlobalID == -1 ) + return NULL; + + if ( !g_pChoreoView ) + return NULL; + + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( !scene ) + return NULL; + + // look to see if it's focused any any event + for ( int i = 0; i < scene->GetNumEvents() ; i++ ) + { + CChoreoEvent *e = scene->GetEvent( i ); + if ( !e || e->GetType() != CChoreoEvent::FLEXANIMATION ) + continue; + + if ( e->GetGlobalID() == m_nFocusEventGlobalID ) + { + DoTrackLookup( e ); + return e; + } + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rcHandle - +//----------------------------------------------------------------------------- +void ExpressionTool::GetScrubHandleRect( RECT& rcHandle, bool clipped ) +{ + float pixel = 0.0f; + if ( m_pWorkspace->w2() > 0 ) + { + pixel = GetPixelForTimeValue( m_flScrub ); + if ( clipped ) + { + pixel = clamp( pixel, SCRUBBER_HANDLE_WIDTH/2, w2() - SCRUBBER_HANDLE_WIDTH/2 ); + } + } + + rcHandle.left = pixel-SCRUBBER_HANDLE_WIDTH/2; + rcHandle.right = pixel + SCRUBBER_HANDLE_WIDTH/2; + rcHandle.top = 2 + GetCaptionHeight(); + rcHandle.bottom = rcHandle.top + SCRUBBER_HANDLE_HEIGHT; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rcHandle - +//----------------------------------------------------------------------------- +void ExpressionTool::DrawScrubHandle( CChoreoWidgetDrawHelper& drawHelper, RECT& rcHandle ) +{ + HBRUSH br = CreateSolidBrush( RGB( 0, 150, 100 ) ); + + COLORREF areaBorder = RGB( 230, 230, 220 ); + + drawHelper.DrawColoredLine( areaBorder, + PS_SOLID, 1, 0, rcHandle.top, w2(), rcHandle.top ); + drawHelper.DrawColoredLine( areaBorder, + PS_SOLID, 1, 0, rcHandle.bottom, w2(), rcHandle.bottom ); + + drawHelper.DrawFilledRect( br, rcHandle ); + + // + char sz[ 32 ]; + sprintf( sz, "%.3f", m_flScrub ); + + CChoreoEvent *ev = GetSafeEvent(); + if ( ev ) + { + float st, ed; + st = ev->GetStartTime(); + ed = ev->GetEndTime(); + + float dt = ed - st; + if ( dt > 0.0f ) + { + sprintf( sz, "%.3f", st + m_flScrub ); + } + } + + int len = drawHelper.CalcTextWidth( "Arial", 9, 500, sz ); + + RECT rcText = rcHandle; + + int textw = rcText.right - rcText.left; + + rcText.left += ( textw - len ) / 2; + + drawHelper.DrawColoredText( "Arial", 9, 500, RGB( 255, 255, 255 ), rcText, sz ); + + DeleteObject( br ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ExpressionTool::IsMouseOverScrubHandle( mxEvent *event ) +{ + RECT rcHandle; + GetScrubHandleRect( rcHandle, true ); + InflateRect( &rcHandle, 2, 2 ); + + POINT pt; + pt.x = (short)event->x; + pt.y = (short)event->y; + if ( PtInRect( &rcHandle, pt ) ) + { + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ExpressionTool::IsProcessing( void ) +{ + if ( !GetSafeEvent() ) + return false; + + if ( m_flScrub != m_flScrubTarget ) + return true; + + return false; +} + +bool ExpressionTool::IsScrubbing( void ) const +{ + bool scrubbing = ( m_nDragType == DRAGTYPE_SCRUBBER ) ? true : false; + return scrubbing; +} + +void ExpressionTool::Think( float dt ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + bool scrubbing = IsScrubbing(); + + ScrubThink( dt, scrubbing ); +} + +void ExpressionTool::SetScrubTime( float t ) +{ + m_flScrub = t; + CChoreoEvent *e = GetSafeEvent(); + if ( e ) + { + float realtime = e->GetStartTime() + m_flScrub; + + g_pChoreoView->SetScrubTime( realtime ); + g_pChoreoView->DrawScrubHandle(); + } +} + +void ExpressionTool::SetScrubTargetTime( float t ) +{ + m_flScrubTarget = t; + CChoreoEvent *e = GetSafeEvent(); + if ( e ) + { + float realtime = e->GetStartTime() + m_flScrubTarget; + + g_pChoreoView->SetScrubTargetTime( realtime ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dt - +//----------------------------------------------------------------------------- +void ExpressionTool::ScrubThink( float dt, bool scrubbing ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + if ( m_flScrubTarget == m_flScrub && !scrubbing ) + return; + + float d = m_flScrubTarget - m_flScrub; + int sign = d > 0.0f ? 1 : -1; + + float maxmove = dt; + + if ( sign > 0 ) + { + if ( d < maxmove ) + { + SetScrubTime( m_flScrubTarget ); + } + else + { + SetScrubTime( m_flScrub + maxmove ); + } + } + else + { + if ( -d < maxmove ) + { + SetScrubTime( m_flScrubTarget ); + } + else + { + SetScrubTime( m_flScrub - maxmove ); + } + } + + if ( scrubbing ) + { + g_pMatSysWindow->Frame(); + } +} + +void ExpressionTool::redraw() +{ + if ( !ToolCanDraw() ) + return; + + CChoreoWidgetDrawHelper drawHelper( this ); + HandleToolRedraw( drawHelper ); + + COLORREF areaBorder = RGB( 230, 230, 220 ); + + RECT rcSelection; + GetWorkspaceRect( rcSelection ); + + drawHelper.DrawColoredLine( areaBorder, + PS_SOLID, 1, 0, rcSelection.top, w2(), rcSelection.top ); + drawHelper.DrawColoredLine( areaBorder, + PS_SOLID, 1, 0, rcSelection.bottom, w2(), rcSelection.bottom ); + + if ( m_bSelectionActive ) + { + RECT rcClient; + drawHelper.GetClientRect( rcClient ); + + int left, right; + left = GetPixelForTimeValue( m_flSelection[ 0 ] ); + right = GetPixelForTimeValue( m_flSelection[ 1 ] ); + + rcSelection.left = left; + rcSelection.right = right; + rcSelection.bottom = TRAY_HEIGHT; + + drawHelper.DrawFilledRect( RGB( 200, 220, 230 ), rcSelection ); + + drawHelper.DrawColoredLine( RGB( 100, 100, 255 ), PS_SOLID, 3, rcSelection.left, rcSelection.top, rcSelection.left, rcSelection.bottom ); + drawHelper.DrawColoredLine( RGB( 100, 100, 255 ), PS_SOLID, 3, rcSelection.right, rcSelection.top, rcSelection.right, rcSelection.bottom ); + + } + + CChoreoEvent *ev = GetSafeEvent(); + if ( ev ) + { + RECT rcText; + drawHelper.GetClientRect( rcText ); + rcText.top += GetCaptionHeight()+1; + rcText.bottom = rcText.top + 13; + rcText.left += 5; + rcText.right -= 5; + + OffsetRect( &rcText, 0, 12 ); + + int current, total; + + g_pChoreoView->GetUndoLevels( current, total ); + if ( total > 0 ) + { + RECT rcUndo = rcText; + OffsetRect( &rcUndo, 0, 2 ); + + drawHelper.DrawColoredText( "Small Fonts", 8, FW_NORMAL, RGB( 0, 100, 0 ), rcUndo, + "Undo: %i/%i", current, total ); + } + + rcText.left += 60; + + // Found it, write out description + // + drawHelper.DrawColoredText( "Arial", 11, 900, RGB( 200, 150, 100 ), rcText, + "Event: %s", + ev->GetName() ); + + OffsetRect( &rcText, 0, 30 ); + + rcText.left = 5; + + RECT timeRect = rcText; + + timeRect.right = timeRect.left + 100; + + char sz[ 32 ]; + + float st, ed; + + GetStartAndEndTime( st, ed ); + + st += ev->GetStartTime(); + ed += ev->GetStartTime(); + + Q_snprintf( sz, sizeof( sz ), "%.2f", st ); + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 0, 0, 0 ), timeRect, sz ); + + timeRect = rcText; + + Q_snprintf( sz, sizeof( sz ), "%.2f", ed ); + + int textW = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, sz ); + + timeRect.right = w2() - 10; + timeRect.left = timeRect.right - textW; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 0, 0, 0 ), timeRect, sz ); + } + + RECT rcHandle; + GetScrubHandleRect( rcHandle, true ); + DrawScrubHandle( drawHelper, rcHandle ); + + DrawRelativeTags( drawHelper ); + + RECT rcPos; + GetMouseOverPosRect( rcPos ); + DrawMouseOverPos( drawHelper, rcPos ); + + DrawEventEnd( drawHelper ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *tag - +//----------------------------------------------------------------------------- +bool ExpressionTool::GetTimingTagRect( RECT& rcClient, CChoreoEvent *event, CFlexTimingTag *tag, RECT& rcTag ) +{ + rcTag = rcClient; + + int tagx = GetPixelForTimeValue( tag->GetStartTime() - event->GetStartTime() ); + + rcTag.top = rcClient.bottom - 6; + rcTag.bottom = rcTag.top + 6; + rcTag.left = tagx - 3; + rcTag.right = tagx + 3; + + return true; +} + +// Get workspace min, max point in terms of tool window +void ExpressionTool::GetWorkspaceLeftRight( int& left, int& right ) +{ + POINT pt; + pt.x = TRAY_ITEM_INSET; + pt.y = 0; + + ClientToScreen( (HWND)m_pWorkspace->getHandle(), &pt ); + ScreenToClient( (HWND)getHandle(), &pt ); + + left = (short)pt.x; + + pt.x = m_pWorkspace->w2() - TRAY_ITEM_INSET - 12; + pt.y = 0; + + ClientToScreen( (HWND)m_pWorkspace->getHandle(), &pt ); + ScreenToClient( (HWND)getHandle(), &pt ); + + right = (short)pt.x; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +// Output : CFlexTimingTag +//----------------------------------------------------------------------------- +CFlexTimingTag *ExpressionTool::IsMouseOverTag( int mx, int my ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return NULL; + + RECT rcClient; + GetClientRect( (HWND)getHandle(), &rcClient ); + + int left, right; + + GetWorkspaceLeftRight( left, right ); + + rcClient.left = left; + rcClient.right = right; + rcClient.top = GetCaptionHeight(); + rcClient.bottom = rcClient.top + TRAY_HEIGHT; + + POINT pt; + pt.x = mx; + pt.y = my; + + for ( int i = 0 ; i < event->GetNumTimingTags(); i++ ) + { + CFlexTimingTag *tag = event->GetTimingTag( i ); + if ( !tag ) + continue; + + RECT rcTag; + + if ( !GetTimingTagRect( rcClient, event, tag, rcTag ) ) + continue; + + if ( !PtInRect( &rcTag, pt ) ) + continue; + + return tag; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +//----------------------------------------------------------------------------- +void ExpressionTool::DrawRelativeTags( CChoreoWidgetDrawHelper& drawHelper ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + float st, ed; + GetStartAndEndTime( st, ed ); + + if ( event->GetDuration() <= 0.0f ) + return; + + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( !scene ) + return; + + RECT rcClient; + drawHelper.GetClientRect( rcClient ); + + int left, right; + + GetWorkspaceLeftRight( left, right ); + + rcClient.top += GetCaptionHeight(); + + rcClient.left = left; + rcClient.right = right; + + rcClient.bottom = rcClient.top + TRAY_HEIGHT; + + // Iterate relative tags + for ( int i = 0; i < scene->GetNumActors(); i++ ) + { + CChoreoActor *a = scene->GetActor( i ); + if ( !a ) + continue; + + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannel *c = a->GetChannel( j ); + if ( !c ) + continue; + + for ( int k = 0 ; k < c->GetNumEvents(); k++ ) + { + CChoreoEvent *e = c->GetEvent( k ); + if ( !e ) + continue; + + // add each tag to combo box + for ( int t = 0; t < e->GetNumRelativeTags(); t++ ) + { + CEventRelativeTag *tag = e->GetRelativeTag( t ); + if ( !tag ) + continue; + + //SendMessage( control, CB_ADDSTRING, 0, (LPARAM)va( "\"%s\" \"%s\"", tag->GetName(), e->GetParameters() ) ); + bool clipped; + int tagx = GetPixelForTimeValue( tag->GetStartTime() - event->GetStartTime(), &clipped ); + if ( clipped ) + continue; + + //drawHelper.DrawColoredLine( RGB( 180, 180, 220 ), PS_SOLID, 1, tagx, rcClient.top, tagx, rcClient.bottom ); + + RECT rcMark; + rcMark = rcClient; + rcMark.top = rcClient.bottom - 6; + rcMark.left = tagx - 3; + rcMark.right = tagx + 3; + + drawHelper.DrawTriangleMarker( rcMark, RGB( 0, 100, 250 ) ); + + RECT rcText; + rcText = rcMark; + rcText.top -= 10; + + int len = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, tag->GetName() ); + rcText.left = tagx - len / 2; + rcText.right = rcText.left + len + 2; + + rcText.bottom = rcText.top + 10; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 0, 100, 200 ), rcText, tag->GetName() ); + + } + } + } + } + + for ( int t = 0; t < event->GetNumTimingTags(); t++ ) + { + CFlexTimingTag *tag = event->GetTimingTag( t ); + if ( !tag ) + continue; + + RECT rcMark; + + if ( !GetTimingTagRect( rcClient, event, tag, rcMark ) ) + continue; + + drawHelper.DrawTriangleMarker( rcMark, RGB( 250, 100, 0 ) ); + + RECT rcText; + rcText = rcMark; + rcText.top -= 20; + + char text[ 256 ]; + sprintf( text, "%s", tag->GetName() ); + if ( tag->GetLocked() ) + { + strcat( text, " - locked" ); + } + + int len = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, text ); + rcText.left = ( rcMark.left + rcMark.right ) / 2 - len / 2; + rcText.right = rcText.left + len + 2; + + rcText.bottom = rcText.top + 10; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 200, 100, 0 ), rcText, text ); + + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ExpressionTool::ShowContextMenu( mxEvent *event, bool include_track_menus ) +{ + // Construct main menu + mxPopupMenu *pop = new mxPopupMenu(); + + TimelineItem *item = NULL; + CFlexAnimationTrack *track = NULL; + + if ( include_track_menus ) + { + item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + item->CountSelected(); + track = item->GetSafeTrack(); + } + } + + int current, total; + g_pChoreoView->GetUndoLevels( current, total ); + if ( total > 0 ) + { + if ( current > 0 ) + { + pop->add( va( "Undo %s", g_pChoreoView->GetUndoDescription() ), IDC_UNDO_FA ); + } + + if ( current <= total - 1 ) + { + pop->add( va( "Redo %s", g_pChoreoView->GetRedoDescription() ), IDC_REDO_FA ); + } + pop->addSeparator(); + } + + // Create expand menu + mxPopupMenu *expand = new mxPopupMenu(); + if ( item && track && item->IsCollapsed() ) + { + expand->add( va( "Track '%s'", track->GetFlexControllerName() ), IDC_TL_EXPAND ); + } + expand->add( "All tracks", IDC_EXPANDALL ); + expand->add( "Used tracks", IDC_EXPANDVALID ); + + pop->addMenu( "Expand", expand ); + + mxPopupMenu *collapse = new mxPopupMenu; + + if ( item && track && !item->IsCollapsed() ) + { + collapse->add( va( "Track '%s'", track->GetFlexControllerName() ), IDC_TL_COLLAPSE ); + collapse->add( va( "All tracks except '%s'", track->GetFlexControllerName() ), IDC_COLLAPSE_ALL_EXCEPT ); + } + + collapse->add( "All tracks", IDC_COLLAPSEALL ); + + pop->addMenu( "Collapse", collapse ); + + pop->addSeparator(); + + pop->add( va( "Enable all valid" ), IDC_ENABLE_ALL_VALID ); + + if ( item && track ) + { + if ( item->IsActive() ) + { + pop->add( va( "Disable '%s'", track->GetFlexControllerName() ), IDC_TL_DISABLE ); + } + else + { + pop->add( va( "Enable '%s'", track->GetFlexControllerName() ), IDC_TL_ENABLE ); + } + pop->add( va( "Disable all except '%s'", track->GetFlexControllerName() ), IDC_DISABLE_ALL_EXCEPT ); + + pop->addSeparator(); + pop->add( "Copy", IDC_TL_COPY ); + if ( HasCopyData() ) + { + pop->add( "Paste", IDC_TL_PASTE ); + } + + pop->addSeparator(); + if ( item->GetNumSelected() > 0 ) + { + pop->add( va( "Delete" ), IDC_TL_DELETE ); + pop->add( "Deselect all", IDC_TL_DESELECT ); + pop->add( va( "Scale selected..." ), IDC_FLEX_SCALESAMPLES ); + } + pop->add( "Select all", IDC_TL_SELECTALL ); + + if ( FacePoser_IsSnapping() ) + { + mxPopupMenu *snap = new mxPopupMenu(); + + snap->add( va( "All points" ), IDC_TL_SNAPALL ); + snap->add( va( "All points in '%s'", track->GetFlexControllerName() ), IDC_TL_SNAPPOINTS ); + snap->add( va( "Selected points in '%s'", track->GetFlexControllerName() ), IDC_TL_SNAPSELECTED ); + + pop->addSeparator(); + + pop->addMenu( "Snap", snap ); + } + + if ( track->IsComboType() ) + { + pop->addSeparator(); + + if ( item->GetEditType() == 0 ) + { + pop->add( "Edit <left/right>", IDC_TL_EDITLEFTRIGHT ); + } + else + { + pop->add( "Edit <amount>", IDC_TL_EDITNORMAL ); + } + } + + pop->addSeparator(); + mxPopupMenu *heightMenu = new mxPopupMenu(); + heightMenu->add( va( "Reset '%s'", track->GetFlexControllerName() ) , IDC_ET_RESET_ITEM_SIZE ); + heightMenu->add( "Reset All", IDC_ET_RESET_ALL_ITEM_SIZES ); + pop->addMenu( "Height", heightMenu ); + + pop->addSeparator(); + pop->add( "Edge Properties...", IDC_ET_EDGEPROPERTIES ); + } + pop->addSeparator(); + + mxPopupMenu *tagmenu = new mxPopupMenu(); + + CFlexTimingTag *tag = IsMouseOverTag( (short)event->x, (short)event->y ); + if ( tag ) + { + if ( tag->GetLocked() ) + { + tagmenu->add( va( "Unlock tag '%s'...", tag->GetName() ), IDC_UNLOCK_TIMING_TAG ); + } + else + { + tagmenu->add( va( "Lock tag '%s'...", tag->GetName() ), IDC_LOCK_TIMING_TAG ); + } + tagmenu->addSeparator(); + tagmenu->add( va( "Delete tag '%s'...", tag->GetName() ), IDC_DELETE_TIMING_TAG ); + } + else + { + tagmenu->add( "Insert...", IDC_INSERT_TIMING_TAG ); + } + + bool bMouseOverSelection = IsMouseOverSelection( (short)event->x, (short)event->y ); + + if ( bMouseOverSelection || HasCopiedColumn() ) + { + mxPopupMenu *selectionMenu = new mxPopupMenu(); + + if ( bMouseOverSelection ) + { + selectionMenu->add( "Copy samples", IDC_ET_SELECTION_COPY ); + } + + if ( HasCopiedColumn() ) + { + selectionMenu->add( "Paste samples", IDC_ET_SELECTION_PASTE ); + } + + if ( bMouseOverSelection ) + { + selectionMenu->addSeparator(); + selectionMenu->add( "Delete samples", IDC_ET_SELECTION_DELETE ); + selectionMenu->add( "Delete samples and shift remainder", IDC_ET_SELECTION_EXCISE ); + } + pop->addMenu( "Column", selectionMenu ); + } + + + + pop->addMenu( "Timing Tags", tagmenu ); + + if ( FacePoser_IsSnapping() ) + { + pop->addSeparator(); + pop->add( "Delete keys by frame", IDC_TL_DELETECOLUMN ); + pop->addSeparator(); + } + + mxPopupMenu *flexmenu = new mxPopupMenu(); + + flexmenu->add( "Copy to sliders", IDC_COPY_TO_FLEX ); + flexmenu->add( "Copy from sliders", IDC_COPY_FROM_FLEX ); + pop->addMenu( "Flex", flexmenu ); + + pop->add( "Create expression...", IDC_NEW_EXPRESSION_FROM_FLEXANIMATION ); + + CChoreoEvent *e = GetSafeEvent(); + if ( e ) + { + mxPopupMenu *importexport = new mxPopupMenu(); + + importexport->add( "Export flex animation...", IDC_EXPORT_FA ); + importexport->add( "Import flex animation...", IDC_IMPORT_FA ); + + pop->addMenu( "Import/Export", importexport ); + } + + pop->add( va( "Change scale..." ), IDC_FLEX_CHANGESCALE ); + + mxPopupMenu *sortmenu = new mxPopupMenu(); + sortmenu->add( "Sort by name", IDC_ET_SORT_BY_NAME ); + sortmenu->add( "Sort by used", IDC_ET_SORT_BY_USED ); + + pop->addMenu( "Sort", sortmenu ); + + pop->popup( this, (short)event->x, (short)event->y ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ExpressionTool::DrawFocusRect( void ) +{ + HDC dc = GetDC( NULL ); + + for ( int i = 0; i < m_FocusRects.Size(); i++ ) + { + RECT rc = m_FocusRects[ i ].m_rcFocus; + + ::DrawFocusRect( dc, &rc ); + } + + ReleaseDC( NULL, dc ); +} + +void ExpressionTool::SetClickedPos( int x, int y ) +{ + m_nClickedX = x; + m_nClickedY = y; +} + +float ExpressionTool::GetTimeForClickedPos( void ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return 0.0f; + + float t = GetTimeValueForMouse( m_nClickedX ); + + // Get spline intensity for controller + float faketime = e->GetStartTime() + t; + return faketime; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dragtype - +// startx - +// cursor - +//----------------------------------------------------------------------------- +void ExpressionTool::StartDragging( int dragtype, int startx, int starty, HCURSOR cursor ) +{ + m_nDragType = dragtype; + m_nStartX = startx; + m_nLastX = startx; + m_nStartY = starty; + m_nLastY = starty; + + if ( m_hPrevCursor ) + { + SetCursor( m_hPrevCursor ); + m_hPrevCursor = NULL; + } + m_hPrevCursor = SetCursor( cursor ); + + m_FocusRects.Purge(); + + RECT rc; + GetWorkspaceRect( rc ); + + RECT rcStart; + rcStart.left = startx; + rcStart.right = startx; + + bool addrect = true; + switch ( dragtype ) + { + default: + case DRAGTYPE_SCRUBBER: + { + RECT rcScrub; + GetScrubHandleRect( rcScrub, true ); + + rcStart = rcScrub; + rcStart.left = ( rcScrub.left + rcScrub.right ) / 2; + rcStart.right = rcStart.left; + rcStart.top = rcScrub.bottom; + + rcStart.bottom = h2(); + } + break; + case DRAGTYPE_FLEXTIMINGTAG: + { + rcStart.top = rc.top; + rcStart.bottom = h2(); + } + break; + case DRAGTYPE_SELECTSAMPLES: + { + float st = GetTimeValueForMouse( startx ); + rcStart.left = GetPixelForTimeValue( st ); + rcStart.right = rcStart.left; + + m_nStartX = rcStart.left; + m_nLastX = rcStart.left; + } + case DRAGTYPE_MOVESELECTIONSTART: + case DRAGTYPE_MOVESELECTIONEND: + { + rcStart.top = rc.top; + rcStart.bottom = rc.bottom; + } + break; + case DRAGTYPE_MOVESELECTION: + { + rcStart.top = rc.top; + rcStart.bottom = rc.bottom; + + // Compute left/right pixels for selection + rcStart.left = GetPixelForTimeValue( m_flSelection[ 0 ] ); + rcStart.right = GetPixelForTimeValue( m_flSelection[ 1 ] ); + } + break; + } + + + if ( addrect ) + { + AddFocusRect( rcStart ); + } + + DrawFocusRect(); +} + +void ExpressionTool::OnMouseMove( mxEvent *event ) +{ + int mx = (short)event->x; + int my = (short)event->y; + + event->x = (short)mx; + + if ( m_nDragType != DRAGTYPE_NONE ) + { + DrawFocusRect(); + + for ( int i = 0; i < m_FocusRects.Size(); i++ ) + { + CFocusRect *f = &m_FocusRects[ i ]; + f->m_rcFocus = f->m_rcOrig; + + switch ( m_nDragType ) + { + default: + { + OffsetRect( &f->m_rcFocus, ( (short)event->x - m_nStartX ), 0 ); + } + break; + case DRAGTYPE_SELECTSAMPLES: + { + float st = GetTimeValueForMouse( mx ); + int snapx = GetPixelForTimeValue( st ); + f->m_rcFocus.left = min( snapx, m_nStartX ); + f->m_rcFocus.right = max( snapx, m_nStartY ); + + POINT offset; + offset.x = 0; + offset.y = 0; + ClientToScreen( (HWND)getHandle(), &offset ); + OffsetRect( &f->m_rcFocus, offset.x, 0 ); + + } + break; + } + } + + DrawFocusRect(); + } + else + { + if ( m_hPrevCursor ) + { + SetCursor( m_hPrevCursor ); + m_hPrevCursor = NULL; + } + + if ( IsMouseOverScrubHandle( event ) ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + } + else if ( IsMouseOverTag( mx, my ) ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + } + else if ( IsMouseOverSelection( (short)event->x, (short)event->y ) ) + { + if ( IsMouseOverSelectionStartEdge( event ) ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + } + else if ( IsMouseOverSelectionEndEdge( event ) ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + } + else + { + if ( event->modifiers & mxEvent::KeyShift ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEALL ) ); + } + } + } + } + + switch ( m_nDragType ) + { + default: + break; + case DRAGTYPE_FLEXTIMINGTAG: + { + ApplyBounds( mx, my ); + } + break; + case DRAGTYPE_SCRUBBER: + { + ApplyBounds( mx, my ); + if ( w2() > 0 ) + { + float t = GetTimeValueForMouse( mx ); + t += m_flScrubberTimeOffset; + ForceScrubPosition( t ); + } + } + break; + } + + m_nLastX = (short)event->x; + m_nLastY = (short)event->y; +} + +int ExpressionTool::handleEvent( mxEvent *event ) +{ + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + int iret = 0; + + if ( HandleToolEvent( event ) ) + { + return iret; + } + + switch ( event->event ) + { + case mxEvent::Size: + { + int w, h; + w = event->width; + h = event->height; + + m_pWorkspace->setBounds( 5, TRAY_HEIGHT + GetCaptionHeight(), w - 10, h - ( TRAY_HEIGHT + 5 + GetCaptionHeight() ) - m_nScrollbarHeight ); + + m_nLastHPixelsNeeded = 0; + InvalidateLayout(); + + iret = 1; + } + break; + case mxEvent::MouseWheeled: + { + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( scene ) + { + int tz = g_pChoreoView->GetTimeZoom( GetToolName() ); + bool shiftdown = ( event->modifiers & mxEvent::KeyShift ) ? true : false; + int stepMultipiler = shiftdown ? 5 : 1; + + // Zoom time in / out + if ( event->height > 0 ) + { + tz = min( tz + TIME_ZOOM_STEP * stepMultipiler, MAX_TIME_ZOOM ); + } + else + { + tz = max( tz - TIME_ZOOM_STEP * stepMultipiler, TIME_ZOOM_STEP ); + } + + g_pChoreoView->SetPreservedTimeZoom( this, tz ); + } + //RepositionHSlider(); + m_pWorkspace->redraw(); + redraw(); + iret = 1; + } + break; + case mxEvent::MouseDown: + { +// bool ctrldown = ( event->modifiers & mxEvent::KeyCtrl ) ? true : false; + bool shiftdown = ( event->modifiers & mxEvent::KeyShift ) ? true : false; + + iret = 1; + + int mx = (short)event->x; + int my = (short)event->y; + + SetClickedPos( mx, my ); + + SetMouseOverPos( mx, my ); + DrawMouseOverPos(); + + if ( event->buttons & mxEvent::MouseRightButton ) + { + ShowContextMenu( event, false ); + return iret; + } + + if ( m_nDragType == DRAGTYPE_NONE ) + { + if ( IsMouseOverScrubHandle( event ) ) + { + if ( w2() > 0 ) + { + float t = GetTimeValueForMouse( (short)event->x ); + m_flScrubberTimeOffset = m_flScrub - t; + float maxoffset = 0.5f * (float)SCRUBBER_HANDLE_WIDTH / GetPixelsPerSecond(); + m_flScrubberTimeOffset = clamp( m_flScrubberTimeOffset, -maxoffset, maxoffset ); + t += m_flScrubberTimeOffset; + ForceScrubPosition( t ); + } + + StartDragging( DRAGTYPE_SCRUBBER, m_nClickedX, m_nClickedY, LoadCursor( NULL, IDC_SIZEWE ) ); + } + else if ( IsMouseOverTag( m_nClickedX, m_nClickedY ) ) + { + StartDragging( DRAGTYPE_FLEXTIMINGTAG, m_nClickedX, m_nClickedY, LoadCursor( NULL, IDC_SIZEWE ) ); + } + else if ( IsMouseOverPoints( m_nClickedX, m_nClickedY ) ) + { + if ( !m_bSelectionActive ) + { + StartDragging( DRAGTYPE_SELECTSAMPLES, m_nClickedX, m_nClickedY, LoadCursor( NULL, IDC_SIZEWE ) ); + } + else + { + // Either move, move edge if ctrl key is held, or deselect + if ( IsMouseOverSelection( m_nClickedX,m_nClickedY ) ) + { + if ( IsMouseOverSelectionStartEdge( event ) ) + { + StartDragging( DRAGTYPE_MOVESELECTIONSTART, m_nClickedX, m_nClickedY, LoadCursor( NULL, IDC_SIZEWE ) ); + } + else if ( IsMouseOverSelectionEndEdge( event ) ) + { + StartDragging( DRAGTYPE_MOVESELECTIONEND, m_nClickedX, m_nClickedY, LoadCursor( NULL, IDC_SIZEWE ) ); + } + else + { + if ( shiftdown ) + { + StartDragging( DRAGTYPE_MOVESELECTION, m_nClickedX, m_nClickedY, LoadCursor( NULL, IDC_SIZEALL ) ); + } + } + } + else + { + m_bSelectionActive = false; + redraw(); + return iret; + } + } + } + else + { + if ( w2() > 0 ) + { + float t = GetTimeValueForMouse( (short)event->x ); + + SetScrubTargetTime( t ); + } + } + + CalcBounds( m_nDragType ); + } + } + break; + case mxEvent::MouseDrag: + case mxEvent::MouseMove: + { + int mx = (short)event->x; + int my = (short)event->y; + + SetMouseOverPos( mx, my ); + DrawMouseOverPos(); + + OnMouseMove( event ); + + iret = 1; + } + break; + case mxEvent::MouseUp: + { + if ( event->buttons & mxEvent::MouseRightButton ) + { + return 1; + } + + int mx = (short)event->x; + int my = (short)event->y; + + if ( m_nDragType != DRAGTYPE_NONE ) + { + DrawFocusRect(); + } + + if ( m_hPrevCursor ) + { + SetCursor( m_hPrevCursor ); + m_hPrevCursor = 0; + } + + switch ( m_nDragType ) + { + case DRAGTYPE_NONE: + break; + case DRAGTYPE_SELECTSAMPLES: + FinishSelect( m_nStartX, mx ); + break; + case DRAGTYPE_MOVESELECTION: + FinishMoveSelection( m_nStartX, mx ); + break; + case DRAGTYPE_MOVESELECTIONSTART: + FinishMoveSelectionStart( m_nStartX, mx ); + break; + case DRAGTYPE_MOVESELECTIONEND: + FinishMoveSelectionEnd( m_nStartX, mx ); + break; + case DRAGTYPE_SCRUBBER: + { + ApplyBounds( mx, my ); + +// int dx = mx - m_nStartX; +// int dy = my = m_nStartY; + + if ( w2() > 0 ) + { + float t = GetTimeValueForMouse( (short)event->x ); + t += m_flScrubberTimeOffset; + m_flScrubberTimeOffset = 0.0f; + ForceScrubPosition( t ); + } + } + break; + case DRAGTYPE_FLEXTIMINGTAG: + { + ApplyBounds( mx, my ); + +// int dx = mx - m_nStartX; +// int dy = my = m_nStartY; + + // Compute dx, dy and apply to sections + //Con_Printf( "dx == %i\n", dx ); + CFlexTimingTag *tag = IsMouseOverTag( m_nStartX, m_nStartY ); + CChoreoEvent *ev = GetSafeEvent(); + if ( tag && g_pChoreoView && ev && ev->GetDuration() ) + { + float t = GetTimeValueForMouse( mx ); + + float percent = t / ev->GetDuration(); + + g_pChoreoView->SetDirty( true ); + + g_pChoreoView->PushUndo( "Move Timing Tag" ); + + if ( tag->GetLocked() ) + { + // Resample all control points on right/left + // of locked tags all the way to the next lock or edge + ResampleControlPoints( tag, percent ); + } + + tag->SetPercentage( percent ); + + g_pChoreoView->PushRedo( "Move Timing Tag" ); + } + + LayoutItems( true ); + redraw(); + } + break; + } + + m_nDragType = DRAGTYPE_NONE; + + SetMouseOverPos( mx, my ); + DrawMouseOverPos(); + + iret = 1; + } + break; + case mxEvent::Action: + { + iret = 1; + switch ( event->action ) + { + default: + iret = 0; + break; + case IDC_ET_RESET_ITEM_SIZE: + OnResetItemSize(); + break; + case IDC_ET_RESET_ALL_ITEM_SIZES: + OnResetAllItemSizes(); + break; + case IDC_ET_SELECTION_DELETE: + OnDeleteSelection( false ); + break; + case IDC_ET_SELECTION_EXCISE: + OnDeleteSelection( true ); + break; + case IDC_ET_SELECTION_COPY: + OnCopyColumn(); + break; + case IDC_ET_SELECTION_PASTE: + OnPasteColumn(); + break; + case IDC_ET_SORT_BY_USED: + OnSortByUsed(); + break; + case IDC_ET_SORT_BY_NAME: + OnSortByName(); + break; + case IDC_EXPORT_FA: + OnExportFlexAnimation(); + break; + case IDC_IMPORT_FA: + OnImportFlexAnimation(); + break; + case IDC_LOCK_TIMING_TAG: + LockTimingTag(); + break; + case IDC_UNLOCK_TIMING_TAG: + UnlockTimingTag(); + break; + case IDC_DELETE_TIMING_TAG: + DeleteFlexTimingTag( m_nClickedX, m_nClickedY ); + break; + case IDC_INSERT_TIMING_TAG: + AddFlexTimingTag( m_nClickedX ); + break; + case IDC_EXPANDALL: + m_pWorkspace->ExpandAll(); + break; + case IDC_COLLAPSEALL: + m_pWorkspace->CollapseAll( NULL ); + break; + case IDC_COLLAPSE_ALL_EXCEPT: + m_pWorkspace->CollapseAll( m_pWorkspace->GetClickedItem() ); + break; + case IDC_EXPANDVALID: + m_pWorkspace->ExpandValid(); + break; + case IDC_COPY_TO_FLEX: + OnCopyToFlex( true ); + break; + case IDC_COPY_FROM_FLEX: + OnCopyFromFlex( false ); + break; + case IDC_NEW_EXPRESSION_FROM_FLEXANIMATION: + OnNewExpression(); + break; + case IDC_UNDO_FA: + OnUndo(); + break; + case IDC_REDO_FA: + OnRedo(); + break; + case IDC_TL_EDITNORMAL: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + item->SetEditType( 0 ); + item->DrawSelf(); + } + } + break; + case IDC_TL_EDITLEFTRIGHT: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + item->SetEditType( 1 ); + item->DrawSelf(); + } + } + break; + case IDC_TL_EXPAND: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + item->SetCollapsed( false ); + } + LayoutItems(); + } + break; + case IDC_TL_COLLAPSE: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + item->SetCollapsed( true ); + } + LayoutItems(); + } + break; + case IDC_TL_ENABLE: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Enable item" ); + + item->SetActive( true ); + + g_pChoreoView->PushRedo( "Enable item" ); + + item->DrawSelf(); + } + } + break; + case IDC_TL_DISABLE: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Disable item" ); + + item->SetActive( false ); + + g_pChoreoView->PushRedo( "Disable item" ); + + item->DrawSelf(); + } + } + break; + case IDC_TL_COPY: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + item->Copy(); + } + } + break; + case IDC_TL_PASTE: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + item->Paste(); + item->DrawSelf(); + } + } + break; + case IDC_TL_DELETE: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + item->Delete(); + item->DrawSelf(); + } + } + break; + case IDC_TL_DESELECT: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + item->DeselectAll(); + item->DrawSelf(); + } + } + break; + case IDC_TL_SELECTALL: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + item->SelectAll(); + item->DrawSelf(); + } + } + break; + case IDC_DISABLE_ALL_EXCEPT: + { + m_pWorkspace->DisableAllExcept(); + } + break; + case IDC_ENABLE_ALL_VALID: + { + m_pWorkspace->EnableValid(); + } + break; + case IDC_TL_SNAPSELECTED: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Snap Selected" ); + + item->SnapSelected(); + + g_pChoreoView->PushRedo( "Snap Selected" ); + item->DrawSelf(); + } + } + break; + case IDC_TL_SNAPPOINTS: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Snap Item" ); + + item->SnapAll(); + + g_pChoreoView->PushRedo( "Snap Item" ); + item->DrawSelf(); + } + } + break; + case IDC_TL_DELETECOLUMN: + { + m_pWorkspace->OnDeleteColumn(); + } + break; + case IDC_TL_SNAPALL: + { + m_pWorkspace->OnSnapAll(); + } + break; + case IDC_FLEXHSCROLL: + { + int offset = 0; + bool processed = true; + + switch ( event->modifiers ) + { + case SB_THUMBTRACK: + offset = event->height; + break; + case SB_PAGEUP: + offset = m_pHorzScrollBar->getValue(); + offset -= 20; + offset = max( offset, m_pHorzScrollBar->getMinValue() ); + break; + case SB_PAGEDOWN: + offset = m_pHorzScrollBar->getValue(); + offset += 20; + offset = min( offset, m_pHorzScrollBar->getMaxValue() ); + break; + case SB_LINEUP: + offset = m_pHorzScrollBar->getValue(); + offset -= 10; + offset = max( offset, m_pHorzScrollBar->getMinValue() ); + break; + case SB_LINEDOWN: + offset = m_pHorzScrollBar->getValue(); + offset += 10; + offset = min( offset, m_pHorzScrollBar->getMaxValue() ); + break; + default: + processed = false; + break; + } + + if ( processed ) + { + MoveTimeSliderToPos( offset ); + } + } + break; + case IDC_FLEX_CHANGESCALE: + { + OnChangeScale(); + } + break; + case IDC_FLEX_SCALESAMPLES: + { + OnScaleSamples(); + } + break; + case IDC_ET_EDGEPROPERTIES: + { + OnEdgeProperties(); + } + break; + } + } + break; + case mxEvent::KeyDown: + case mxEvent::KeyUp: + { + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( item ) + { + iret = item->handleEvent( event ); + } + + if ( !iret ) + { + switch ( event->key ) + { + default: + break; + case VK_ESCAPE: + { + DeselectAll(); + iret = 1; + } + break; + } + } + } + break; + } + return iret; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : false - +//----------------------------------------------------------------------------- +void ExpressionTool::LayoutItems( bool force /*= false*/ ) +{ + m_pWorkspace->LayoutItems( force ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +//----------------------------------------------------------------------------- +void ExpressionTool::AddFlexTimingTag( int mx ) +{ + Assert( g_pChoreoView ); + + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + if ( event->GetType() != CChoreoEvent::FLEXANIMATION ) + { + Con_ErrorPrintf( "Timing Tag: Can only tag FLEXANIMATION events\n" ); + return; + } + + CInputParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Event Tag Name" ); + strcpy( params.m_szPrompt, "Name:" ); + + strcpy( params.m_szInputText, "" ); + + if ( !InputProperties( ¶ms ) ) + return; + + if ( strlen( params.m_szInputText ) <= 0 ) + { + Con_ErrorPrintf( "Timing Tag Name: No name entered!\n" ); + return; + } + + // Convert click to frac + float t = GetTimeValueForMouse( mx ); + float frac = 0.0f; + if ( event->GetDuration() ) + { + frac = t / event->GetDuration(); + frac = clamp( frac, 0.0f, 1.0f ); + } + + g_pChoreoView->SetDirty( true ); + + g_pChoreoView->PushUndo( "Add Timing Tag" ); + + event->AddTimingTag( params.m_szInputText, frac, true ); + + g_pChoreoView->PushRedo( "Add Timing Tag" ); + + // Redraw this window + m_pWorkspace->redraw(); + redraw(); +} + +void ExpressionTool::DeleteFlexTimingTag( int mx, int my ) +{ + Assert( g_pChoreoView ); + + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + CFlexTimingTag *tag = IsMouseOverTag( mx, my ); + if ( !tag ) + return; + + g_pChoreoView->SetDirty( true ); + + g_pChoreoView->PushUndo( "Delete Timing Tag" ); + + event->RemoveTimingTag( tag->GetName() ); + + g_pChoreoView->PushRedo( "Delete Timing Tag" ); + + LayoutItems( true ); + // Redraw this window + redraw(); + +} + +void ExpressionTool::LockTimingTag( void ) +{ + Assert( g_pChoreoView ); + + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + CFlexTimingTag *tag = IsMouseOverTag( m_nClickedX, m_nClickedY ); + if ( !tag ) + return; + + if ( tag->GetLocked() ) + return; + + g_pChoreoView->SetDirty( true ); + + g_pChoreoView->PushUndo( "Lock Timing Tag" ); + + tag->SetLocked( true ); + + g_pChoreoView->PushRedo( "Lock Timing Tag" ); + + redraw(); +} + +void ExpressionTool::UnlockTimingTag( void ) +{ + Assert( g_pChoreoView ); + + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + CFlexTimingTag *tag = IsMouseOverTag( m_nClickedX, m_nClickedY ); + if ( !tag ) + return; + + if ( !tag->GetLocked() ) + return; + + g_pChoreoView->SetDirty( true ); + + g_pChoreoView->PushUndo( "Unlock Timing Tag" ); + + tag->SetLocked( false ); + + g_pChoreoView->PushRedo( "Unlock Timing Tag" ); + + redraw(); +} + +void ExpressionTool::ApplyBounds( int& mx, int& my ) +{ + if ( !m_bUseBounds ) + return; + + mx = clamp( mx, m_nMinX, m_nMaxX ); +} + +void ExpressionTool::CalcBounds( int movetype ) +{ + switch ( movetype ) + { + default: + case DRAGTYPE_NONE: + m_bUseBounds = false; + m_nMinX = 0; + m_nMaxX = 0; + break; + case DRAGTYPE_SCRUBBER: + m_bUseBounds = true; + m_nMinX = 0; + m_nMaxX = w2(); + break; + case DRAGTYPE_FLEXTIMINGTAG: + { + m_bUseBounds = true; + + int left, right; + GetWorkspaceLeftRight( left, right ); + + m_nMinX = left; + m_nMaxX = right; + + RECT rcClient; + rcClient.left = left; + rcClient.right = right; + rcClient.top = 0; + rcClient.bottom = TRAY_HEIGHT; + + CFlexTimingTag *tag = IsMouseOverTag( m_nStartX, m_nStartY ); + if ( tag && + tag->GetOwner() ) + { + CChoreoEvent *e = tag->GetOwner(); + + float st = e->GetStartTime(); + float ed = e->GetEndTime(); + + if ( ed > st ) + { + + + // Find previous tag, if any + CFlexTimingTag *prev = NULL; + CFlexTimingTag *next = NULL; + + for ( int i = 0; i < e->GetNumTimingTags(); i++ ) + { + CFlexTimingTag *test = e->GetTimingTag( i ); + if ( test != tag ) + continue; + + // Found it + if ( i > 0 ) + { + prev = e->GetTimingTag( i - 1 ); + } + + if ( i + 1 < e->GetNumTimingTags() ) + { + next = e->GetTimingTag( i + 1 ); + } + break; + } + + if ( prev ) + { + // Compute x pixel of prev tag + float frac = ( prev->GetStartTime() - st ) / ( ed - st ); + if ( frac >= 0.0f && frac <= 1.0f ) + { + int tagx = rcClient.left + (int)( frac * (float)( rcClient.right - rcClient.left ) ); + + m_nMinX = max( m_nMinX, tagx + 5 ); + } + } + + if ( next ) + { + // Compute x pixel of next tag + float frac = ( next->GetStartTime() - st ) / ( ed - st ); + if ( frac >= 0.0f && frac <= 1.0f ) + { + int tagx = rcClient.left + (int)( frac * (float)( rcClient.right - rcClient.left ) ); + m_nMaxX = min( m_nMaxX, tagx - 5 ); + } + } + } + + } + } + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *tag - +// newposition - +//----------------------------------------------------------------------------- +void ExpressionTool::ResampleControlPoints( CFlexTimingTag *tag, float newposition ) +{ + CChoreoEvent *e = tag->GetOwner(); + if ( !e ) + return; + + float duration = e->GetDuration(); + + float leftedge = 0.0f; + float rightedge = duration; + + // Find neighboring locked tags, if any + CFlexTimingTag *prev = NULL; + CFlexTimingTag *next = NULL; + + int i; + for ( i = 0; i < e->GetNumTimingTags(); i++ ) + { + CFlexTimingTag *test = e->GetTimingTag( i ); + if ( test != tag ) + continue; + + // Found it + if ( i > 0 ) + { + int i1 = i - 1; + while ( 1 ) + { + if ( i1 < 0 ) + { + prev = NULL; + break; + } + + prev = e->GetTimingTag( i1 ); + if ( prev->GetLocked() ) + break; + + i1--; + } + } + + if ( i + 1 < e->GetNumTimingTags() ) + { + int i1 = i + 1; + while ( 1 ) + { + if ( i1 >= e->GetNumTimingTags() ) + { + next = NULL; + break; + } + + next = e->GetTimingTag( i1 ); + if ( next->GetLocked() ) + break; + + i1++; + } + } + break; + } + + if ( prev ) + { + leftedge = prev->GetPercentage() * duration; + } + + if ( next ) + { + rightedge = next->GetPercentage() * duration; + } + + // Now, using the tags old position as a pivot, rescale intervening + // sample points based on size delta of new vs old range + float oldpivot = tag->GetPercentage() * duration; + float newpivot = newposition * duration; + + float oldleftrange = oldpivot - leftedge; + float oldrightrange = rightedge - oldpivot; + + float newleftrange = newpivot - leftedge; + float newrightrange = rightedge - newpivot; + + if ( oldleftrange <= 0.0f || + oldrightrange <= 0.0f || + newleftrange <= 0.0f || + newrightrange <= 0.0f ) + { + Con_Printf( "Range problem!!! avoiding division by zero\n" ); + return; + } + + for ( i = 0 ; i < e->GetNumFlexAnimationTracks(); i++ ) + { + CFlexAnimationTrack *track = e->GetFlexAnimationTrack( i ); + if ( !track ) + continue; + + for ( int t = 0; t < ( track->IsComboType() ? 2 : 1 ); t++ ) + { + for ( int j = 0; j < track->GetNumSamples( t ); j++ ) + { + CExpressionSample *s = track->GetSample( j, t ); + if ( !s ) + continue; + + float oldtime = s->time; + + // In old range? + if ( oldtime < leftedge ) + continue; + if ( oldtime > rightedge ) + continue; + + // In left or right side( tiebreak toward left ) + float newtime = oldtime; + + if ( oldtime <= oldpivot ) + { + float n = ( oldtime - leftedge ) / oldleftrange; + newtime = leftedge + n * newleftrange; + } + else + { + float n = ( oldtime - oldpivot ) / oldrightrange; + newtime = newpivot + n * newrightrange; + } + + //newtime = FacePoser_SnapTime( newtime ); + + s->time = newtime; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ExpressionTool::OnNewExpression( void ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return; + + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if ( !hdr ) + { + Con_ErrorPrintf( "ExpressionTool::OnNewExpression: Can't create new face pose, must load a model first!\n" ); + return; + } + + CExpClass *active = expressions->GetActiveClass(); + if ( !active ) + { + Con_ErrorPrintf( "ExpressionTool::OnNewExpression: Can't create new face pose, must load an expression file first!\n" ); + return; + } + + g_pExpressionTrayTool->Deselect(); + + float t = GetTimeValueForMouse( m_nClickedX ); + + // Get spline intensity for controller + float faketime = e->GetStartTime() + t; + + float settings[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ]; + float weights[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ]; + memset( settings, 0, sizeof( settings ) ); + memset( weights, 0, sizeof( settings ) ); + + for ( int i = 0 ; i < e->GetNumFlexAnimationTracks(); i++ ) + { + CFlexAnimationTrack *track = e->GetFlexAnimationTrack( i ); + if ( !track ) + continue; + + // Disabled + if ( !track->IsTrackActive() ) + continue; + + // Map track flex controller to global name + if ( track->IsComboType() ) + { + for ( int side = 0; side < 2; side++ ) + { + int controller = track->GetFlexControllerIndex( side ); + if ( controller != -1 ) + { + // Get spline intensity for controller + float flIntensity = track->GetIntensity( faketime, side ); + + settings[ controller ] = flIntensity; + weights[ controller ] = 1.0f; + } + } + } + else + { + int controller = track->GetFlexControllerIndex( 0 ); + if ( controller != -1 ) + { + // Get spline intensity for controller + float flIntensity = track->GetIntensity( faketime, 0 ); + + settings[ controller ] = flIntensity; + weights[ controller ] = 1.0f; + } + } + } + + CExpressionParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Add Expression" ); + strcpy( params.m_szName, "" ); + strcpy( params.m_szDescription, "" ); + + if ( !ExpressionProperties( ¶ms ) ) + return; + + if ( ( strlen( params.m_szName ) <= 0 ) || + !stricmp( params.m_szName, "unnamed" ) ) + { + Con_ErrorPrintf( "You must type in a valid name\n" ); + return; + } + + if ( ( strlen( params.m_szDescription ) <= 0 ) || + !stricmp( params.m_szDescription, "description" ) ) + { + Con_ErrorPrintf( "You must type in a valid description\n" ); + return; + } + + active->AddExpression( params.m_szName, params.m_szDescription, settings, weights, true, true ); +} + +LocalFlexController_t FindFlexControllerIndexByName( StudioModel *model, char const *searchname ) +{ + if ( !model ) + return LocalFlexController_t(-1); + + CStudioHdr *hdr = model->GetStudioHdr(); + if ( !hdr ) + return LocalFlexController_t(-1); + + for ( LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++ ) + { + char const *name = hdr->pFlexcontroller( i )->pszName(); + if ( !name ) + continue; + + if ( strcmp( name, searchname ) ) + continue; + + return i; + } + return LocalFlexController_t(-1); +} + +void ExpressionTool::OnCopyToFlex( bool isEdited ) +{ + // local time in the expression tool for the last mouse click + float t = GetTimeValueForMouse( m_nClickedX ); + + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return; + + float scenetime = e->GetStartTime() + t; + + OnCopyToFlex( scenetime, isEdited ); + + return; +} + + +void ExpressionTool::OnCopyToFlex( float scenetime, bool isEdited ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return; + + if ( scenetime < e->GetStartTime() || scenetime > e->GetEndTime() ) + return; + + bool needundo = false; + + float *settings = NULL; + float *weights = NULL; + CExpression *exp = NULL; + CExpClass *active = expressions->GetActiveClass(); + if ( active ) + { + + int index = active->GetSelectedExpression(); + if ( index != -1 ) + { + exp = active->GetExpression( index ); + if ( exp ) + { + needundo = true; + settings = exp->GetSettings(); + weights = exp->GetWeights(); + } + } + } + + if ( needundo && exp ) + { + exp->PushUndoInformation(); + active->SetDirty( true ); + } + + g_pFlexPanel->ResetSliders( false, true ); + + StudioModel *model = models->GetActiveStudioModel(); + + for ( int i = 0 ; i < e->GetNumFlexAnimationTracks(); i++ ) + { + CFlexAnimationTrack *track = e->GetFlexAnimationTrack( i ); + if ( !track ) + continue; + + // Disabled + if ( !track->IsTrackActive() ) + continue; + + // Map track flex controller to global name + for ( int side = 0; side < 1 + track->IsComboType(); side++ ) + { + int controller = track->GetFlexControllerIndex( side ); + if ( controller != -1 ) + { + // Get spline intensity for controller + float flIntensity = track->GetIntensity( scenetime, side ); + + g_pFlexPanel->SetSlider( controller, flIntensity ); + g_pFlexPanel->SetInfluence( controller, 1.0f ); + g_pFlexPanel->SetEdited( controller, isEdited ); + if( model ) + { + LocalFlexController_t raw = track->GetRawFlexControllerIndex( side ); + if ( raw != LocalFlexController_t(-1) ) + { + model->SetFlexController( raw, flIntensity ); + } + } + if ( settings && weights ) + { + settings[ controller ] = flIntensity; + weights[ controller ] = 1.0f; + } + } + } + } + + if ( needundo && exp ) + { + exp->PushRedoInformation(); + } +} + +void ExpressionTool::OnCopyFromFlex( bool isEdited ) +{ + // local time in the expression tool for the last mouse click + float t = GetTimeValueForMouse( m_nClickedX ); + + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return; + + float scenetime = e->GetStartTime() + t; + + OnCopyFromFlex( scenetime, isEdited ); + + return; +} + +void ExpressionTool::OnSetSingleKeyFromFlex( char const *sliderName ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e || !e->GetDuration() ) + return; + + float scenetime = g_pChoreoView->GetScene()->GetTime(); + + if ( scenetime < e->GetStartTime() || scenetime > e->GetEndTime() ) + return; + + scenetime = FacePoser_SnapTime( scenetime ); + + float relativetime = scenetime - e->GetStartTime(); + + // Get spline intensity for controller + + float setting; + float influence; + float minvalue, maxvalue; + + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Set Single Key" ); + + for (int j = 0; j < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; j++) + { + if ( !g_pFlexPanel->IsValidSlider( j ) ) + continue; + + if ( Q_stricmp( g_pFlexPanel->getLabel(), sliderName ) ) + continue; + + setting = g_pFlexPanel->GetSliderRawValue( j ); + influence = g_pFlexPanel->GetInfluence( j ); + + // g_pFlexPanel->SetEdited( j, isEdited ); + + g_pFlexPanel->GetSliderRange( j, minvalue, maxvalue ); + + bool found = false; + for ( int i = 0 ; i < e->GetNumFlexAnimationTracks() && !found; i++ ) + { + CFlexAnimationTrack *track = e->GetFlexAnimationTrack( i ); + if ( !track ) + continue; + + for ( int side = 0; side < 1 + track->IsComboType(); side++ ) + { + if ( track->GetFlexControllerIndex( side ) != j ) + continue; + + float normalized = setting; + if ( side == 0 ) + { + if ( minvalue != maxvalue ) + { + normalized = ( setting - minvalue ) / ( maxvalue - minvalue ); + } + if (track->IsInverted()) + { + normalized = 1.0 - normalized; + } + } + + found = true; + + int nSampleCount = track->GetNumSamples( side ); + + int j = 0; + for ( ; j < nSampleCount; ++j ) + { + CExpressionSample *s = track->GetSample( j, side ); + if ( s->time == relativetime ) + break; + } + + if ( j >= nSampleCount ) + { + track->AddSample( relativetime, normalized, side ); + track->Resort( side ); + } + else + { + CExpressionSample *s = track->GetSample( j, side ); + s->value = normalized; + } + + track->SetTrackActive( true ); + + break; + } + } + } + + g_pChoreoView->PushRedo( "Set Single Key" ); + + m_pWorkspace->redraw(); + redraw(); +} + +void ExpressionTool::OnCopyFromFlex( float scenetime, bool isEdited ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e || !e->GetDuration() ) + return; + + if ( scenetime < e->GetStartTime() || scenetime > e->GetEndTime() ) + return; + + scenetime = FacePoser_SnapTime( scenetime ); + + float relativetime = scenetime - e->GetStartTime(); + + // Get spline intensity for controller + + float setting; + float influence; + float minvalue, maxvalue; + + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Copy from Flex" ); + + for (int j = 0; j < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; j++) + { + if ( !g_pFlexPanel->IsValidSlider( j ) ) + continue; + + setting = g_pFlexPanel->GetSliderRawValue( j ); + //setting = g_pFlexPanel->GetSlider( j ); + influence = g_pFlexPanel->GetInfluence( j ); + + g_pFlexPanel->SetEdited( j, isEdited ); + + g_pFlexPanel->GetSliderRange( j, minvalue, maxvalue ); + + // Found it + if ( !influence ) + { + continue; + } + + bool found = false; + for ( int i = 0 ; i < e->GetNumFlexAnimationTracks() && !found; i++ ) + { + CFlexAnimationTrack *track = e->GetFlexAnimationTrack( i ); + if ( !track ) + continue; + + for ( int side = 0; side < 1 + track->IsComboType(); side++ ) + { + if ( track->GetFlexControllerIndex( side ) != j ) + continue; + + float normalized = setting; + if ( side == 0 ) + { + if ( minvalue != maxvalue ) + { + normalized = ( setting - minvalue ) / ( maxvalue - minvalue ); + } + if (track->IsInverted()) + { + normalized = 1.0 - normalized; + } + } + + found = true; + + track->AddSample( relativetime, normalized, side ); + track->Resort( side ); + track->SetTrackActive( true ); + + break; + } + } + } + + g_pChoreoView->PushRedo( "Copy from Flex" ); + + m_pWorkspace->redraw(); + redraw(); +} + +bool ExpressionTool::SetFlexAnimationTrackFromExpression( int mx, int my, CExpClass *cl, CExpression *exp ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e | !e->GetDuration() ) + { + return false; + } + + if ( !exp ) + { + return false; + } + + // Convert screen to client + POINT pt; + pt.x = mx; + pt.y = my; + + ScreenToClient( (HWND)getHandle(), &pt ); + + if ( pt.x < 0 || pt.y < 0 ) + { + return false; + } + + if ( pt.x > w2() || pt.y > h2() ) + { + return false; + } + + float t = GetTimeValueForMouse( (short)pt.x ); + + // Get spline intensity for controller + // Get spline intensity for controller + float relativetime = t; + float faketime = e->GetStartTime() + relativetime; + + faketime = FacePoser_SnapTime( faketime ); + + float *settings = exp->GetSettings(); + float *influence = exp->GetWeights(); + + if ( !settings || !influence ) + return false; + + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Copy from Expression" ); + + for ( int i = 0 ; i < e->GetNumFlexAnimationTracks(); i++ ) + { + CFlexAnimationTrack *track = e->GetFlexAnimationTrack( i ); + if ( !track ) + continue; + + if ( track->IsComboType() ) + { + int left = track->GetFlexControllerIndex( 0 ); + int right = track->GetFlexControllerIndex( 1 ); + + float leftval = settings[ left ]; + float leftinfluence = influence[ left ]; + float rightval = settings[ right ]; + float rightinfluence = influence[ right ]; + + if ( leftinfluence || rightinfluence ) + { + + //Con_Printf( "%s %i(side %i): amount %f inf %f\n", track->GetFlexControllerName(), j, side, s, inf ); + + float mag, leftright; + + if (leftval < rightval) + { + mag = rightval; + leftright = 1.0 - (leftval / rightval) * 0.5; + } + else if (leftval > rightval) + { + mag = leftval; + leftright = (rightval / leftval) * 0.5; + } + else + { + mag = leftval; + leftright = 0.5; + } + + track->AddSample( relativetime, mag * leftinfluence, 0 ); + track->AddSample( relativetime, leftright, 1 ); + + track->Resort( 0 ); + track->Resort( 1 ); + + track->SetTrackActive( true ); + } + } + else + { + int j = track->GetFlexControllerIndex( 0 ); + + float s = settings[ j ]; + float inf = influence[ j ]; + + if ( inf ) + { + track->AddSample( relativetime, s, 0 ); + + track->Resort( 0 ); + + track->SetTrackActive( true ); + } + } + } + + g_pChoreoView->PushRedo( "Copy from Expression" ); + + m_pWorkspace->redraw(); + redraw(); + + return true; +} + +bool ExpressionTool::PaintBackground() +{ + redraw(); + return false; +} + +void ExpressionTool::OnExportFlexAnimation( void ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + // Create flexanimations dir + CreatePath( "flexanimations/foo" ); + + char fafilename[ 512 ]; + if ( !FacePoser_ShowSaveFileNameDialog( fafilename, sizeof( fafilename ), "flexanimations", "*.vfa" ) ) + { + return; + } + + Q_DefaultExtension( fafilename, ".vfa", sizeof( fafilename ) ); + + Con_Printf( "Exporting events to %s\n", fafilename ); + + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + + CChoreoScene::FileSaveFlexAnimations( buf, 0, event ); + + // Write it out baby + FileHandle_t fh = filesystem->Open( fafilename, "wt" ); + if (fh) + { + filesystem->Write( buf.Base(), buf.TellPut(), fh ); + filesystem->Close(fh); + } + else + { + Con_Printf( "Unable to write file %s!!!\n", fafilename ); + } +} + +void ExpressionTool::OnImportFlexAnimation( void ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return; + + char fafilename[ 512 ]; + if ( !FacePoser_ShowOpenFileNameDialog( fafilename, sizeof( fafilename ), "flexanimations", "*.vfa" ) ) + { + return; + } + + if ( !filesystem->FileExists( fafilename ) ) + return; + + char fullpath[ 512 ]; + filesystem->RelativePathToFullPath( fafilename, "MOD", fullpath, sizeof( fullpath ) ); + + LoadScriptFile( (char *)fullpath ); + + tokenprocessor->GetToken( true ); + if ( stricmp( tokenprocessor->CurrentToken(), "flexanimations" ) ) + { + Con_Printf( "ExpressionTool::OnImportFlexAnimation: %s, expecting \"flexanimations\"\n", + fullpath ); + } + else + { + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Import flex animations" ); + + CChoreoScene::ParseFlexAnimations( tokenprocessor, event, true ); + + // Force a full reset + m_pLastEvent = NULL; + SetEvent( event ); + + g_pChoreoView->PushRedo( "Import flex animations" ); + + Con_Printf( "Parsed flex animations from %s\n", fullpath ); + } +} + +void ExpressionTool::OnUndo( void ) +{ + g_pChoreoView->Undo(); +} + +void ExpressionTool::OnRedo( void ) +{ + g_pChoreoView->Redo(); +} + +void ExpressionTool::ForceScrubPositionFromSceneTime( float scenetime ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e || !e->GetDuration() ) + return; + + float t = scenetime - e->GetStartTime(); + m_flScrub = t; + m_flScrubTarget = t; + + DrawScrubHandles(); +} + +void ExpressionTool::ForceScrubPosition( float frac ) +{ + m_flScrub = frac; + m_flScrubTarget = frac; + + CChoreoEvent *e = GetSafeEvent(); + if ( e ) + { + float realtime = e->GetStartTime() + frac; + + g_pChoreoView->SetScrubTime( realtime ); + g_pChoreoView->SetScrubTargetTime( realtime ); + + g_pChoreoView->DrawScrubHandle(); + } + + DrawScrubHandles(); +} + +void ExpressionTool::DrawScrubHandles() +{ + RECT rcHandle; + GetScrubHandleRect( rcHandle, true ); + + RECT rcTray = rcHandle; + rcTray.left = 0; + rcTray.right = w2(); + + CChoreoWidgetDrawHelper drawHelper( this, rcTray ); + DrawScrubHandle( drawHelper, rcHandle ); +} + +void ExpressionTool::SetMouseOverPos( int x, int y ) +{ + m_nMousePos[ 0 ] = x; + m_nMousePos[ 1 ] = y; +} + +void ExpressionTool::GetMouseOverPos( int &x, int& y ) +{ + x = m_nMousePos[ 0 ]; + y = m_nMousePos[ 1 ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rcPos - +//----------------------------------------------------------------------------- +void ExpressionTool::GetMouseOverPosRect( RECT& rcPos ) +{ + rcPos.top = GetCaptionHeight() + 12; + rcPos.left = w2() - 200; + rcPos.right = w2() - 5; + rcPos.bottom = rcPos.top + 13; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rcPos - +//----------------------------------------------------------------------------- +void ExpressionTool::DrawMouseOverPos( CChoreoWidgetDrawHelper& drawHelper, RECT& rcPos ) +{ + // Compute time for pixel x + float t = GetTimeValueForMouse( m_nMousePos[ 0 ] ); + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return; + + t += e->GetStartTime(); + + float snapped = FacePoser_SnapTime( t ); + + // Found it, write out description + // + char sz[ 128 ]; + if ( t != snapped ) + { + Q_snprintf( sz, sizeof( sz ), "%s", FacePoser_DescribeSnappedTime( t ) ); + } + else + { + Q_snprintf( sz, sizeof( sz ), "%.3f", t ); + } + + int len = drawHelper.CalcTextWidth( "Arial", 11, 900, sz ); + + RECT rcText = rcPos; + rcText.left = max( rcPos.left, rcPos.right - len ); + + drawHelper.DrawColoredText( "Arial", 11, 900, RGB( 255, 50, 70 ), rcText, sz ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ExpressionTool::DrawMouseOverPos() +{ + RECT rcPos; + GetMouseOverPosRect( rcPos ); + + CChoreoWidgetDrawHelper drawHelper( this, rcPos ); + DrawMouseOverPos( drawHelper, rcPos ); +} + +int ExpressionTool::CountSelectedSamples( void ) +{ + return m_pWorkspace->CountSelectedSamples(); +} + +void ExpressionTool::MoveSelectedSamples( float dfdx, float dfdy, bool snap ) +{ + m_pWorkspace->MoveSelectedSamples( dfdx, dfdy, snap ); +} + +void ExpressionTool::DeleteSelectedSamples( void ) +{ + m_pWorkspace->DeleteSelectedSamples(); +} + +void ExpressionTool::DeselectAll( void ) +{ + m_pWorkspace->DeselectAll(); + m_bSelectionActive = false; + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : start - +// end - +//----------------------------------------------------------------------------- +void ExpressionTool::SelectPoints( float starttime, float endtime ) +{ + // Make sure order is correct + if ( endtime < starttime ) + { + float temp = endtime; + endtime = starttime; + starttime = temp; + } + + DeselectAll(); + + m_flSelection[ 0 ] = starttime; + m_flSelection[ 1 ] = endtime; + m_bSelectionActive = true; + + // Select any words that span the selection + // + m_pWorkspace->SelectPoints( starttime, endtime ); + + redraw(); +} + +void ExpressionTool::FinishMoveSelection( int startx, int mx ) +{ + float start = GetTimeValueForMouse( startx ); + float end = GetTimeValueForMouse( mx ); + + float delta = end - start; + + for ( int i = 0; i < 2; i++ ) + { + m_flSelection[ i ] += delta; + } + + SelectPoints( m_flSelection[ 0 ], m_flSelection[ 1 ] ); + + redraw(); +} + +void ExpressionTool::FinishMoveSelectionStart( int startx, int mx ) +{ + float start = GetTimeValueForMouse( startx ); + float end = GetTimeValueForMouse( mx ); + + float delta = end - start; + + m_flSelection[ 0 ] += delta; + + SelectPoints( m_flSelection[ 0 ], m_flSelection[ 1 ] ); + + redraw(); +} + +void ExpressionTool::FinishMoveSelectionEnd( int startx, int mx ) +{ + float start = GetTimeValueForMouse( startx ); + float end = GetTimeValueForMouse( mx ); + + float delta = end - start; + + m_flSelection[ 1 ] += delta; + + SelectPoints( m_flSelection[ 0 ], m_flSelection[ 1 ] ); + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : startx - +// mx - +//----------------------------------------------------------------------------- +void ExpressionTool::FinishSelect( int startx, int mx ) +{ + // Don't select really small areas + if ( abs( startx - mx ) < 1 ) + return; + + float start = GetTimeValueForMouse( startx ); + float end = GetTimeValueForMouse( mx ); + + SelectPoints( start, end ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ExpressionTool::IsMouseOverPoints( int mx, int my ) +{ + RECT rc; + GetWorkspaceRect( rc ); + + // Over tag + if ( my > TRAY_HEIGHT ) + return false; + + if ( my <= 12 + GetCaptionHeight() ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ExpressionTool::IsMouseOverSelection( int mx, int my ) +{ + if ( !m_bSelectionActive ) + return false; + + if ( !IsMouseOverPoints( mx, my ) ) + return false; + + float t = GetTimeValueForMouse( mx ); + + if ( t >= m_flSelection[ 0 ] && + t <= m_flSelection[ 1 ] ) + { + return true; + } + + return false; +} + +bool ExpressionTool::IsMouseOverSelectionStartEdge( mxEvent *event ) +{ + int mx, my; + mx = (short)event->x; + my = (short)event->y; + + if ( !(event->modifiers & mxEvent::KeyCtrl ) ) + return false; + + if ( !IsMouseOverSelection( mx, my ) ) + return false; + + int left; + + left = GetPixelForTimeValue( m_flSelection[ 0 ] ); + + if ( abs( left - mx ) <= 2 ) + { + return true; + } + + return false; +} + +bool ExpressionTool::IsMouseOverSelectionEndEdge( mxEvent *event ) +{ + int mx, my; + mx = (short)event->x; + my = (short)event->y; + + if ( !(event->modifiers & mxEvent::KeyCtrl ) ) + return false; + + if ( !IsMouseOverSelection( mx, my ) ) + return false; + + int right; + + right = GetPixelForTimeValue( m_flSelection[ 1 ] ); + + if ( abs( right - mx ) <= 2 ) + { + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &rc - +//----------------------------------------------------------------------------- +void ExpressionTool::GetWorkspaceRect( RECT &rc ) +{ + GetClientRect( (HWND)getHandle(), &rc ); + + rc.top = TRAY_HEIGHT - 17; + rc.bottom = TRAY_HEIGHT - 1; + //InflateRect( &rc, -1, -1 ); +} + +void ExpressionTool::AddFocusRect( RECT& rc ) +{ + RECT rcFocus = rc; + + POINT offset; + offset.x = 0; + offset.y = 0; + ClientToScreen( (HWND)getHandle(), &offset ); + OffsetRect( &rcFocus, offset.x, offset.y ); + + // Convert to screen space? + CFocusRect fr; + fr.m_rcFocus = rcFocus; + fr.m_rcOrig = rcFocus; + + m_FocusRects.AddToTail( fr ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int ExpressionTool::ComputeHPixelsNeeded( void ) +{ + CChoreoEvent *event = GetSafeEvent(); + if ( !event ) + return 0; + + int pixels = 0; + float maxtime = event->GetDuration(); + pixels = (int)( ( maxtime + 5.0 ) * GetPixelsPerSecond() + 10 ); + + return pixels; + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ExpressionTool::RepositionHSlider( void ) +{ + int pixelsneeded = ComputeHPixelsNeeded(); + + if ( pixelsneeded <= w2() ) + { + m_pHorzScrollBar->setVisible( false ); + } + else + { + m_pHorzScrollBar->setVisible( true ); + } + m_pHorzScrollBar->setBounds( 0, h2() - m_nScrollbarHeight, w2(), m_nScrollbarHeight ); + + m_flLeftOffset = max( 0.f, m_flLeftOffset ); + m_flLeftOffset = min( (float)pixelsneeded, m_flLeftOffset ); + + m_pHorzScrollBar->setRange( 0, pixelsneeded ); + m_pHorzScrollBar->setValue( m_flLeftOffset ); + m_pHorzScrollBar->setPagesize( w2() ); + + m_nLastHPixelsNeeded = pixelsneeded; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float ExpressionTool::GetPixelsPerSecond( void ) +{ + return m_flPixelsPerSecond * (float)g_pChoreoView->GetTimeZoom( GetToolName() ) / 100.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : x - +//----------------------------------------------------------------------------- +void ExpressionTool::MoveTimeSliderToPos( int x ) +{ + m_flLeftOffset = x; + m_pHorzScrollBar->setValue( m_flLeftOffset ); + InvalidateRect( (HWND)m_pHorzScrollBar->getHandle(), NULL, TRUE ); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ExpressionTool::InvalidateLayout( void ) +{ + if ( m_bSuppressLayout ) + return; + + if ( ComputeHPixelsNeeded() != m_nLastHPixelsNeeded ) + { + RepositionHSlider(); + } + + m_bLayoutIsValid = false; + m_pWorkspace->redraw(); + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : time - +// *clipped - +// Output : int +//----------------------------------------------------------------------------- +int ExpressionTool::GetPixelForTimeValue( float time, bool *clipped /*=NULL*/ ) +{ + int left, right; + + GetWorkspaceLeftRight( left, right ); + + if ( clipped ) + { + *clipped = false; + } + + float st, ed; + GetStartAndEndTime( st, ed ); + + float frac = ( time - st ) / ( ed - st ); + if ( frac < 0.0 || frac > 1.0 ) + { + if ( clipped ) + { + *clipped = true; + } + } + + int pixel = left + ( int )( frac * (right - left ) ); + return pixel; +} + +void ExpressionTool::OnChangeScale( void ) +{ + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( !scene ) + { + return; + } + + // Zoom time in / out + CInputParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Change Zoom" ); + strcpy( params.m_szPrompt, "New scale (e.g., 2.5x):" ); + + Q_snprintf( params.m_szInputText, sizeof( params.m_szInputText ), "%.2f", (float)g_pChoreoView->GetTimeZoom( GetToolName() ) / 100.0f ); + + if ( !InputProperties( ¶ms ) ) + return; + + g_pChoreoView->SetTimeZoom( GetToolName(), clamp( (int)( 100.0f * atof( params.m_szInputText ) ), 1, MAX_TIME_ZOOM ), false ); + + m_nLastHPixelsNeeded = -1; + InvalidateLayout(); + Con_Printf( "Zoom factor %i %%\n", g_pChoreoView->GetTimeZoom( GetToolName() ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : st - +// ed - +//----------------------------------------------------------------------------- +void ExpressionTool::GetStartAndEndTime( float& st, float& ed ) +{ + st = m_flLeftOffset / GetPixelsPerSecond(); + int left, right; + GetWorkspaceLeftRight( left, right ); + if ( right <= left ) + { + ed = st; + } + else + { + ed = st + (float)( right - left ) / GetPixelsPerSecond(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : float +//----------------------------------------------------------------------------- +float ExpressionTool::GetEventEndTime() +{ + CChoreoEvent *ev = GetSafeEvent(); + if ( !ev ) + return 1.0f; + + return ev->GetDuration(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// clip - +// Output : float +//----------------------------------------------------------------------------- +float ExpressionTool::GetTimeValueForMouse( int mx, bool clip /*=false*/) +{ + int left, right; + + GetWorkspaceLeftRight( left, right ); + + float st, ed; + GetStartAndEndTime( st, ed ); + + if ( clip ) + { + if ( mx < 0 ) + { + return st; + } + if ( mx > w2() ) + { + return ed; + } + } + + float frac = (float)( mx - left ) / (float)( right - left ); + return st + frac * ( ed - st ); +} + +void ExpressionTool::DrawEventEnd( CChoreoWidgetDrawHelper& drawHelper ) +{ + CChoreoEvent *e = GetSafeEvent(); + if ( !e ) + return; + + float duration = e->GetDuration(); + if ( !duration ) + return; + + int leftx = GetPixelForTimeValue( duration ); + if ( leftx >= w2() ) + return; + + RECT rcClient; + drawHelper.GetClientRect( rcClient ); + + drawHelper.DrawColoredLine( + COLOR_CHOREO_ENDTIME, PS_SOLID, 1, + leftx, rcClient.top + TRAY_HEIGHT, leftx, rcClient.bottom ); + +} + +void ExpressionTool::OnSortByUsed( void ) +{ + m_pWorkspace->OnSortByUsed(); +} + +void ExpressionTool::OnSortByName( void ) +{ + m_pWorkspace->OnSortByName(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *item - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ExpressionTool::IsFocusItem( TimelineItem *item ) +{ + return m_pWorkspace->GetClickedItem() == item; +} + +//----------------------------------------------------------------------------- +// Purpose: Delete a vertical column of samples between the selection +// markers. If excise_time is true, shifts remaining samples left +// Input : excise_time - +//----------------------------------------------------------------------------- +void ExpressionTool::OnDeleteSelection( bool excise_time ) +{ + if ( !m_bSelectionActive ) + return; + + // Force selection of everything again! + SelectPoints( m_flSelection[ 0 ], m_flSelection[ 1 ] ); + + int i, t; + + char const *undotext = excise_time ? "Excise column" : "Delete column"; + + float shift_left_time = m_flSelection[ 1 ] - m_flSelection[ 0 ]; + Assert( shift_left_time > 0.0f ); + + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( undotext ); + + for ( int controller = 0; controller < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; controller++ ) + { + TimelineItem *item = m_pWorkspace->GetItem( controller ); + if ( !item ) + continue; + + CFlexAnimationTrack *track = item->GetSafeTrack(); + if ( !track ) + continue; + + for ( t = 0; t < 2; t++ ) + { + for ( i = track->GetNumSamples( t ) - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = track->GetSample( i, t ); + if ( !sample->selected ) + continue; + + track->RemoveSample( i, t ); + } + + if ( !excise_time ) + continue; + + + // Now shift things after m_flSelection[0] to the left + for ( i = track->GetNumSamples( t ) - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = track->GetSample( i, t ); + if ( sample->time < m_flSelection[ 1 ] ) + continue; + + // Shift it + sample->time -= shift_left_time; + } + } + + item->DrawSelf(); + } + + g_pChoreoView->PushRedo( undotext ); + + // Clear selection and redraw() + DeselectAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ExpressionTool::OnResetItemSize() +{ + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( !item ) + return; + + item->ResetHeight(); + m_pWorkspace->LayoutItems( true ); + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ExpressionTool::OnResetAllItemSizes() +{ + for ( int controller = 0; controller < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; controller++ ) + { + TimelineItem *item = m_pWorkspace->GetItem( controller ); + if ( !item ) + continue; + item->ResetHeight(); + } + + m_pWorkspace->LayoutItems( true ); + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ExpressionTool::OnScaleSamples() +{ + int t, i; + + //Scale samples + CInputParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Scale selected samples" ); + strcpy( params.m_szPrompt, "Factor:" ); + strcpy( params.m_szInputText, "1.0" ); + + if ( !InputProperties( ¶ms ) ) + return; + + float scale_factor = atof( params.m_szInputText ); + if( scale_factor <= 0.0f ) + { + Con_Printf( "Can't scale to %.2f\n", scale_factor ); + } + + char const *undotext = "Scale samples"; + + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( undotext ); + + for ( int controller = 0; controller < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; controller++ ) + { + TimelineItem *item = m_pWorkspace->GetItem( controller ); + if ( !item ) + continue; + + CFlexAnimationTrack *track = item->GetSafeTrack(); + if ( !track ) + continue; + + for ( t = 0; t < 2; t++ ) + { + for ( i = track->GetNumSamples( t ) - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = track->GetSample( i, t ); + if ( !sample->selected ) + continue; + + // Scale it + float curvalue = sample->value; + curvalue *= scale_factor; + // Clamp it + curvalue = clamp( curvalue, 0.0f, 1.0f ); + sample->value = curvalue; + } + } + } + + g_pChoreoView->PushRedo( undotext ); + + m_pWorkspace->redraw(); + redraw(); +} + +void ExpressionTool::OnModelChanged() +{ + SetEvent( NULL ); + redraw(); +} + +void ExpressionTool::OnEdgeProperties() +{ + TimelineItem *item = m_pWorkspace->GetClickedItem(); + if ( !item ) + return; + + CFlexAnimationTrack *track = item->GetSafeTrack(); + if ( !track ) + return; + + CEdgePropertiesParams params; + Q_memset( ¶ms, 0, sizeof( params ) ); + Q_strcpy( params.m_szDialogTitle, "Edge Properties" ); + + params.SetFromFlexTrack( track ); + + if ( !EdgeProperties( ¶ms ) ) + { + return; + } + + char const *undotext = "Change Edge Properties"; + + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( undotext ); + + // Apply changes. + params.ApplyToTrack( track ); + + g_pChoreoView->PushRedo( undotext ); + + m_pWorkspace->redraw(); + redraw(); +} + +float ExpressionTool::GetScrubberSceneTime() +{ + CChoreoEvent *ev = GetSafeEvent(); + if ( !ev ) + return 0.0f; + + float curtime = GetScrub(); + curtime += ev->GetStartTime(); + return curtime; +} + +void ExpressionTool::GetTimelineItems( CUtlVector< TimelineItem * >& list ) +{ + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + TimelineItem *item = m_pWorkspace->GetItem( i ); + if ( !item ) + continue; + + list.AddToTail( item ); + } +} + + + +bool ExpressionTool::HasCopiedColumn() +{ + return m_ColumnCopy.m_bActive; +} + +void ExpressionTool::OnCopyColumn() +{ + m_ColumnCopy.Reset(); + + m_ColumnCopy.m_bActive = true; + m_ColumnCopy.m_flCopyTimes[ 0 ] = m_flSelection[ 0 ]; + m_ColumnCopy.m_flCopyTimes[ 1 ] = m_flSelection[ 1 ]; + + for ( int controller = 0; controller < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; ++controller ) + { + TimelineItem *item = m_pWorkspace->GetItem( controller ); + if ( !item ) + continue; + + CFlexAnimationTrack *track = item->GetSafeTrack(); + if ( !track ) + continue; + + for ( int t = 0; t < 2; t++ ) + { + for ( int i = track->GetNumSamples( t ) - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = track->GetSample( i, t ); + if ( !sample->selected ) + continue; + + // Add to dictionary + CExpressionSample copy( *sample ); + copy.selected = false; + + int tIndex = m_ColumnCopy.m_Data.Find( track->GetFlexControllerName() ); + if ( tIndex == m_ColumnCopy.m_Data.InvalidIndex() ) + { + tIndex = m_ColumnCopy.m_Data.Insert( track->GetFlexControllerName() ); + } + + CColumnCopier::CTrackData &data = m_ColumnCopy.m_Data[ tIndex ]; + data.m_Samples[ t ].AddToTail( copy ); + } + } + } +} + +void ExpressionTool::OnPasteColumn() +{ + if ( !m_ColumnCopy.m_bActive ) + { + Msg( "Nothing to paste\n" ); + return; + } + + float flPasteTime = GetTimeForClickedPos(); + + float flPasteEndTime = flPasteTime + m_ColumnCopy.m_flCopyTimes[ 1 ] - m_ColumnCopy.m_flCopyTimes[ 0 ]; + + // Clear selection and redraw() + DeselectAll(); + + // Select everthing in the paste region so we can delete the existing stuff + SelectPoints( flPasteTime, flPasteEndTime ); + + int i, t; + + char const *undotext = "Paste column"; + + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( undotext ); + + for ( int controller = 0; controller < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; controller++ ) + { + TimelineItem *item = m_pWorkspace->GetItem( controller ); + if ( !item ) + continue; + + CFlexAnimationTrack *track = item->GetSafeTrack(); + if ( !track ) + continue; + + int tIndex = m_ColumnCopy.m_Data.Find( track->GetFlexControllerName() ); + + for ( t = 0; t < 2; t++ ) + { + // Remove all selected samples + for ( i = track->GetNumSamples( t ) - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = track->GetSample( i, t ); + if ( !sample->selected ) + continue; + + track->RemoveSample( i, t ); + } + + // Now add the new samples, if any in the time selection + if ( tIndex != m_ColumnCopy.m_Data.InvalidIndex() ) + { + CColumnCopier::CTrackData &data = m_ColumnCopy.m_Data[ tIndex ]; + + for ( int j = 0; j < data.m_Samples[ t ].Count(); ++j ) + { + CExpressionSample *s = &data.m_Samples[ t ][ j ]; + CExpressionSample *newSample = track->AddSample( s->time - m_ColumnCopy.m_flCopyTimes[ 0 ] + flPasteTime, s->value, t ); + newSample->selected = true; + } + } + track->Resort( t ); + } + + item->DrawSelf(); + } + + g_pChoreoView->PushRedo( undotext ); +} + +void ExpressionTool::ClearColumnCopy() +{ + m_ColumnCopy.Reset(); +}
\ No newline at end of file diff --git a/utils/hlfaceposer/expressiontool.h b/utils/hlfaceposer/expressiontool.h new file mode 100644 index 0000000..49365b8 --- /dev/null +++ b/utils/hlfaceposer/expressiontool.h @@ -0,0 +1,368 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef EXPRESSIONTOOL_H +#define EXPRESSIONTOOL_H +#ifdef _WIN32 +#pragma once +#endif + +#include <mxtk/mx.h> +#include "studio.h" +#include "utlvector.h" +#include "tier1/utldict.h" +#include "faceposertoolwindow.h" + +class CChoreoEvent; +class TimelineItem; +class CFlexAnimationTrack; +class CExpClass; +class CChoreoWidgetDrawHelper; +class CExpressionToolWorkspace; +class CChoreoView; +class CFlexTimingTag; +class CExpression; +class mxSlider; + +#define IDC_EXPRESSIONTOOLVSCROLL 1000 +#define IDC_ADDTRACKS 1001 +#define IDC_COLLAPSEALL 1002 +#define IDC_EXPANDALL 1003 +#define IDC_EXPANDVALID 1004 +#define IDC_INSERT_TIMING_TAG 1005 +#define IDC_DELETE_TIMING_TAG 1006 +#define IDC_LOCK_TIMING_TAG 1007 +#define IDC_UNLOCK_TIMING_TAG 1008 + +#define IDC_COPY_TO_FLEX 1009 +#define IDC_COPY_FROM_FLEX 1010 + +#define IDC_NEW_EXPRESSION_FROM_FLEXANIMATION 1011 + +#define IDC_EXPORT_FA 1012 +#define IDC_IMPORT_FA 1013 + +#define IDC_REDO_FA 1014 +#define IDC_UNDO_FA 1015 + +#define IDC_TL_COPY 1016 +#define IDC_TL_PASTE 1017 +#define IDC_TL_DELETE 1018 +#define IDC_TL_DESELECT 1019 +#define IDC_TL_SELECTALL 1020 + +#define IDC_TL_COLLAPSE 1021 +#define IDC_TL_EXPAND 1022 +#define IDC_TL_ENABLE 1023 +#define IDC_TL_DISABLE 1024 + +#define IDC_TL_EDITNORMAL 1025 +#define IDC_TL_EDITLEFTRIGHT 1026 + +#define IDC_COLLAPSE_ALL_EXCEPT 1027 +#define IDC_DISABLE_ALL_EXCEPT 1028 +#define IDC_ENABLE_ALL_VALID 1029 + +#define IDC_TL_SNAPSELECTED 1030 +#define IDC_TL_SNAPPOINTS 1031 +#define IDC_TL_DELETECOLUMN 1032 +#define IDC_TL_SNAPALL 1033 + +#define IDC_FLEX_CHANGESCALE 1034 +#define IDC_FLEXHSCROLL 1035 + +#define IDC_ET_SORT_BY_USED 1036 +#define IDC_ET_SORT_BY_NAME 1037 + +#define IDC_ET_SELECTION_DELETE 1038 +#define IDC_ET_SELECTION_EXCISE 1039 + +#define IDC_ET_RESET_ITEM_SIZE 1040 +#define IDC_ET_RESET_ALL_ITEM_SIZES 1041 + +#define IDC_FLEX_SCALESAMPLES 1042 + +#define IDC_TL_KB_TENSION 1050 +#define IDC_TL_KB_BIAS 1051 +#define IDC_TL_KB_CONTINUITY 1052 + +#define IDC_ET_EDGEPROPERTIES 1053 +#define IDC_ET_SELECTION_COPY 1054 +#define IDC_ET_SELECTION_PASTE 1055 + +#include "ExpressionSample.h" + +class ExpressionTool : public mxWindow, public IFacePoserToolWindow +{ +public: + // Construction + ExpressionTool( mxWindow *parent ); + ~ExpressionTool( void ); + + virtual void Think( float dt ); + void ScrubThink( float dt, bool scrubbing ); + virtual bool IsScrubbing( void ) const; + virtual bool IsProcessing( void ); + + + virtual int handleEvent( mxEvent *event ); + virtual void redraw( void ); + virtual bool PaintBackground(); + + bool SetFlexAnimationTrackFromExpression( int mx, int my, CExpClass *cl, CExpression *exp ); + + void SetEvent( CChoreoEvent *event ); + + bool HasCopyData( void ); + + void Copy( CFlexAnimationTrack *source ); + void Paste( CFlexAnimationTrack *destination ); + + void GetScrubHandleRect( RECT& rcHandle, bool clipped = false ); + void DrawScrubHandle( CChoreoWidgetDrawHelper& drawHelper, RECT& rcHandle ); + void DrawEventEnd( CChoreoWidgetDrawHelper& drawHelper ); + + CChoreoEvent *GetSafeEvent( void ); + + void ExpandAll( void ); + void ExpandValid( void ); + + void LayoutItems( bool force = false ); + + void OnCopyToFlex( bool isEdited ); + void OnCopyFromFlex( bool isEdited ); + + void OnCopyToFlex( float scenetime, bool isEdited ); + void OnCopyFromFlex( float scenetime, bool isEdited ); + void OnSetSingleKeyFromFlex( char const *sliderName ); + + void OnNewExpression( void ); + void ShowContextMenu( mxEvent *event, bool include_track_menus ); + + void ForceScrubPosition( float newtime ); + void ForceScrubPositionFromSceneTime( float scenetime ); + + void SetScrubTime( float t ); + void SetScrubTargetTime( float t ); + + void DrawScrubHandles(); + + void SetClickedPos( int x, int y ); + float GetTimeForClickedPos( void ); + + void SetMouseOverPos( int x, int y ); + void GetMouseOverPos( int &x, int& y ); + void GetMouseOverPosRect( RECT& rcPos ); + void DrawMouseOverPos( CChoreoWidgetDrawHelper& drawHelper, RECT& rcPos ); + void DrawMouseOverPos(); + + + void MoveSelectedSamples( float dfdx, float dfdy, bool snap ); + void DeleteSelectedSamples( void ); + int CountSelectedSamples( void ); + void DeselectAll( void ); + + void RepositionHSlider( void ); + + bool IsFocusItem( TimelineItem *item ); + virtual void OnModelChanged(); + + float GetScrub() const { return m_flScrub; } + float GetScrubberSceneTime(); + + void GetTimelineItems( CUtlVector< TimelineItem * >& list ); + void InvalidateLayout( void ); + +private: + void DoTrackLookup( CChoreoEvent *event ); + + void AddFlexTimingTag( int mx ); + void DeleteFlexTimingTag( int mx, int my ); + + void OnSortByUsed( void ); + void OnSortByName( void ); + + void OnDeleteSelection( bool excise_time ); + void OnResetItemSize(); + void OnResetAllItemSizes(); + void ResampleControlPoints( CFlexTimingTag *tag, float newposition ); + + void OnScaleSamples(); + + void LockTimingTag( void ); + void UnlockTimingTag( void ); + + bool GetTimingTagRect( RECT& rcClient, CChoreoEvent *event, CFlexTimingTag *tag, RECT& rcTag ); + +// float MouseToFrac( int mx ); +//float MouseToTime( int mx ); +// int TimeToMouse( float t ); + + void GetWorkspaceLeftRight( int& left, int& right ); + + bool IsMouseOverScrubHandle( mxEvent *event ); + CFlexTimingTag *IsMouseOverTag( int mx, int my ); + + void DrawRelativeTags( CChoreoWidgetDrawHelper& drawHelper ); + + void DrawFocusRect( void ); + + void ApplyBounds( int& mx, int& my ); + void CalcBounds( int movetype ); + + void OnExportFlexAnimation( void ); + void OnImportFlexAnimation( void ); + + void OnUndo( void ); + void OnRedo( void ); + + void StartDragging( int dragtype, int startx, int starty, HCURSOR cursor ); + void GetWorkspaceRect( RECT &rc ); + void AddFocusRect( RECT& rc ); + void OnMouseMove( mxEvent *event ); + + // Mouse control over selected samples + void SelectPoints( float starttime, float endtime ); + void FinishSelect( int startx, int mx ); + void FinishMoveSelection( int startx, int mx ); + void FinishMoveSelectionStart( int startx, int mx ); + void FinishMoveSelectionEnd( int startx, int mx ); + + // In general over the point area tray + bool IsMouseOverPoints( int mx, int my ); + // Specifically over selected points + bool IsMouseOverSelection( int mx, int my ); + bool IsMouseOverSelectionStartEdge( mxEvent *event ); + bool IsMouseOverSelectionEndEdge( mxEvent *event ); + + // Readjust slider + void MoveTimeSliderToPos( int x ); + void OnChangeScale(); + int ComputeHPixelsNeeded( void ); + float GetTimeValueForMouse( int mx, bool clip = false ); + + void OnEdgeProperties(); + +public: + int GetPixelForTimeValue( float time, bool *clipped = NULL ); + float GetPixelsPerSecond( void ); + void GetStartAndEndTime( float& st, float& ed ); + float GetEventEndTime(); + +private: + + class CColumnCopier + { + public: + class CTrackData + { + public: + CTrackData() {}; + CTrackData( const CTrackData& other ) + { + m_Samples[ 0 ].CopyArray( other.m_Samples[ 0 ].Base(), other.m_Samples[ 0 ].Count() ); + m_Samples[ 1 ].CopyArray( other.m_Samples[ 1 ].Base(), other.m_Samples[ 1 ].Count() ); + } + CUtlVector< CExpressionSample > m_Samples[ 2 ]; + }; + + bool m_bActive; + float m_flCopyTimes[ 2 ]; + CUtlDict< CTrackData, int > m_Data; + + CColumnCopier() : m_bActive( false ) + { + m_flCopyTimes[ 0 ] = m_flCopyTimes[ 1 ] = 0.0f; + } + + void Reset() + { + m_bActive = false; + m_flCopyTimes[ 0 ] = m_flCopyTimes[ 1 ] = 0.0f; + m_Data.Purge(); + } + }; + + bool HasCopiedColumn(); + void OnCopyColumn(); + void OnPasteColumn(); + void ClearColumnCopy(); + + CColumnCopier m_ColumnCopy; + + int m_nFocusEventGlobalID; + + float m_flScrub; + float m_flScrubTarget; + + enum + { + DRAGTYPE_NONE = 0, + DRAGTYPE_SCRUBBER, + DRAGTYPE_FLEXTIMINGTAG, + + DRAGTYPE_SELECTSAMPLES, + DRAGTYPE_MOVESELECTION, + DRAGTYPE_MOVESELECTIONSTART, + DRAGTYPE_MOVESELECTIONEND, + }; + + HCURSOR m_hPrevCursor; + int m_nDragType; + + int m_nStartX; + int m_nStartY; + int m_nLastX; + int m_nLastY; + + int m_nClickedX; + int m_nClickedY; + + bool m_bUseBounds; + int m_nMinX; + int m_nMaxX; + + struct CFocusRect + { + RECT m_rcOrig; + RECT m_rcFocus; + }; + CUtlVector < CFocusRect > m_FocusRects; + + CUtlVector< CExpressionSample > m_CopyData[2]; + + CExpressionToolWorkspace *m_pWorkspace; + + CChoreoEvent *m_pLastEvent; + + int m_nMousePos[ 2 ]; + + float m_flSelection[ 2 ]; + bool m_bSelectionActive; + + bool m_bSuppressLayout; + // Height/width of scroll bars + int m_nScrollbarHeight; + float m_flLeftOffset; + mxScrollbar *m_pHorzScrollBar; + int m_nLastHPixelsNeeded; + // How many pixels per second we are showing in the UI + float m_flPixelsPerSecond; + // Do we need to move controls? + bool m_bLayoutIsValid; + float m_flLastDuration; + bool m_bInSetEvent; + float m_flScrubberTimeOffset; + + friend class CChoreoView; + + +}; + +extern ExpressionTool *g_pExpressionTool; + +#endif // EXPRESSIONTOOL_H diff --git a/utils/hlfaceposer/faceposer_models.cpp b/utils/hlfaceposer/faceposer_models.cpp new file mode 100644 index 0000000..49ec500 --- /dev/null +++ b/utils/hlfaceposer/faceposer_models.cpp @@ -0,0 +1,1142 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "hlfaceposer.h" +#include "StudioModel.h" +#include "faceposer_models.h" +#include "filesystem.h" +#include "ifaceposerworkspace.h" +#include <mxtk/mx.h> +#include "mdlviewer.h" +#include "mxexpressiontray.h" +#include "ControlPanel.h" +#include "checksum_crc.h" +#include "ViewerSettings.h" +#include "matsyswin.h" +#include "KeyValues.h" +#include "utlbuffer.h" +#include "expression.h" +#include "ProgressDialog.h" +#include "tier1/UtlString.h" +#include "tier1/FmtStr.h" +#include "tier1/KeyValues.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +void SetupModelFlexcontrollerLinks( StudioModel *model ); + +IFaceposerModels::CFacePoserModel::CFacePoserModel( char const *modelfile, StudioModel *model ) +{ + m_pModel = model; + m_szActorName[ 0 ] = 0; + m_szShortName[ 0 ] = 0; + strcpy( m_szModelFileName, modelfile ); + Q_FixSlashes( m_szModelFileName ); + + CStudioHdr *hdr = model->GetStudioHdr(); + if ( hdr ) + { + Q_StripExtension( hdr->pszName(), m_szShortName, sizeof( m_szShortName ) ); + } + + m_bVisibileIn3DView = false; + m_bFirstBitmapLoad = true; + + LoadBitmaps(); +} + +IFaceposerModels::CFacePoserModel::~CFacePoserModel() +{ + FreeBitmaps(); +} + +void IFaceposerModels::CFacePoserModel::LoadBitmaps() +{ + CStudioHdr *hdr = m_pModel ? m_pModel->GetStudioHdr() : NULL; + if ( hdr ) + { + for ( int i = 0 ;i < hdr->GetNumSeq(); i++ ) + { + mxbitmapdata_t *bm = new mxbitmapdata_t(); + + AnimBitmap *entry = new AnimBitmap(); + entry->needsload = true; + entry->bitmap = bm; + + // Need to load bitmap from disk image via crc, etc. + //Assert( 0 ); + + m_AnimationBitmaps.AddToTail( entry ); + } + } +} + +CRC32_t IFaceposerModels::CFacePoserModel::GetBitmapCRC( int sequence ) +{ + CStudioHdr *hdr = m_pModel ? m_pModel->GetStudioHdr() : NULL; + if ( !hdr ) + return (CRC32_t)-1; + + if ( sequence < 0 || sequence >= hdr->GetNumSeq() ) + return (CRC32_t)-1; + + mstudioseqdesc_t &seqdesc = hdr->pSeqdesc( sequence ); + + CRC32_t crc; + CRC32_Init( &crc ); + + // For sequences, we'll checsum a bit of data + + CRC32_ProcessBuffer( &crc, (void *)seqdesc.pszLabel(), Q_strlen( seqdesc.pszLabel() ) ); + CRC32_ProcessBuffer( &crc, (void *)seqdesc.pszActivityName(), Q_strlen( seqdesc.pszActivityName() ) ); + CRC32_ProcessBuffer( &crc, (void *)&seqdesc.flags, sizeof( seqdesc.flags ) ); + //CRC32_ProcessBuffer( &crc, (void *)&seqdesc.numevents, sizeof( seqdesc.numevents ) ); + CRC32_ProcessBuffer( &crc, (void *)&seqdesc.numblends, sizeof( seqdesc.numblends ) ); + CRC32_ProcessBuffer( &crc, (void *)seqdesc.groupsize, sizeof( seqdesc.groupsize ) ); + + KeyValues *seqKeyValues = new KeyValues(""); + if ( seqKeyValues->LoadFromBuffer( m_pModel->GetFileName( ), m_pModel->GetKeyValueText( sequence ) ) ) + { + // Yuck, but I need it in a contiguous block of memory... oh well... + CUtlBuffer buf; + seqKeyValues->RecursiveSaveToFile( buf, 0 ); + CRC32_ProcessBuffer( &crc, ( void * )buf.Base(), buf.TellPut() ); + } + + seqKeyValues->deleteThis(); + + CRC32_Final( &crc ); + + return crc; +} + +const char *IFaceposerModels::CFacePoserModel::GetBitmapChecksum( int sequence ) +{ + CRC32_t crc = GetBitmapCRC( sequence ); + + // Create string name out of binary data + static char filename[ 512 ]; + + char hex[ 16 ]; + Q_binarytohex( (byte *)&crc, sizeof( crc ), hex, sizeof( hex ) ); + + Q_snprintf( filename, sizeof( filename ), "%s", hex ); + return filename; +} + +const char *IFaceposerModels::CFacePoserModel::GetBitmapFilename( int sequence ) +{ + char *in, *out; + static char filename[ 256 ]; + filename[ 0 ] = 0; + + char modelName[512], modelNameTemp[512]; + Q_strncpy( modelNameTemp, GetShortModelName(), sizeof( modelNameTemp ) ); + + in = modelNameTemp; + out = modelName; + + while ( *in ) + { + if ( V_isalnum( *in ) || + *in == '_' || + *in == '\\' || + *in == '/' || + *in == '.' || + *in == ':' ) + { + *out++ = *in; + } + in++; + } + *out = 0; + + + Q_snprintf( filename, sizeof( filename ), "expressions/%s/animation/%s.bmp", modelName, GetBitmapChecksum( sequence ) ); + + Q_FixSlashes( filename ); + strlwr( filename ); + + CreatePath( filename ); + + return filename; +} + +void IFaceposerModels::CFacePoserModel::FreeBitmaps() +{ + while ( m_AnimationBitmaps.Count() > 0 ) + { + AnimBitmap *bm = m_AnimationBitmaps[ 0 ]; + delete bm->bitmap; + delete bm; + m_AnimationBitmaps.Remove( 0 ); + } +} + +void IFaceposerModels::CFacePoserModel::LoadBitmapForSequence( mxbitmapdata_t *bitmap, int sequence ) +{ + // See if it exists + char filename[ 512 ]; + Q_strncpy( filename, GetBitmapFilename( sequence ), sizeof( filename ) ); + + if ( !LoadBitmapFromFile( filename, *bitmap ) ) + { + CreateNewBitmap( filename, sequence, 256, false, NULL, bitmap ); + } +} + +static float FindPoseCycle( StudioModel *model, int sequence ) +{ + float cycle = 0.0f; + if ( !model->GetStudioHdr() ) + return cycle; + + 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 ) + { + int thumbnail_frame = pkvAllFaceposer->GetInt( "thumbnail_frame", 0 ); + if ( thumbnail_frame ) + { + // Convert frame to cycle if we have valid data + int maxFrame = model->GetNumFrames( sequence ) - 1; + + if ( maxFrame > 0 ) + { + cycle = thumbnail_frame / (float)maxFrame; + } + } + } + } + + seqKeyValues->deleteThis(); + + return cycle; +} + + +void EnableStickySnapshotMode( void ) +{ + g_pMatSysWindow->EnableStickySnapshotMode( ); +} + +void DisableStickySnapshotMode( void ) +{ + g_pMatSysWindow->DisableStickySnapshotMode( ); +} + + +void IFaceposerModels::CreateNewBitmap( int modelindex, char const *pchBitmapFilename, int sequence, int nSnapShotSize, bool bZoomInOnFace, CExpression *pExpression, mxbitmapdata_t *bitmap ) +{ + CFacePoserModel *m = m_Models[ modelindex ]; + if ( m ) + { + m->CreateNewBitmap( pchBitmapFilename, sequence, nSnapShotSize, bZoomInOnFace, pExpression, bitmap ); + } +} + +void IFaceposerModels::CFacePoserModel::CreateNewBitmap( char const *pchBitmapFilename, int sequence, int nSnapShotSize, bool bZoomInOnFace, CExpression *pExpression, mxbitmapdata_t *bitmap ) +{ + MatSysWindow *pWnd = g_pMatSysWindow; + if ( !pWnd ) + return; + + StudioModel *model = m_pModel; + if ( !model ) + return; + + CStudioHdr *hdr = model->GetStudioHdr(); + if ( !hdr ) + return; + if ( sequence < 0 || sequence >= hdr->GetNumSeq() ) + return; + + mstudioseqdesc_t &seqdesc = hdr->pSeqdesc( sequence ); + + Con_ColorPrintf( FILE_COLOR, "Creating bitmap %s for sequence '%s'\n", pchBitmapFilename, seqdesc.pszLabel() ); + + model->ClearOverlaysSequences(); + int iLayer = model->GetNewAnimationLayer(); + model->SetOverlaySequence( iLayer, sequence, 1.0 ); + model->SetOverlayRate( iLayer, FindPoseCycle( model, sequence ), 0.0 ); + + for (int i = 0; i < hdr->GetNumPoseParameters(); i++) + { + model->SetPoseParameter( i, 0.0 ); + } + + float flexValues[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ] = { 0 }; + + if ( pExpression ) + { + float *settings = pExpression->GetSettings(); + float *weights = pExpression->GetWeights(); + + // Save existing settings from model + for ( LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); ++i ) + { + int j = hdr->pFlexcontroller( i )->localToGlobal; + if ( j == -1 ) + continue; + flexValues[ i ] = model->GetFlexController( i ); + // Set Value from passed in settings + model->SetFlexController( i, settings[ j ] * weights[ j ] ); + } + } + + model->ClearLookTargets( ); + + QAngle oldrot, oldLight; + Vector oldtrans; + + VectorCopy( model->m_angles, oldrot ); + VectorCopy( model->m_origin, oldtrans ); + VectorCopy( g_viewerSettings.lightrot, oldLight ); + + model->m_angles.Init(); + model->m_origin.Init(); + g_viewerSettings.lightrot.Init(); + + g_viewerSettings.lightrot.y = -180; + + bool bSaveGround = g_viewerSettings.showGround; + g_viewerSettings.showGround = false; + + if ( bZoomInOnFace ) + { + Vector size; + VectorSubtract( hdr->hull_max(), hdr->hull_min(), size ); + + float eyeheight = hdr->hull_min().z + 0.9 * size.z; + // float width = ( size.x + size.y ) / 2.0f; + + model->m_origin.x = size.z * .6f; + + if ( hdr->GetNumAttachments() > 0 ) + { + for (int i = 0; i < hdr->GetNumAttachments(); i++) + { + const mstudioattachment_t &attachment = hdr->pAttachment( i ); + int iBone = hdr->GetAttachmentBone( i ); + + if ( Q_stricmp( attachment.pszName(), "eyes" ) ) + continue; + + mstudiobone_t *bone = hdr->pBone( iBone ); + if ( !bone ) + continue; + + matrix3x4_t boneToPose; + MatrixInvert( bone->poseToBone, boneToPose ); + + matrix3x4_t attachmentPoseToLocal; + ConcatTransforms( boneToPose, attachment.local, attachmentPoseToLocal ); + + Vector localSpaceEyePosition; + VectorITransform( vec3_origin, attachmentPoseToLocal, localSpaceEyePosition ); + + // Not sure why this must be negative? + eyeheight = -localSpaceEyePosition.z + hdr->hull_min().z; + break; + } + } + + 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 ) + { + float flEyeheight = pkvAllFaceposer->GetFloat( "eye_height", -9999.0f ); + if ( flEyeheight != -9999.0f ) + { + eyeheight = flEyeheight; + } + } + } + + model->m_origin.z += eyeheight; + } + else + { + Vector mins, maxs; + model->ExtractBbox(mins, maxs); + Vector size; + VectorSubtract( maxs, mins, size ); + + float maxdim = size.x; + if ( size.y > maxdim ) + maxdim = size.y; + if ( size.z > maxdim ) + maxdim = size.z; + + float midpoint = mins.z + 0.5 * size.z; + + model->m_origin.x = 3 * maxdim; + model->m_origin.z += midpoint; + } + + g_pMatSysWindow->PushSnapshotMode( nSnapShotSize ); + + // Snapshots are taken of the back buffer; + // we need to render to the back buffer but not move it to the front + pWnd->SuppressBufferSwap( true ); + pWnd->redraw(); + pWnd->SuppressBufferSwap( false ); + + // make it square, assumes w > h + char fullpath[ 512 ]; + Q_snprintf( fullpath, sizeof( fullpath ), "%s%s", GetGameDirectory(), pchBitmapFilename ); + pWnd->TakeSnapshotRect( fullpath, 0, 0, nSnapShotSize, nSnapShotSize ); + + g_pMatSysWindow->PopSnapshotMode( ); + + VectorCopy( oldrot, model->m_angles ); + VectorCopy( oldtrans, model->m_origin ); + VectorCopy( oldLight, g_viewerSettings.lightrot ); + + g_viewerSettings.showGround = bSaveGround; + + if ( pExpression ) + { + // Save existing settings from model + for ( LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); ++i ) + { + int j = hdr->pFlexcontroller( i )->localToGlobal; + if ( j == -1 ) + continue; + + model->SetFlexController( i, flexValues[ i ] ); + } + } + + model->ClearOverlaysSequences(); + + if ( bitmap->valid ) + { + DeleteObject( bitmap->image ); + bitmap->image = 0; + bitmap->valid = false; + } + + LoadBitmapFromFile( pchBitmapFilename, *bitmap ); +} + +mxbitmapdata_t *IFaceposerModels::CFacePoserModel::GetBitmapForSequence( int sequence ) +{ + static mxbitmapdata_t nullbitmap; + if ( sequence < 0 || sequence >= m_AnimationBitmaps.Count() ) + return &nullbitmap; + + /* + if ( m_bFirstBitmapLoad ) + { + m_bFirstBitmapLoad = false; + ReconcileAnimationBitmaps(); + } + */ + + AnimBitmap *slot = m_AnimationBitmaps[ sequence ]; + if ( slot->needsload ) + { + slot->needsload = false; + LoadBitmapForSequence( slot->bitmap, sequence ); + } + + return m_AnimationBitmaps[ sequence ]->bitmap; +} + +void IFaceposerModels::CFacePoserModel::BuildValidChecksums( CUtlRBTree< CRC32_t > &tree ) +{ + StudioModel *model = m_pModel; + if ( !model ) + return; + + CStudioHdr *hdr = model->GetStudioHdr(); + if ( !hdr ) + return; + + for ( int i = 0; i < hdr->GetNumSeq(); i++ ) + { + CRC32_t crc = GetBitmapCRC( i ); + tree.Insert( crc ); + } +} + +void IFaceposerModels::CFacePoserModel::ReconcileAnimationBitmaps() +{ + // iterate files in directory and see if each checksum is valid and if not delete the .bmp + char path[ 512 ]; + Q_snprintf( path, sizeof( path ), "expressions/%s/animation/*.bmp", GetShortModelName() ); + + FileFindHandle_t hFindFile; + + char const *fn = filesystem->FindFirstEx( path, "MOD", &hFindFile ); + + g_pProgressDialog->Start( CFmtStr( "%s - Reconcile Animation Thumbnails", GetShortModelName() ), "", true ); + + CUtlVector< CUtlString > workList; + + if ( fn ) + { + while ( fn ) + { + // Don't do anything with directories + if ( !filesystem->FindIsDirectory( hFindFile ) ) + { + CUtlString s = fn; + workList.AddToTail( s ); + } + + fn = filesystem->FindNext( hFindFile ); + } + + filesystem->FindClose( hFindFile ); + } + + CUtlRBTree< CRC32_t > tree( 0, 0, DefLessFunc( CRC32_t ) ); + BuildValidChecksums( tree ); + + for ( int i = 0 ; i < workList.Count(); ++i ) + { + char testname[ 256 ]; + Q_StripExtension( workList[ i ].String(), testname, sizeof( testname ) ); + + g_pProgressDialog->UpdateText( "%s", testname ); + g_pProgressDialog->Update( (float)i / (float)workList.Count() ); + + CRC32_t check; + Q_hextobinary( testname, Q_strlen( testname ), (byte *)&check, sizeof( check ) ); + + if ( tree.Find( check ) == tree.InvalidIndex() ) + { + Q_snprintf( testname, sizeof( testname ), "expressions/%s/animation/%s", GetShortModelName(), fn ); + + char fullpath[ 512 ]; + filesystem->RelativePathToFullPath( testname, "MOD", fullpath, sizeof( fullpath ) ); + // Delete it + Con_ErrorPrintf( "Removing unused bitmap file %s\n", + fullpath ); + + _unlink( fullpath ); + } + + if ( g_pProgressDialog->IsCancelled() ) + { + Msg( "Cancelled\n" ); + break; + } + } + + g_pProgressDialog->Finish(); +} + +void IFaceposerModels::CFacePoserModel::RecreateAllAnimationBitmaps() +{ + StudioModel *model = m_pModel; + if ( !model ) + return; + + CStudioHdr *hdr = model->GetStudioHdr(); + if ( !hdr ) + return; + + g_pProgressDialog->Start( CFmtStr( "%s - Animation Thumbnails", GetShortModelName() ), "", true ); + + for ( int i = 0; i < hdr->GetNumSeq(); ++i ) + { + const mstudioseqdesc_t &seq = hdr->pSeqdesc( i ); + + g_pProgressDialog->UpdateText( "%s", seq.pszLabel() ); + g_pProgressDialog->Update( (float)i / (float)hdr->GetNumSeq() ); + + RecreateAnimationBitmap( i, false ); + + if ( g_pProgressDialog->IsCancelled() ) + { + Msg( "Cancelling\n" ); + break; + } + } + + g_pProgressDialog->Finish(); + + ReconcileAnimationBitmaps(); +} + +void IFaceposerModels::CFacePoserModel::RecreateAnimationBitmap( int sequence, bool reconcile ) +{ + if ( sequence < 0 || sequence >= m_AnimationBitmaps.Count() ) + { + Assert( 0 ); + return; + } + AnimBitmap *slot = m_AnimationBitmaps[ sequence ]; + slot->needsload = true; + if ( slot->bitmap->valid ) + { + DeleteObject( slot->bitmap->image ); + slot->bitmap->image = 0; + slot->bitmap->valid = false; + } + + char filename[ 512 ]; + Q_snprintf( filename, sizeof( filename ), "%s", GetBitmapFilename( sequence ) ); + + if ( filesystem->FileExists( filename ) ) + { + char fullpath[ 512 ]; + filesystem->RelativePathToFullPath( filename, "MOD", fullpath, sizeof( fullpath ) ); + _unlink( fullpath ); + } + + // Force recreation + GetBitmapForSequence( sequence ); + + if ( reconcile ) + { + ReconcileAnimationBitmaps( ); + } +} + +void IFaceposerModels::CFacePoserModel::Release( void ) +{ + m_pModel->FreeModel( true ); +} + +void IFaceposerModels::CFacePoserModel::Restore( void ) +{ + StudioModel *save = g_pStudioModel; + + g_pStudioModel = m_pModel; + + if (m_pModel->LoadModel( m_pModel->GetFileName() ) ) + { + m_pModel->PostLoadModel( m_pModel->GetFileName() ); + m_pModel->SetSequence( m_pModel->LookupSequence( "idle_subtle" ) ); + } + + g_pStudioModel = save; + + SetupModelFlexcontrollerLinks( m_pModel ); +} + + +IFaceposerModels::IFaceposerModels() +{ + m_nLastRenderFrame = -1; + m_nForceModelIndex = -1; +} + +IFaceposerModels::~IFaceposerModels() +{ + while ( m_Models.Count() > 0 ) + { + delete m_Models[ 0 ]; + m_Models.Remove( 0 ); + } +} + +IFaceposerModels::CFacePoserModel *IFaceposerModels::GetEntry( int index ) +{ + if ( index < 0 || index >= Count() ) + return NULL; + + CFacePoserModel *m = m_Models[ index ]; + if ( !m ) + return NULL; + return m; +} + +int IFaceposerModels::Count( void ) const +{ + return m_Models.Count(); +} + +char const *IFaceposerModels::GetModelName( int index ) +{ + CFacePoserModel *entry = GetEntry( index ); + if ( !entry ) + return ""; + + return entry->GetShortModelName(); +} + +char const *IFaceposerModels::GetModelFileName( int index ) +{ + CFacePoserModel *entry = GetEntry( index ); + if ( !entry ) + return ""; + + return entry->GetModelFileName(); +} + +void IFaceposerModels::ForceActiveModelIndex( int index ) +{ + m_nForceModelIndex = index; +} + +void IFaceposerModels::UnForceActiveModelIndex() +{ + m_nForceModelIndex = -1; +} + +int IFaceposerModels::GetActiveModelIndex( void ) const +{ + if ( !g_MDLViewer ) + return 0; + + if ( m_nForceModelIndex != -1 ) + return m_nForceModelIndex; + + return g_MDLViewer->GetActiveModelTab(); +} + +char const *IFaceposerModels::GetActiveModelName( void ) +{ + if ( !g_MDLViewer ) + return NULL; + + return GetModelName( GetActiveModelIndex() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : StudioModel +//----------------------------------------------------------------------------- +StudioModel *IFaceposerModels::GetActiveStudioModel( void ) +{ + StudioModel *mdl = GetStudioModel( GetActiveModelIndex() ); + if ( !mdl ) + return g_pStudioModel; + return mdl; +} + +int IFaceposerModels::FindModelByFilename( char const *filename ) +{ + int c = Count(); + for ( int i = 0; i < c; i++ ) + { + CFacePoserModel *m = GetEntry( i ); + if ( !m ) + continue; + + if ( !stricmp( m->GetModelFileName(), filename ) ) + return i; + } + + return -1; +} + +void SetupModelFlexcontrollerLinks( StudioModel *model ); + +int IFaceposerModels::LoadModel( char const *filename ) +{ + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + int idx = FindModelByFilename( filename ); + if ( idx == -1 && Count() < MAX_FP_MODELS ) + { + StudioModel *model = new StudioModel(); + + StudioModel *save = g_pStudioModel; + g_pStudioModel = model; + if ( !model->LoadModel( filename ) ) + { + delete model; + g_pStudioModel = save; + return 0; // ?? ERROR + } + g_pStudioModel = save; + + model->SetSequence( model->LookupSequence( "idle_subtle" ) ); + int idx = model->GetSequence(); + model->SetSequence( idx ); + + SetupModelFlexcontrollerLinks( model ); + + if (!LoadViewerSettings( filename, model )) + { + InitViewerSettings( "faceposer" ); + } + model->ClearOverlaysSequences(); + + + CFacePoserModel *newEntry = new CFacePoserModel( filename, model ); + + idx = m_Models.AddToTail( newEntry ); + + g_MDLViewer->InitModelTab(); + + g_MDLViewer->SetActiveModelTab( idx ); + + //g_pControlPanel->CenterOnFace(); + } + return idx; +} + +void IFaceposerModels::FreeModel( int index ) +{ + CFacePoserModel *entry = GetEntry( index ); + if ( !entry ) + return; + + StudioModel *m = entry->GetModel(); + + SaveViewerSettings( m->GetFileName(), m ); + + m->FreeModel( false ); + delete m; + + delete entry; + m_Models.Remove( index ); + + g_MDLViewer->InitModelTab(); +} + +void IFaceposerModels::CloseAllModels( void ) +{ + int c = Count(); + for ( int i = c - 1; i >= 0; i-- ) + { + FreeModel( i ); + } +} + +StudioModel *IFaceposerModels::GetStudioModel( int index ) +{ + CFacePoserModel *m = GetEntry( index ); + if ( !m ) + return NULL; + + if ( !m->GetModel() ) + return NULL; + + return m->GetModel(); +} + +CStudioHdr *IFaceposerModels::GetStudioHeader( int index ) +{ + StudioModel *m = GetStudioModel( index ); + if ( !m ) + return NULL; + + CStudioHdr *hdr = m->GetStudioHdr(); + if ( !hdr ) + return NULL; + return hdr; +} + +int IFaceposerModels::GetModelIndexForActor( char const *actorname ) +{ + int c = Count(); + for ( int i = 0; i < c; i++ ) + { + CFacePoserModel *m = GetEntry( i ); + if ( !m ) + continue; + + if ( !stricmp( m->GetActorName(), actorname ) ) + return i; + } + + return 0; +} + +StudioModel *IFaceposerModels::GetModelForActor( char const *actorname ) +{ + int c = Count(); + for ( int i = 0; i < c; i++ ) + { + CFacePoserModel *m = GetEntry( i ); + if ( !m ) + continue; + + if ( !stricmp( m->GetActorName(), actorname ) ) + return m->GetModel(); + } + + return NULL; +} + +char const *IFaceposerModels::GetActorNameForModel( int modelindex ) +{ + CFacePoserModel *m = GetEntry( modelindex ); + if ( !m ) + return ""; + return m->GetActorName(); +} + +void IFaceposerModels::SetActorNameForModel( int modelindex, char const *actorname ) +{ + CFacePoserModel *m = GetEntry( modelindex ); + if ( !m ) + return; + + m->SetActorName( actorname ); +} + +void IFaceposerModels::SaveModelList( void ) +{ + workspacefiles->StartStoringFiles( IWorkspaceFiles::MODELDATA ); + int c = Count(); + for ( int i = 0; i < c; i++ ) + { + CFacePoserModel *m = GetEntry( i ); + if ( !m ) + continue; + + workspacefiles->StoreFile( IWorkspaceFiles::MODELDATA, m->GetModelFileName() ); + } + workspacefiles->FinishStoringFiles( IWorkspaceFiles::MODELDATA ); +} + +void IFaceposerModels::LoadModelList( void ) +{ + int files = workspacefiles->GetNumStoredFiles( IWorkspaceFiles::MODELDATA ); + for ( int i = 0; i < files; i++ ) + { + char const *filename = workspacefiles->GetStoredFile( IWorkspaceFiles::MODELDATA, i ); + LoadModel( filename ); + } +} + +void IFaceposerModels::ReleaseModels( void ) +{ + int c = Count(); + for ( int i = 0; i < c; i++ ) + { + CFacePoserModel *m = GetEntry( i ); + if ( !m ) + continue; + + m->Release(); + } +} + +void IFaceposerModels::RestoreModels( void ) +{ + int c = Count(); + for ( int i = 0; i < c; i++ ) + { + CFacePoserModel *m = GetEntry( i ); + if ( !m ) + continue; + + m->Restore(); + } +} + + +/* +void IFaceposerModels::RefreshModels( void ) +{ + int c = Count(); + for ( int i = 0; i < c; i++ ) + { + CFacePoserModel *m = GetEntry( i ); + if ( !m ) + continue; + + m->Refresh(); + } +} +*/ + +int IFaceposerModels::CountVisibleModels( void ) +{ + int num = 0; + int c = Count(); + for ( int i = 0; i < c; i++ ) + { + CFacePoserModel *m = GetEntry( i ); + if ( !m ) + continue; + + if ( m->GetVisibleIn3DView() ) + { + num++; + } + } + + return num; +} + +void IFaceposerModels::ShowModelIn3DView( int modelindex, bool show ) +{ + CFacePoserModel *m = GetEntry( modelindex ); + if ( !m ) + return; + + m->SetVisibleIn3DView( show ); +} + +bool IFaceposerModels::IsModelShownIn3DView( int modelindex ) +{ + CFacePoserModel *m = GetEntry( modelindex ); + if ( !m ) + return false; + + return m->GetVisibleIn3DView(); +} + +int IFaceposerModels::GetIndexForStudioModel( StudioModel *model ) +{ + int c = Count(); + for ( int i = 0; i < c; i++ ) + { + CFacePoserModel *m = GetEntry( i ); + if ( !m ) + continue; + + if ( m->GetModel() == model ) + return i; + } + return -1; +} + +void IFaceposerModels::CheckResetFlexes( void ) +{ + int current_render_frame = g_MDLViewer->GetCurrentFrame(); + if ( current_render_frame == m_nLastRenderFrame ) + return; + + m_nLastRenderFrame = current_render_frame; + + // the phoneme editor just adds to the face, so reset the controllers + int c = Count(); + for ( int i = 0; i < c; i++ ) + { + CFacePoserModel *m = GetEntry( i ); + if ( !m ) + continue; + + StudioModel *model = m->GetModel(); + if ( !model ) + continue; + + CStudioHdr *hdr = model->GetStudioHdr(); + if ( !hdr ) + continue; + + for ( LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++ ) + { + model->SetFlexController( i, 0.0f ); + } + } +} + +void IFaceposerModels::ClearOverlaysSequences( void ) +{ + int c = Count(); + for ( int i = 0; i < c; i++ ) + { + CFacePoserModel *m = GetEntry( i ); + if ( !m ) + continue; + + StudioModel *model = m->GetModel(); + if ( !model ) + continue; + + model->ClearOverlaysSequences(); + } +} + +mxbitmapdata_t *IFaceposerModels::GetBitmapForSequence( int modelindex, int sequence ) +{ + static mxbitmapdata_t nullbitmap; + CFacePoserModel *m = GetEntry( modelindex ); + if ( !m ) + return &nullbitmap; + + return m->GetBitmapForSequence( sequence ); +} + +void IFaceposerModels::RecreateAllAnimationBitmaps( int modelindex ) +{ + CFacePoserModel *m = GetEntry( modelindex ); + if ( !m ) + return; + + m->RecreateAllAnimationBitmaps(); + +} + +void IFaceposerModels::RecreateAnimationBitmap( int modelindex, int sequence ) +{ + CFacePoserModel *m = GetEntry( modelindex ); + if ( !m ) + return; + + m->RecreateAnimationBitmap( sequence, true ); +} + +int IFaceposerModels::CountActiveSources() +{ + int count = 0; + + int c = Count(); + for ( int i = 0; i < c; i++ ) + { + CFacePoserModel *m = GetEntry( i ); + if ( !m ) + continue; + + StudioModel *model = m->GetModel(); + if ( !model ) + continue; + + count += model->m_mouth.GetNumVoiceSources(); + } + + return count; +} + +void IFaceposerModels::ClearModelTargets( bool force /*=false*/ ) +{ + int c = Count(); + for ( int i = 0; i < c; i++ ) + { + CFacePoserModel *m = GetEntry( i ); + if ( !m ) + continue; + + StudioModel *mdl = m->GetModel(); + if ( !mdl ) + continue; + + mdl->ClearLookTargets(); + } +} + +void IFaceposerModels::SetSolveHeadTurn( int solve ) +{ + int c = Count(); + for ( int i = 0; i < c; i++ ) + { + CFacePoserModel *m = GetEntry( i ); + if ( !m ) + continue; + + StudioModel *mdl = m->GetModel(); + if ( !mdl ) + continue; + + mdl->SetSolveHeadTurn( solve ); + } +} + + +static IFaceposerModels g_ModelManager; +IFaceposerModels *models = &g_ModelManager; diff --git a/utils/hlfaceposer/faceposer_models.h b/utils/hlfaceposer/faceposer_models.h new file mode 100644 index 0000000..2d3db4e --- /dev/null +++ b/utils/hlfaceposer/faceposer_models.h @@ -0,0 +1,193 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef FACEPOSER_MODELS_H +#define FACEPOSER_MODELS_H +#ifdef _WIN32 +#pragma once +#endif + +class StudioModel; + +#include "mxbitmaptools.h" + +typedef unsigned int CRC32_t; + +class IFaceposerModels +{ +public: + IFaceposerModels(); + virtual ~IFaceposerModels(); + + virtual int Count( void ) const; + virtual char const *GetModelName( int index ); + virtual char const *GetModelFileName( int index ); + virtual int GetActiveModelIndex( void ) const; + virtual char const *GetActiveModelName( void ); + virtual StudioModel *GetActiveStudioModel( void ); + virtual void ForceActiveModelIndex( int index ); + virtual void UnForceActiveModelIndex(); + virtual int FindModelByFilename( char const *filename ); + + virtual int LoadModel( char const *filename ); + virtual void FreeModel( int index ); + + virtual void CloseAllModels( void ); + + virtual StudioModel *GetStudioModel( int index ); + virtual CStudioHdr *GetStudioHeader( int index ); + virtual int GetIndexForStudioModel( StudioModel *model ); + + virtual int GetModelIndexForActor( char const *actorname ); + virtual StudioModel *GetModelForActor( char const *actorname ); + + virtual char const *GetActorNameForModel( int modelindex ); + virtual void SetActorNameForModel( int modelindex, char const *actorname ); + + virtual int CountVisibleModels( void ); + virtual void ShowModelIn3DView( int modelindex, bool show ); + virtual bool IsModelShownIn3DView( int modelindex ); + + virtual void SaveModelList( void ); + virtual void LoadModelList( void ); + + virtual void ReleaseModels( void ); + virtual void RestoreModels( void ); + + //virtual void RefreshModels( void ); + + virtual void CheckResetFlexes( void ); + virtual void ClearOverlaysSequences( void ); + + virtual mxbitmapdata_t *GetBitmapForSequence( int modelindex, int sequence ); + + virtual void RecreateAllAnimationBitmaps( int modelindex ); + virtual void RecreateAnimationBitmap( int modelindex, int sequence ); + + virtual void CreateNewBitmap( int modelindex, char const *pchBitmapFilename, int sequence, int nSnapShotSize, bool bZoomInOnFace, class CExpression *pExpression, mxbitmapdata_t *bitmap ); + + virtual int CountActiveSources(); + + virtual void SetSolveHeadTurn( int solve ); + + virtual void ClearModelTargets( bool force = false ); + +private: + class CFacePoserModel + { + public: + CFacePoserModel( char const *modelfile, StudioModel *model ); + ~CFacePoserModel(); + + void LoadBitmaps(); + void FreeBitmaps(); + mxbitmapdata_t *GetBitmapForSequence( int sequence ); + + const char *GetBitmapChecksum( int sequence ); + CRC32_t GetBitmapCRC( int sequence ); + const char *GetBitmapFilename( int sequence ); + void RecreateAllAnimationBitmaps(); + void RecreateAnimationBitmap( int sequence, bool reconcile ); + + + void SetActorName( char const *actorname ) + { + strcpy( m_szActorName, actorname ); + } + + char const *GetActorName( void ) const + { + return m_szActorName; + } + + StudioModel *GetModel( void ) const + { + return m_pModel; + } + + char const *GetModelFileName( void ) const + { + return m_szModelFileName; + } + + char const *GetShortModelName( void ) const + { + return m_szShortName; + } + + void SetVisibleIn3DView( bool visible ) + { + m_bVisibileIn3DView = visible; + } + + bool GetVisibleIn3DView( void ) const + { + return m_bVisibileIn3DView; + } + + // For material system purposes + void Release( void ); + void Restore( void ); + + void Refresh( void ) + { + // Forces a reload from disk + Release(); + Restore(); + } + + void CreateNewBitmap( char const *pchBitmapFilename, int sequence, int nSnapShotSize, bool bZoomInOnFace, class CExpression *pExpression, mxbitmapdata_t *bitmap ); + + private: + + void LoadBitmapForSequence( mxbitmapdata_t *bitmap, int sequence ); + + void ReconcileAnimationBitmaps(); + void BuildValidChecksums( CUtlRBTree< CRC32_t > &tree ); + + enum + { + MAX_ACTOR_NAME = 64, + MAX_MODEL_FILE = 128, + MAX_SHORT_NAME = 32, + }; + + char m_szActorName[ MAX_ACTOR_NAME ]; + char m_szModelFileName[ MAX_MODEL_FILE ]; + char m_szShortName[ MAX_SHORT_NAME ]; + StudioModel *m_pModel; + bool m_bVisibileIn3DView; + + struct AnimBitmap + { + AnimBitmap() + { + needsload = false; + bitmap = 0; + } + bool needsload; + mxbitmapdata_t *bitmap; + }; + + CUtlVector< AnimBitmap * > m_AnimationBitmaps; + bool m_bFirstBitmapLoad; + }; + + CFacePoserModel *GetEntry( int index ); + + CUtlVector< CFacePoserModel * > m_Models; + + int m_nLastRenderFrame; + int m_nForceModelIndex; +}; + +extern IFaceposerModels *models; + +void EnableStickySnapshotMode( void ); +void DisableStickySnapshotMode( void ); + +#endif // FACEPOSER_MODELS_H diff --git a/utils/hlfaceposer/faceposer_vgui.cpp b/utils/hlfaceposer/faceposer_vgui.cpp new file mode 100644 index 0000000..742c166 --- /dev/null +++ b/utils/hlfaceposer/faceposer_vgui.cpp @@ -0,0 +1,140 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +#include "cbase.h" +#include "faceposer_vgui.h" +#include <vgui/IVGui.h> +#include <vgui/ISurface.h> +#include <vgui/ISystem.h> +#include "vgui/IInput.h" +#include <VGuiMatSurface/IMatSystemSurface.h> +#include <matsys_controls/matsyscontrols.h> +#include <dme_controls/dmecontrols.h> +//#include "material.h" +#include "vgui_controls/AnimationController.h" +#include "inputsystem/iinputsystem.h" +#include "VGuiWnd.h" + +extern CreateInterfaceFn g_Factory; + +//----------------------------------------------------------------------------- +// Purpose: singleton accessor +//----------------------------------------------------------------------------- +static CFacePoserVGui s_FaceposerVGui; + +CFacePoserVGui *FaceposerVGui() +{ + return &s_FaceposerVGui; +} + +CFacePoserVGui::CFacePoserVGui(void) +{ + m_pActiveWindow = NULL; + m_hMainWindow = NULL; +} + +//----------------------------------------------------------------------------- +// Setup the base vgui panels +//----------------------------------------------------------------------------- +bool CFacePoserVGui::Init( HWND hWindow ) +{ + // initialize vgui_control interfaces + if (!vgui::VGui_InitInterfacesList( "FACEPOSER", &g_Factory, 1 )) + return false; + +// if ( !vgui::VGui_InitMatSysInterfacesList( "FACEPOSER", &g_Factory, 1 ) ) +// return false; + + // All of the various tools .dlls expose GetVGuiControlsModuleName() to us to make sure we don't have communication across .dlls +// if ( !vgui::VGui_InitDmeInterfacesList( "FACEPOSER", &g_Factory, 1 ) ) +// return false; + + if ( !g_pMatSystemSurface ) + return false; + + // configuration settings + vgui::system()->SetUserConfigFile("faceposer.vdf", "EXECUTABLE_PATH"); + + // Are we trapping input? + g_pMatSystemSurface->EnableWindowsMessages( true ); + + // Need to be able to play sounds through vgui + // g_pMatSystemSurface->InstallPlaySoundFunc( VGui_PlaySound ); + + // load scheme + if (!vgui::scheme()->LoadSchemeFromFile("Resource/SourceScheme.res", "FacePoser")) + { + return false; + } + + m_hMainWindow = hWindow; + + // Start the App running + vgui::ivgui()->Start(); + vgui::ivgui()->SetSleep(false); + + return true; +} + +void CFacePoserVGui::SetFocus( CVGuiWnd *pVGuiWnd ) +{ + if ( pVGuiWnd == m_pActiveWindow ) + return; + + g_pInputSystem->PollInputState(); + vgui::ivgui()->RunFrame(); + + g_pMatSystemSurface->AttachToWindow( NULL, false ); + g_pInputSystem->DetachFromWindow( ); + + if ( pVGuiWnd ) + { + HWND hWnd = (HWND)pVGuiWnd->GetParentWnd()->getHandle(); + + m_pActiveWindow = pVGuiWnd; + g_pInputSystem->AttachToWindow( hWnd ); + g_pMatSystemSurface->AttachToWindow( hWnd, false ); + vgui::ivgui()->ActivateContext( pVGuiWnd->GetVGuiContext() ); + } + else + { + m_pActiveWindow = NULL; + vgui::ivgui()->ActivateContext( vgui::DEFAULT_VGUI_CONTEXT ); + } +} + +bool CFacePoserVGui::HasFocus( CVGuiWnd *pWnd ) +{ + return m_pActiveWindow == pWnd; +} + +void CFacePoserVGui::Simulate() +{ + // VPROF( "CFacePoserVGui::Simulate" ); + + if ( !IsInitialized() ) + return; + + g_pInputSystem->PollInputState(); + vgui::ivgui()->RunFrame(); + + // run vgui animations + vgui::GetAnimationController()->UpdateAnimations( vgui::system()->GetCurrentTime() ); +} + +void CFacePoserVGui::Shutdown() +{ + // Give panels a chance to settle so things + // Marked for deletion will actually get deleted + + if ( !IsInitialized() ) + return; + + g_pInputSystem->PollInputState(); + vgui::ivgui()->RunFrame(); + + // stop the App running + vgui::ivgui()->Stop(); +} + +CFacePoserVGui::~CFacePoserVGui(void) +{ +} diff --git a/utils/hlfaceposer/faceposer_vgui.h b/utils/hlfaceposer/faceposer_vgui.h new file mode 100644 index 0000000..f905fae --- /dev/null +++ b/utils/hlfaceposer/faceposer_vgui.h @@ -0,0 +1,40 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef FACEPOSER_VGUI_H +#define FACEPOSER_VGUI_H +#ifdef _WIN32 +#pragma once +#endif + +class IMatSystemSurface; +class CVGuiWnd; + +extern IMatSystemSurface *g_pMatSystemSurface; + +class CFacePoserVGui +{ +public: + CFacePoserVGui(void); + ~CFacePoserVGui(void); + + bool Init( HWND hWindow ); + void Simulate(); + void Shutdown(); + bool HasFocus( CVGuiWnd *pWnd ); + void SetFocus( CVGuiWnd *pWnd ); + bool IsInitialized() { return m_hMainWindow != NULL; }; + + +protected: + + HWND m_hMainWindow; + CVGuiWnd *m_pActiveWindow; // the VGUI window that has the focus +}; + +CFacePoserVGui *FaceposerVGui(); + +#endif // FACEPOSER_VGUI_H diff --git a/utils/hlfaceposer/faceposertoolwindow.cpp b/utils/hlfaceposer/faceposertoolwindow.cpp new file mode 100644 index 0000000..7997a35 --- /dev/null +++ b/utils/hlfaceposer/faceposertoolwindow.cpp @@ -0,0 +1,746 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "hlfaceposer.h" +#include "faceposertoolwindow.h" +#include "utlvector.h" +#include "tier1/strtools.h" +#include "MDLViewer.h" +#include "choreowidgetdrawhelper.h" +#include "StudioModel.h" +#include "faceposer_models.h" + +extern MDLViewer *g_MDLViewer; + +static CUtlVector< IFacePoserToolWindow * > g_Tools; +IFacePoserToolWindow *IFacePoserToolWindow::s_pActiveTool = NULL; + +bool IFacePoserToolWindow::s_bToolsCanDraw; +static CUtlVector< IFacePoserToolWindow * > s_NeedRedraw; + + +IFacePoserToolWindow::IFacePoserToolWindow( char const *toolname, char const *displaynameroot ) +{ + m_bAutoProcess = false; + m_bUseForMainWindowTitle = false; + SetToolName( toolname ); + m_szPrefix[0]=0; + m_szSuffix[0]=0; + + SetDisplayNameRoot( displaynameroot ); + + g_Tools.AddToTail( this ); + + m_nToolFrameCount = 0; +} + +mxWindow *IFacePoserToolWindow::GetMxWindow( void ) +{ + return dynamic_cast< mxWindow * >( this ); +} + +IFacePoserToolWindow *IFacePoserToolWindow::GetActiveTool( void ) +{ + if ( s_pActiveTool ) + return s_pActiveTool; + + if ( GetToolCount() > 0 ) + return GetTool( 0 ); + + return NULL; +} + +void IFacePoserToolWindow::SetActiveTool( IFacePoserToolWindow *tool ) +{ + if ( tool != s_pActiveTool && s_pActiveTool ) + { + InvalidateRect( (HWND)s_pActiveTool->GetMxWindow()->getHandle(), NULL, TRUE ); + InvalidateRect( (HWND)tool->GetMxWindow()->getHandle(), NULL, TRUE ); + } + s_pActiveTool = tool; +} + +void IFacePoserToolWindow::Think( float dt ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +void IFacePoserToolWindow::SetToolName( char const *name ) +{ + Q_strncpy( m_szToolName, name, sizeof( m_szToolName ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +char const *IFacePoserToolWindow::GetToolName( void ) const +{ + return m_szToolName; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +void IFacePoserToolWindow::SetDisplayNameRoot( char const *name ) +{ + Q_snprintf( m_szDisplayRoot, sizeof( m_szDisplayRoot ), "%s", name ); + ComputeNewTitle(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +char const *IFacePoserToolWindow::GetDisplayNameRoot( void ) const +{ + return m_szDisplayRoot; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *suffix - +//----------------------------------------------------------------------------- +void IFacePoserToolWindow::SetSuffix( char const *suffix ) +{ + Q_snprintf( m_szSuffix, sizeof( m_szSuffix ), "%s", suffix ); + ComputeNewTitle(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *prefix - +//----------------------------------------------------------------------------- +void IFacePoserToolWindow::SetPrefix( char const *prefix ) +{ + Q_snprintf( m_szPrefix, sizeof( m_szPrefix ), "%s", prefix ); + ComputeNewTitle(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +char const *IFacePoserToolWindow::GetWindowTitle( void ) const +{ + return m_szWindowTitle; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : use - +//----------------------------------------------------------------------------- +void IFacePoserToolWindow::SetUseForMainWindowTitle( bool use ) +{ + m_bUseForMainWindowTitle = use; + if ( use ) + { + g_MDLViewer->setLabel( m_szWindowTitle ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void IFacePoserToolWindow::ComputeNewTitle( void ) +{ + Q_snprintf( m_szWindowTitle, sizeof( m_szWindowTitle ), "%s%s%s", m_szPrefix, m_szDisplayRoot, m_szSuffix ); + if ( GetMxWindow() ) + { + GetMxWindow()->setLabel( m_szWindowTitle ); + } + if ( !m_bUseForMainWindowTitle ) + return; + + g_MDLViewer->setLabel( m_szWindowTitle ); +} + +IFacePoserToolWindow::~IFacePoserToolWindow( void ) +{ + g_Tools.FindAndRemove( this ); +} + +struct ToolTranslate +{ + char const *toolname; + float xfrac; + float yfrac; + float wfrac; + float hfrac; + bool locked; +}; + +static ToolTranslate s_ToolTranslate[]= +{ + { "3D View", 0.0, 0.0, 0.4, 0.5, false }, + { "ControlPanel", 0.4, 0.0, 0.2, 0.25, false }, + { "FlexPanel", 0.6, 0.0, 0.4, 0.25, false }, + { "RampTool", 0.4, 0.25, 0.6, 0.25, false }, + { "CChoreoView", 0.0, 0.5, 1.0, 0.45, false }, +// { "Status Window", 0.0, 0.85, 1.0, 0.15, false }, +}; + +static bool TranslateToolPos( char const *toolname, int workspacew, int workspaceh, int& x, int& y, int &w, int &h, bool& locked ) +{ + int c = ARRAYSIZE( s_ToolTranslate ); + + for ( int i = 0; i < c; ++i ) + { + ToolTranslate& tt = s_ToolTranslate[ i ]; + + if ( !Q_stricmp( toolname, tt.toolname ) ) + { + x = (int)((float)workspacew * tt.xfrac + 0.5f ); + y = (int)((float)workspaceh * tt.yfrac + 0.5f ); + w = (int)((float)workspacew * tt.wfrac + 0.5f ); + h = (int)((float)workspaceh * tt.hfrac + 0.5f ); + locked = tt.locked; + return true; + } + } + + return false; +} + +static int s_nToolCount = 0; + +void IFacePoserToolWindow::LoadPosition( void ) +{ + bool visible; + bool locked; + bool zoomed; + int x, y, w, h; + + FacePoser_LoadWindowPositions( GetToolName(), visible, x, y, w, h, locked, zoomed ); + + if ( w == 0 || h == 0 ) + { + int idx = g_Tools.Find( this ); + Assert( idx != g_Tools.InvalidIndex() ); + if ( idx == 0 ) + { + s_nToolCount = 0; + } + + zoomed = false; + locked = false; + visible = true; + + // Just do a simple tiling + w = g_MDLViewer->w2() * 0.5; + h = g_MDLViewer->h2() * 0.5; + + x = g_MDLViewer->w2() * 0.25f + s_nToolCount * 20; + y = s_nToolCount * 20; + + bool translated = TranslateToolPos + ( + GetToolName(), + g_MDLViewer->w2(), + g_MDLViewer->h2(), + x, + y, + w, + h, + locked + ); + if ( !translated ) + { + ++s_nToolCount; + visible = false; + } + } + + GetMxWindow()->setBounds( x, y, w, h ); + if ( locked ^ IsLocked() ) + { + ToggleLockedState(); + } + GetMxWindow()->setVisible( visible ); +} + +void IFacePoserToolWindow::SavePosition( void ) +{ + bool visible; + int xpos, ypos, width, height; + + visible = GetMxWindow()->isVisible(); + xpos = GetMxWindow()->x(); + ypos = GetMxWindow()->y(); + width = GetMxWindow()->w(); + height = GetMxWindow()->h(); + + // xpos and ypos are screen space + POINT pt; + pt.x = xpos; + pt.y = ypos; + + // Convert from screen space to relative to client area of parent window so + // the setBounds == MoveWindow call will offset to the same location + if ( GetMxWindow()->getParent() ) + { + ScreenToClient( (HWND)GetMxWindow()->getParent()->getHandle(), &pt ); + xpos = (short)pt.x; + ypos = (short)pt.y; + } + + FacePoser_SaveWindowPositions( GetToolName(), visible, xpos, ypos, width, height, IsLocked(), false ); +} + +int IFacePoserToolWindow::GetToolCount( void ) +{ + return g_Tools.Count(); +} + +IFacePoserToolWindow *IFacePoserToolWindow::GetTool( int index ) +{ + return g_Tools[ index ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void IFacePoserToolWindow::InitTools( void ) +{ + int c = GetToolCount(); + int i; + for ( i = 0; i < c ; i++ ) + { + IFacePoserToolWindow *tool = GetTool( i ); + + FacePoser_MakeToolWindow( tool->GetMxWindow(), true ); + tool->GetMxWindow()->setLabel( tool->GetWindowTitle() ); + } +} + +void IFacePoserToolWindow::ShutdownTools( void ) +{ + int c = GetToolCount(); + int i; + for ( i = 0; i < c ; i++ ) + { + IFacePoserToolWindow *tool = GetTool( i ); + tool->Shutdown(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dt - +//----------------------------------------------------------------------------- +void IFacePoserToolWindow::ToolThink( float dt ) +{ + int c = GetToolCount(); + int i; + for ( i = 0; i < c ; i++ ) + { + IFacePoserToolWindow *tool = GetTool( i ); + tool->Think( dt ); + } + + // Don't self animate, all animation driven by thinking of various tools now + + if ( !ShouldAutoProcess() ) + { + c = models->Count(); + for ( i = 0; i < c; i++ ) + { + StudioModel *m = models->GetStudioModel( i ); + if ( m ) + { + m->AdvanceFrame ( dt ); + } + } + } +} + +bool IFacePoserToolWindow::IsLocked( void ) +{ + mxWindow *w = GetMxWindow(); + if ( !w ) + return false; + + return !FacePoser_HasWindowStyle( w, WS_SYSMENU ); +} + +void IFacePoserToolWindow::ToggleLockedState( void ) +{ + mxWindow *w = GetMxWindow(); + if ( !w ) + return; + + bool visible = w->isVisible(); + + bool islocked = IsLocked(); + if ( islocked ) + { + FacePoser_MakeToolWindow( w, true ); + } + else + { + FacePoser_RemoveWindowStyle( w, WS_OVERLAPPEDWINDOW ); + FacePoser_AddWindowExStyle( w, WS_EX_OVERLAPPEDWINDOW ); + } + + w->setVisible( false ); + + // If visible, force it to redraw, etc. + if ( visible ) + { + w->setVisible( true ); + } +} + +#define LOCK_INSET 2 +#define LOCK_SIZE 8 + +void IFacePoserToolWindow:: GetLockRect( RECT& rc ) +{ + mxWindow *w = GetMxWindow(); + Assert( w ); + if ( !w ) + return; + + GetCloseRect( rc ); + + OffsetRect( &rc, - ( LOCK_SIZE + 2 * LOCK_INSET ), 0 ); +} + +void IFacePoserToolWindow::GetCloseRect( RECT& rc ) +{ + mxWindow *w = GetMxWindow(); + Assert( w ); + if ( !w ) + return; + + rc.right = w->w2() - LOCK_INSET; + rc.left = rc.right - LOCK_SIZE; + rc.top = LOCK_INSET; + rc.bottom = rc.top + LOCK_SIZE; +} + +bool IFacePoserToolWindow::HandleToolEvent( mxEvent *event ) +{ + bool handled = false; + switch ( event->event ) + { + default: + break; + case mxEvent::Close: + { + g_MDLViewer->UpdateWindowMenu(); + handled = true; + } + break; + case mxEvent::ParentNotify: + { + mxWindow *w = GetMxWindow(); + if ( w ) + { + HWND wnd = (HWND)w->getHandle(); + SetFocus( wnd ); + SetWindowPos( wnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + SetActiveTool( this ); + } + handled = true; + } + break; + case mxEvent::PosChanged: + { + SetActiveTool( this ); + mxWindow *w = GetMxWindow(); + if ( w ) + { + SetFocus( (HWND)w->getHandle() ); + } + handled = true; + } + break; + case mxEvent::MouseDown: + case mxEvent::MouseUp: + { + bool isup = event->event == mxEvent::MouseUp; + + mxWindow *w = GetMxWindow(); + + if ( !isup && w ) + { + SetFocus( (HWND)w->getHandle() ); + SetWindowPos( (HWND)w->getHandle(), HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + + SetActiveTool( this ); + } + + if ( w && IsLocked() ) + { + RECT captionRect; + captionRect.left = 0; + captionRect.right = w->w2(); + captionRect.top = 0; + captionRect.bottom = GetCaptionHeight(); + + POINT pt; + pt.x = (short)event->x; + pt.y = (short)event->y; + + if ( PtInRect( &captionRect, pt ) ) + { + handled = !isup; + + // Right button anywhere + if ( event->buttons & mxEvent::MouseRightButton ) + { + if ( isup ) + { + ToggleLockedState(); + } + } + + // Left button on lock icon + RECT lockRect, closeRect; + GetLockRect( lockRect ); + GetCloseRect( closeRect ); + + if ( PtInRect( &lockRect, pt ) ) + { + if ( isup ) + { + ToggleLockedState(); + } + } + + if ( PtInRect( &closeRect, pt ) ) + { + if ( isup ) + { + w->setVisible( !w->isVisible() ); + g_MDLViewer->UpdateWindowMenu(); + } + } + } + } + } + break; + case mxEvent::NCMouseUp: + { + if ( event->buttons & mxEvent::MouseRightButton ) + { + ToggleLockedState(); + handled = true; + } + } + break; + + case mxEvent::NCMouseDown: + case mxEvent::Focus: + { + SetActiveTool( this ); + // don't mark handled = true, do this passively + } + break; + } + + return handled; +} + +void IFacePoserToolWindow::HandleToolRedraw( CChoreoWidgetDrawHelper& helper ) +{ + if ( !IsLocked() ) + return; + + mxWindow *w = GetMxWindow(); + if ( !w ) + return; + + ++m_nToolFrameCount; + + RECT lockRect, closeRect; + GetLockRect( lockRect ); + GetCloseRect( closeRect ); + + RECT captionRect; + helper.GetClientRect( captionRect ); + RECT rcClient = captionRect; + captionRect.bottom = captionRect.top + LOCK_SIZE + 2 * LOCK_INSET; + + COLORREF textColor = GetSysColor( COLOR_MENUTEXT ); //GetSysColor( COLOR_INACTIVECAPTIONTEXT ); + + if ( IsActiveTool() ) + { + helper.DrawFilledRect( GetSysColor( COLOR_ACTIVECAPTION ), captionRect ); + } + else + { + helper.DrawFilledRect( GetSysColor( COLOR_INACTIVECAPTION ), captionRect ); + } + + captionRect.top += 1; + + InflateRect( &captionRect, -LOCK_INSET, 0 ); + + helper.DrawColoredText( "Small Fonts", 9, FW_NORMAL, textColor, captionRect, + GetWindowTitle() ); + + //RECT rcFrame = captionRect; + //rcFrame.left = rcFrame.right - 50; + //rcFrame.right = rcFrame.left + 30; + // helper.DrawColoredText( "Small Fonts", 9, FW_NORMAL, textColor, rcFrame, va( "%i", m_nToolFrameCount ) ); + + lockRect.bottom++; + OffsetRect( &lockRect, 1, 1 ); + helper.DrawColoredTextCharset( "Marlett", 8, FW_NORMAL, SYMBOL_CHARSET, textColor, lockRect, "v" ); + + closeRect.bottom++; + helper.DrawOutlinedRect( textColor, PS_SOLID, 1, closeRect ); + OffsetRect( &closeRect, 1, 1 ); + helper.DrawColoredTextCharset( "Marlett", 8, FW_NORMAL, SYMBOL_CHARSET, textColor, closeRect, "r" ); + + rcClient.top += captionRect.bottom; + + helper.StartClipping( rcClient ); +} + +int IFacePoserToolWindow::GetCaptionHeight( void ) +{ + if ( !IsLocked() ) + return 0; + + return LOCK_SIZE + 2 * LOCK_INSET; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : autoprocess - +//----------------------------------------------------------------------------- +void IFacePoserToolWindow::SetAutoProcess( bool autoprocess ) +{ + m_bAutoProcess = autoprocess; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool IFacePoserToolWindow::GetAutoProcess( void ) const +{ + return m_bAutoProcess; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool IFacePoserToolWindow::IsActiveTool( void ) +{ + if ( this == s_pActiveTool ) + return true; + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool IFacePoserToolWindow::IsAnyToolScrubbing( void ) +{ + int c = GetToolCount(); + int i; + for ( i = 0; i < c ; i++ ) + { + IFacePoserToolWindow *tool = GetTool( i ); + if ( tool->IsScrubbing() ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool IFacePoserToolWindow::IsAnyToolProcessing( void ) +{ + int c = GetToolCount(); + int i; + for ( i = 0; i < c ; i++ ) + { + IFacePoserToolWindow *tool = GetTool( i ); + if ( tool->IsProcessing() ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool IFacePoserToolWindow::ShouldAutoProcess( void ) +{ + IFacePoserToolWindow *tool = GetActiveTool(); + if ( !tool ) + return false; + + return tool->GetAutoProcess(); +} + +void IFacePoserToolWindow::EnableToolRedraw( bool enabled ) +{ + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + s_bToolsCanDraw = enabled; + + if ( s_bToolsCanDraw ) + { + int c = s_NeedRedraw.Count(); + int i; + for ( i = 0; i < c; i++ ) + { + IFacePoserToolWindow *tool = s_NeedRedraw[ i ]; + tool->GetMxWindow()->redraw(); + } + + s_NeedRedraw.Purge(); + } +} + +bool IFacePoserToolWindow::ToolCanDraw() +{ + if ( !s_bToolsCanDraw ) + { + if ( s_NeedRedraw.Find( this ) == s_NeedRedraw.InvalidIndex() ) + { + s_NeedRedraw.AddToTail( this ); + } + + return false; + } + + return true; +} + +void IFacePoserToolWindow::OnModelChanged() +{ +} + +void IFacePoserToolWindow::ModelChanged() +{ + int c = GetToolCount(); + int i; + for ( i = 0; i < c ; i++ ) + { + IFacePoserToolWindow *tool = GetTool( i ); + tool->OnModelChanged(); + } +} + + diff --git a/utils/hlfaceposer/faceposertoolwindow.h b/utils/hlfaceposer/faceposertoolwindow.h new file mode 100644 index 0000000..ab4dd32 --- /dev/null +++ b/utils/hlfaceposer/faceposertoolwindow.h @@ -0,0 +1,108 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef FACEPOSERTOOLWINDOW_H +#define FACEPOSERTOOLWINDOW_H +#ifdef _WIN32 +#pragma once +#endif + +#include <mxtk/mx.h> +#include "mxtk/mxWindow.h" + +class CChoreoWidgetDrawHelper; + +class IFacePoserToolWindow +{ +public: + IFacePoserToolWindow( char const *toolname, char const *displaynameroot ); + virtual ~IFacePoserToolWindow( void ); + + virtual mxWindow *GetMxWindow( void ); + virtual void Shutdown() { } + + virtual void Think( float dt ); + virtual bool IsScrubbing( void ) const { return false; } + virtual bool IsProcessing( void ) { return false; } + + bool IsActiveTool( void ); + + virtual bool IsLocked( void ); + virtual bool HandleToolEvent( mxEvent *event ); + virtual void HandleToolRedraw( CChoreoWidgetDrawHelper& helper ); + virtual int GetCaptionHeight( void ); + void ToggleLockedState( void ); + + void LoadPosition( void ); + void SavePosition( void ); + + char const *GetToolName( void ) const; + char const *GetWindowTitle( void ) const; + char const *GetDisplayNameRoot( void ) const; + + void SetDisplayNameRoot( char const *name ); + void SetSuffix( char const *suffix ); + void SetPrefix( char const *prefix ); + + void SetUseForMainWindowTitle( bool use ); + + void SetAutoProcess( bool autoprocess ); + bool GetAutoProcess( void ) const; + + virtual void OnModelChanged(); + + static int GetToolCount( void ); + static IFacePoserToolWindow *GetTool( int index ); + + static IFacePoserToolWindow *GetActiveTool( void ); + static void SetActiveTool( IFacePoserToolWindow *tool ); + static IFacePoserToolWindow *s_pActiveTool; + static void ToolThink( float dt ); + static void ModelChanged(); + static bool IsAnyToolScrubbing( void ); + static bool IsAnyToolProcessing( void ); + + static bool ShouldAutoProcess( void ); + + static void InitTools( void ); + static void ShutdownTools( void ); + + static void EnableToolRedraw( bool enabled ); + static bool s_bToolsCanDraw; + + bool ToolCanDraw( void ); + +private: + void GetLockRect( RECT& rc ); + void GetCloseRect( RECT& rc ); + + void ComputeNewTitle( void ); + + void SetToolName( char const *name ); + + enum + { + MAX_TOOL_NAME = 128, + PREFIX_LENGTH = 32, + SUFFIX_LENGTH = 128, + }; + + char m_szToolName[ MAX_TOOL_NAME ]; + char m_szDisplayRoot[ MAX_TOOL_NAME ]; + char m_szPrefix[ PREFIX_LENGTH ]; + char m_szSuffix[ SUFFIX_LENGTH ]; + + char m_szWindowTitle[ MAX_TOOL_NAME + PREFIX_LENGTH + PREFIX_LENGTH ]; + + bool m_bUseForMainWindowTitle; + + bool m_bAutoProcess; + + int m_nToolFrameCount; +}; + +#endif // FACEPOSERTOOLWINDOW_H diff --git a/utils/hlfaceposer/faceposerworkspace.cpp b/utils/hlfaceposer/faceposerworkspace.cpp new file mode 100644 index 0000000..75b8137 --- /dev/null +++ b/utils/hlfaceposer/faceposerworkspace.cpp @@ -0,0 +1,258 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include <windows.h> +#include <stdio.h> +#include "tier1/strtools.h" +#include "ifaceposerworkspace.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CWorkspaceFiles : public IWorkspaceFiles +{ +public: + CWorkspaceFiles( void ); + ~CWorkspaceFiles( void ); + + virtual void Init( char const *pchShortName ); + + // Restore + int GetNumStoredFiles( int type ); + const char *GetStoredFile( int type, int number ); + + // Save + void StartStoringFiles( int type ); + void FinishStoringFiles( int type ); + void StoreFile( int type, const char *filename ); + +private: + static const char *NameForType( int type ); + static int TypeForName( const char *name ); + + LONG CreateWorkspaceKey( char const *pchGameName, PHKEY phKey ); + bool ReadInt( const char *szSubKey, int *value ); + bool WriteInt( const char *szSubKey, int value ); + bool ReadString( const char *szSubKey, char *value, int bufferlen ); + bool WriteString( const char *szSubKey, const char *value ); + + HKEY m_hKeyMain; + + int m_nStoredFiles[ NUM_FILE_TYPES ]; +}; + +static CWorkspaceFiles g_WorkspaceFiles; +IWorkspaceFiles *workspacefiles = ( IWorkspaceFiles * )&g_WorkspaceFiles; + +CWorkspaceFiles::CWorkspaceFiles( void ) : + m_hKeyMain( (HKEY)0 ) +{ + memset( m_nStoredFiles, 0, sizeof( m_nStoredFiles ) ); +} + +CWorkspaceFiles::~CWorkspaceFiles( void ) +{ + if ( (HKEY)0 != m_hKeyMain ) + { + RegCloseKey( m_hKeyMain ); + } +} + +void CWorkspaceFiles::Init( char const *pchShortName ) +{ + CreateWorkspaceKey( pchShortName, &m_hKeyMain ); +} + +const char *CWorkspaceFiles::NameForType( int type ) +{ + switch ( type ) + { + case EXPRESSION: + return "expressionfiles"; + case CHOREODATA: + return "choreodatafiles"; + case MODELDATA: + return "modelfiles"; + default: + break; + } + + return "unknown"; +} + +int CWorkspaceFiles::TypeForName( const char *name ) +{ + if ( !Q_stricmp( name, "expressionfiles" ) ) + { + return EXPRESSION; + } + else if ( !Q_stricmp( name, "choreodatafiles" ) ) + { + return CHOREODATA; + } + else if ( !Q_stricmp( name, "modelfiles" ) ) + { + return MODELDATA; + } + return -1; +} + + +int CWorkspaceFiles::GetNumStoredFiles( int type ) +{ + char szKeyName[ 256 ]; + Q_snprintf( szKeyName, sizeof( szKeyName ), "%s\\total", NameForType( type ) ); + + int num = 0; + ReadInt( szKeyName, &num ); + return num; +} + +const char *CWorkspaceFiles::GetStoredFile( int type, int number ) +{ + char szKeyName[ 256 ]; + sprintf( szKeyName, "%s\\%04i", NameForType( type ), number ); + + static char filename[ 256 ]; + filename[ 0 ] = 0; + ReadString( szKeyName, filename, 256 ); + return filename; +} + +void CWorkspaceFiles::StartStoringFiles( int type ) +{ + m_nStoredFiles[ type ] = 0; +} + +void CWorkspaceFiles::FinishStoringFiles( int type ) +{ + char szKeyName[ 256 ]; + sprintf( szKeyName, "%s\\total", NameForType( type ) ); + + WriteInt( szKeyName, m_nStoredFiles[ type ] ); +} + +void CWorkspaceFiles::StoreFile( int type, const char *filename ) +{ + char szKeyName[ 256 ]; + sprintf( szKeyName, "%s\\%04i", NameForType( type ), m_nStoredFiles[ type ]++ ); + + WriteString( szKeyName, filename ); +} + +LONG CWorkspaceFiles::CreateWorkspaceKey( char const *pchGameName, PHKEY phKey ) +{ + DWORD disp; + + char sz[ 512 ]; + Q_snprintf( sz, sizeof( sz ), "Software\\Valve\\faceposer\\workspace\\%s", pchGameName ); + + return RegCreateKeyEx( + HKEY_CURRENT_USER, // handle of open key + sz, // address of name of subkey to open + 0, // DWORD ulOptions, // reserved + NULL, // Type of value + REG_OPTION_NON_VOLATILE, // Store permanently in reg. + KEY_ALL_ACCESS, // REGSAM samDesired, // security access mask + NULL, + phKey, // Key we are creating + &disp ); // Type of creation +} + +bool CWorkspaceFiles::ReadInt( const char *szSubKey, int *value ) +{ + LONG lResult; // Registry function result code + DWORD dwType; // Type of key + DWORD dwSize; // Size of element data + + dwSize = sizeof( DWORD ); + + lResult = RegQueryValueEx( + m_hKeyMain, // handle to key + szSubKey, // value name + 0, // reserved + &dwType, // type buffer + (LPBYTE)value, // data buffer + &dwSize ); // size of data buffer + + if (lResult != ERROR_SUCCESS) // Failure + return false; + + if (dwType != REG_DWORD) + return false; + + return true; +} + + +bool CWorkspaceFiles::WriteInt( const char *szSubKey, int value ) +{ + LONG lResult; // Registry function result code + DWORD dwSize; // Size of element data + + dwSize = sizeof( DWORD ); + + lResult = RegSetValueEx( + m_hKeyMain, // handle to key + szSubKey, // value name + 0, // reserved + REG_DWORD, // type buffer + (LPBYTE)&value, // data buffer + dwSize ); // size of data buffer + + if (lResult != ERROR_SUCCESS) // Failure + return false; + + return true; +} + +bool CWorkspaceFiles::ReadString( const char *szSubKey, char *value, int buffersize ) +{ + LONG lResult; // Registry function result code + DWORD dwType; // Type of key + DWORD dwSize; // Size of element data + + dwSize = buffersize; + + lResult = RegQueryValueEx( + m_hKeyMain, // handle to key + szSubKey, // value name + 0, // reserved + &dwType, // type buffer + (LPBYTE)value, // data buffer + &dwSize ); // size of data buffer + + if (lResult != ERROR_SUCCESS) // Failure + return false; + + if (dwType != REG_SZ) + return false; + + return true; +} + + +bool CWorkspaceFiles::WriteString( const char *szSubKey, const char *value ) +{ + LONG lResult; // Registry function result code + DWORD dwSize; // Size of element data + + dwSize = strlen( value ) + 1; + + lResult = RegSetValueEx( + m_hKeyMain, // handle to key + szSubKey, // value name + 0, // reserved + REG_SZ, // type buffer + (LPBYTE)value, // data buffer + dwSize ); // size of data buffer + + if (lResult != ERROR_SUCCESS) // Failure + return false; + + return true; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/fileloaderthread.cpp b/utils/hlfaceposer/fileloaderthread.cpp new file mode 100644 index 0000000..df70e31 --- /dev/null +++ b/utils/hlfaceposer/fileloaderthread.cpp @@ -0,0 +1,422 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "sentence.h" +#include "wavefile.h" +#include "tier2/riff.h" +#include "filesystem.h" +#include <io.h> +#include <fcntl.h> +#include <sys/types.h> +#include "IFileLoader.h" + +bool SceneManager_LoadSentenceFromWavFileUsingIO( char const *wavfile, CSentence& sentence, IFileReadBinary& io ); + +//----------------------------------------------------------------------------- +// Purpose: Implements the RIFF i/o interface on stdio +//----------------------------------------------------------------------------- +class ThreadIOReadBinary : public IFileReadBinary +{ +public: + int open( const char *pFileName ) + { + char filename[ 512 ]; + // POSSIBLE BUG: THIS MIGHT NOT BE THREAD SAFE!!! + filesystem->RelativePathToFullPath( pFileName, "GAME", filename, sizeof( filename ) ); + return (int)_open( filename, _O_BINARY | _O_RDONLY ); + } + + int read( void *pOutput, int size, int file ) + { + if ( !file ) + return 0; + + return _read( file, pOutput, size ); + } + + void seek( int file, int pos ) + { + if ( !file ) + return; + + _lseek( file, pos, SEEK_SET ); + } + + unsigned int tell( int file ) + { + if ( !file ) + return 0; + + return _tell( file ); + } + + unsigned int size( int file ) + { + if ( !file ) + return 0; + + long curpos = tell( file ); + _lseek( file, 0, SEEK_END ); + int s = tell( file ); + _lseek( file, curpos, SEEK_SET ); + + return s; + } + + void close( int file ) + { + if ( !file ) + return; + + _close( file ); + } +}; + +//----------------------------------------------------------------------------- +// Purpose: All wavefile I/O occurs on a thread +//----------------------------------------------------------------------------- +class CFileLoaderThread : public IFileLoader +{ +public: + struct SentenceRequest + { + SentenceRequest() + { + filename[ 0 ] = 0; + sentence.Reset(); + wavefile = NULL; + valid = false; + } + + bool valid; + char filename[ 256 ]; + CSentence sentence; + + CWaveFile *wavefile; + }; + + // Construction + CFileLoaderThread( void ); + virtual ~CFileLoaderThread( void ); + + // Sockets add/remove themselves via their constructor + virtual void AddWaveFilesToThread( CUtlVector< CWaveFile * >& wavefiles ); + + // Lock changes to wavefile list, etc. + virtual void Lock( void ); + // Unlock wavefile list, etc. + virtual void Unlock( void ); + + // Retrieve handle to shutdown event + virtual HANDLE GetShutdownHandle( void ); + + // Caller should call lock before accessing any of these methods and unlock afterwards!!! + virtual int ProcessCompleted(); + + int DoThreadWork(); + + virtual void Start(); + + virtual int GetPendingLoadCount(); +private: + // Critical section used for synchronizing access to wavefile list + CRITICAL_SECTION cs; + CRITICAL_SECTION m_CountCS; + + // List of wavefiles we are listening on + CUtlVector< SentenceRequest * > m_FileList; + + CUtlVector< SentenceRequest * > m_Pending; + CUtlVector< SentenceRequest * > m_Completed; + // Thread handle + HANDLE m_hThread; + // Thread id + DWORD m_nThreadId; + // Event to set when we want to tell the thread to shut itself down + HANDLE m_hShutdown; + + ThreadIOReadBinary m_ThreadIO; + bool m_bLocked; + + int m_nTotalAdds; + int m_nTotalPending; + int m_nTotalProcessed; + int m_nTotalCompleted; + + HANDLE m_hNewItems; +}; + +// Singleton handler +static CFileLoaderThread g_WaveLoader; +extern IFileLoader *fileloader = &g_WaveLoader; + +int CFileLoaderThread::DoThreadWork() +{ + int i; + // Check for shutdown event + if ( WAIT_OBJECT_0 == WaitForSingleObject( GetShutdownHandle(), 0 ) ) + { + return 0; + } + + // No changes to list right now + Lock(); + // Move new items to work list + int newItems = m_FileList.Count(); + for ( i = 0; i < newItems; i++ ) + { + // Move to pending and issue async i/o calls + m_Pending.AddToHead( m_FileList[ i ] ); + + EnterCriticalSection( &m_CountCS ); + m_nTotalPending++; + LeaveCriticalSection( &m_CountCS ); + } + m_FileList.RemoveAll(); + // Done adding new work items + Unlock(); + + int remaining = m_Pending.Count(); + if ( !remaining ) + return 1; + + int workitems = remaining; // min( remaining, 1000 ); + + CUtlVector< SentenceRequest * > transfer; + + for ( i = 0; i < workitems; i++ ) + { + SentenceRequest *r = m_Pending[ 0 ]; + m_Pending.Remove( 0 ); + + transfer.AddToTail( r ); + + // Do the work + EnterCriticalSection( &m_CountCS ); + m_nTotalProcessed++; + LeaveCriticalSection( &m_CountCS ); + + Lock(); + bool load = !r->wavefile->HasLoadedSentenceInfo(); + Unlock(); + + if ( load ) + { + r->valid = SceneManager_LoadSentenceFromWavFileUsingIO( r->filename, r->sentence, m_ThreadIO ); + } + else + { + r->valid = true; + } + + if ( WaitForSingleObject( m_hNewItems, 0 ) == WAIT_OBJECT_0 ) + { + ResetEvent( m_hNewItems ); + break; + } + } + + // Now move to completed list + Lock(); + int c = transfer.Count(); + + for ( i = 0; i < c; ++i ) + { + SentenceRequest *r = transfer[ i ]; + if ( r->valid ) + { + + m_nTotalCompleted++; + + + m_Completed.AddToTail( r ); + } + else + { + delete r; + } + } + Unlock(); + return 1; +} + +int CFileLoaderThread::ProcessCompleted() +{ + Lock(); + int c = m_Completed.Count(); + for ( int i = c - 1; i >= 0 ; i-- ) + { + SentenceRequest *r = m_Completed[ i ]; + + if ( !r->wavefile->HasLoadedSentenceInfo() ) + { + r->wavefile->SetThreadLoadedSentence( r->sentence ); + } + + delete r; + } + m_Completed.RemoveAll(); + Unlock(); + return c; +} + + +//----------------------------------------------------------------------------- +// Purpose: Main winsock processing thread +// Input : threadobject - +// Output : static DWORD WINAPI +//----------------------------------------------------------------------------- +static DWORD WINAPI FileLoaderThreadFunc( LPVOID threadobject ) +{ + // Get pointer to CFileLoaderThread object + CFileLoaderThread *wavefilethread = ( CFileLoaderThread * )threadobject; + Assert( wavefilethread ); + if ( !wavefilethread ) + { + return 0; + } + + // Keep looking for data until shutdown event is triggered + while ( 1 ) + { + if( !wavefilethread->DoThreadWork() ) + break; + + // Yield a small bit of time to main app + Sleep( 10 ); + } + + ExitThread( 0 ); + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Construction +//----------------------------------------------------------------------------- +CFileLoaderThread::CFileLoaderThread( void ) +{ + m_nTotalAdds = 0; + m_nTotalProcessed = 0; + m_nTotalCompleted = 0; + m_nTotalPending = 0; + + m_bLocked = false; + + InitializeCriticalSection( &cs ); + InitializeCriticalSection( &m_CountCS ); + + m_hShutdown = CreateEvent( NULL, TRUE, FALSE, NULL ); + Assert( m_hShutdown ); + + m_hThread = NULL; + + m_hNewItems = CreateEvent( NULL, TRUE, FALSE, NULL ); + + Start(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFileLoaderThread::Start() +{ + m_hThread = CreateThread( NULL, 0, FileLoaderThreadFunc, (void *)this, 0, &m_nThreadId ); + Assert( m_hThread ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CFileLoaderThread::~CFileLoaderThread( void ) +{ + Lock(); + { + SetEvent( m_hShutdown ); + Sleep( 2 ); + TerminateThread( m_hThread, 0 ); + } + Unlock(); + + // Kill the wavefile +//!! need to validate this line +// Assert( !m_FileList ); + + CloseHandle( m_hThread ); + + CloseHandle( m_hShutdown ); + + DeleteCriticalSection( &cs ); + DeleteCriticalSection( &m_CountCS ); + + CloseHandle( m_hNewItems ); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns handle of shutdown event +// Output : HANDLE +//----------------------------------------------------------------------------- +HANDLE CFileLoaderThread::GetShutdownHandle( void ) +{ + return m_hShutdown; +} + +//----------------------------------------------------------------------------- +// Purpose: Locks object and adds wavefile to thread +// Input : *wavefile - +//----------------------------------------------------------------------------- +void CFileLoaderThread::AddWaveFilesToThread( CUtlVector< CWaveFile * >& wavefiles ) +{ + Lock(); + int c = wavefiles.Count(); + for ( int i = 0; i < c; i++ ) + { + SentenceRequest *request = new SentenceRequest; + request->wavefile = wavefiles[ i ]; + Q_strncpy( request->filename, request->wavefile->GetFileName(), sizeof( request->filename ) ); + + m_FileList.AddToTail( request ); + + m_nTotalAdds++; + } + + SetEvent( m_hNewItems ); + Unlock(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFileLoaderThread::Lock( void ) +{ + EnterCriticalSection( &cs ); + Assert( !m_bLocked ); + m_bLocked = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFileLoaderThread::Unlock( void ) +{ + Assert( m_bLocked ); + m_bLocked = false; + LeaveCriticalSection( &cs ); +} + +int CFileLoaderThread::GetPendingLoadCount() +{ + int iret = 0; + + EnterCriticalSection( &m_CountCS ); + + iret = m_nTotalPending - m_nTotalProcessed; + + LeaveCriticalSection( &m_CountCS ); + + return iret; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/flexpanel.cpp b/utils/hlfaceposer/flexpanel.cpp new file mode 100644 index 0000000..c268d51 --- /dev/null +++ b/utils/hlfaceposer/flexpanel.cpp @@ -0,0 +1,1092 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//===========================================================================// + + +#include "hlfaceposer.h" +#include "FlexPanel.h" +#include "ViewerSettings.h" +#include "StudioModel.h" +#include "MatSysWin.h" +#include "ControlPanel.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <mxtk/mx.h> +#include <mxtk/mxBmp.h> + +#include "mxbitmapwindow.h" +#include "mxExpressionTray.h" +#include "expressions.h" +#include "expressiontool.h" +#include "filesystem.h" +#include "mdlviewer.h" +#include "ExpressionProperties.h" +#include "expclass.h" +#include "choreowidgetdrawhelper.h" +#include "choreoview.h" +#include "choreoscene.h" +#include "mxExpressionSlider.h" +#include "faceposer_models.h" + +LocalFlexController_t FindFlexControllerIndexByName( StudioModel *model, char const *searchname ); +char const *GetGlobalFlexControllerName( int index ); + +extern char g_appTitle[]; + +#define LINE_HEIGHT 20 + +#define FLEXSLIDER_INVALID_INDEX -1 + +FlexPanel *g_pFlexPanel = 0; + +void FlexPanel::PositionControls( int width, int height ) +{ + int buttonwidth = 80; + int buttonx = 3; + int row = height - 18; + int buttonheight = 18; + + btnResetSliders->setBounds( buttonx, row, buttonwidth, buttonheight ); + + buttonx += buttonwidth + 5; + btnCopyToSliders->setBounds( buttonx, row, buttonwidth, buttonheight ); + + buttonx += buttonwidth + 5; + buttonwidth = 100; + btnCopyFromSliders->setBounds( buttonx, row, buttonwidth, buttonheight ); + + buttonx += buttonwidth + 5; + buttonwidth = 100; + + btnMenu->setBounds( buttonx, row, buttonwidth, buttonheight ); +} + +FlexPanel::FlexPanel (mxWindow *parent) +: IFacePoserToolWindow( "FlexPanel", "Flex Sliders" ), mxWindow( parent, 0, 0, 0, 0 ) +{ + m_nViewableFlexControllerCount = 0; + + m_bNewExpressionMode = true; + + btnResetSliders = new mxButton( this, 0, 0, 100, 20, "Zero Sliders", IDC_EXPRESSIONRESET ); + + btnCopyToSliders = new mxButton( this, 0, 0, 100, 20, "Get Tracks", IDC_COPY_TO_FLEX ); + btnCopyFromSliders = new mxButton( this, 0, 0, 100, 20, "Make Keyframe", IDC_COPY_FROM_FLEX ); + btnMenu = new mxButton( this, 0, 0, 100, 20, "Menu", IDC_FP_MENU ); + + mxWindow *wFlex = this; + for (int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++) + { + int w = 5; // (i / 4) * 156 + 5; + int h = i * LINE_HEIGHT + 5; // (i % 4) * 20 + 5; + + slFlexScale[i] = new mxExpressionSlider (wFlex, w, h, 220, LINE_HEIGHT, IDC_FLEXSCALE + i); + } + + slScrollbar = new mxScrollbar( wFlex, 0, 0, 18, 100, IDC_FLEXSCROLL, mxScrollbar::Vertical ); + slScrollbar->setRange( 0, GLOBAL_STUDIO_FLEX_CONTROL_COUNT * LINE_HEIGHT ); + slScrollbar->setPagesize( 100 ); +} + + + +FlexPanel::~FlexPanel() +{ +} + +void FlexPanel::redraw() +{ + if ( !ToolCanDraw() ) + return; + + CChoreoWidgetDrawHelper helper( this, GetSysColor( COLOR_BTNFACE ) ); + HandleToolRedraw( helper ); + + BaseClass::redraw(); +} + +void FlexPanel::PositionSliders( int sboffset ) +{ + int reservedheight = GetCaptionHeight() + 5 /*gap at top*/ + 1 * 20 /* space for buttons/edit controls*/; + + int widthofslidercolumn = slFlexScale[ 0 ]->w() + 10; + + int colsavailable = ( this->w2() - 20 /*scrollbar*/ - 10 /*left edge gap + right gap*/ ) / widthofslidercolumn; + // Need at least one column + colsavailable = max( colsavailable, 1 ); + + int rowsneeded = GLOBAL_STUDIO_FLEX_CONTROL_COUNT; + + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if ( hdr ) + { + rowsneeded = m_nViewableFlexControllerCount; + } + + int rowsvisible = ( this->h2() - reservedheight ) / LINE_HEIGHT; + + int rowspercol = rowsvisible; + + if ( rowsvisible * colsavailable < rowsneeded ) + { + // Figure out how many controls should go in each available column + rowspercol = (rowsneeded + (colsavailable - 1)) / colsavailable; + + slScrollbar->setPagesize( rowsvisible * LINE_HEIGHT ); + slScrollbar->setRange( 0, rowspercol * LINE_HEIGHT ); + } + + int row = 0; + int col = 0; + for (int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++) + { + int x = 5 + col * widthofslidercolumn; + int y = row * LINE_HEIGHT + 5 + GetCaptionHeight() - sboffset; // (i % 4) * 20 + 5; + + slFlexScale[ i ]->setBounds( x, y, slFlexScale[i]->w(), slFlexScale[i]->h() ); + + if ( i >= rowsneeded || + ( y + LINE_HEIGHT - 5 > ( this->h2() - reservedheight ) ) ) + { + slFlexScale[ i ]->setVisible( false ); + } + else + { + slFlexScale[ i ]->setVisible( true ); + } + + row++; + if ( row >= rowspercol ) + { + col++; + row = 0; + } + } +} + +int FlexPanel::handleEvent (mxEvent *event) +{ + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + int iret = 0; + + if ( HandleToolEvent( event ) ) + { + return iret; + } + + switch ( event->event ) + { + case mxEvent::Size: + { + int trueh = h2() - GetCaptionHeight(); + PositionControls( w2(), h2() ); + slScrollbar->setPagesize( trueh ); + slScrollbar->setBounds( w2() - 18, GetCaptionHeight(), 18, trueh ); + PositionSliders( 0 ); + iret = 1; + } + break; + case mxEvent::Action: + { + iret = 1; + switch ( event->action ) + { + default: + iret = 0; + break; + case IDC_FLEXSCROLL: + { + if ( event->event == mxEvent::Action && + event->modifiers == SB_THUMBTRACK) + { + int offset = event->height; // ((mxScrollbar *) event->widget)->getValue( ); + + slScrollbar->setValue( offset ); // if (offset > slScrollbar->getPagesize() + + PositionSliders( offset ); + + IFacePoserToolWindow::SetActiveTool( this ); + } + } + break; + case IDC_EXPRESSIONRESET: + { + ResetSliders( true, true ); + IFacePoserToolWindow::SetActiveTool( this ); + } + break; + + case IDC_COPY_TO_FLEX: + { + g_pExpressionTool->OnCopyToFlex( g_pChoreoView->GetScene()->GetTime(), true ); + } + break; + case IDC_COPY_FROM_FLEX: + { + g_pExpressionTool->OnCopyFromFlex( g_pChoreoView->GetScene()->GetTime(), false ); + } + break; + case IDC_FP_UNCHECK_ALL: + { + OnSetAll( FP_STATE_UNCHECK ); + } + break; + case IDC_FP_CHECK_ALL: + { + OnSetAll( FP_STATE_CHECK ); + } + break; + case IDC_FP_INVERT: + { + OnSetAll( FP_STATE_INVERT ); + } + break; + case IDC_FP_MENU: + { + OnMenu(); + } + break; + } + + if ( event->action >= IDC_FLEXSCALE && event->action < IDC_FLEXSCALE + GLOBAL_STUDIO_FLEX_CONTROL_COUNT) + { + iret = 1; + + bool pushundo = false; + + mxExpressionSlider *slider = ( mxExpressionSlider * )event->widget; + int barnumber = event->height; + int slidernum = ( event->action - IDC_FLEXSCALE ); + + float value = slider->getValue ( barnumber ); + float influ = slider->getInfluence( ); + + switch( event->modifiers ) + { + case SB_THUMBPOSITION: + case SB_THUMBTRACK: + break; + case SB_ENDSCROLL: + pushundo = true; + break; + } + int flex = LookupFlex( slidernum, barnumber ); + int flex2 = LookupPairedFlex( flex ); + float value2 = GetSlider( flex2 ); + + CExpClass *active = expressions->GetActiveClass(); + if ( active ) + { + int index = active->GetSelectedExpression(); + if ( pushundo && index != -1 ) + { + CExpression *exp = active->GetExpression( index ); + if ( exp ) + { + float *settings = exp->GetSettings(); + float *weights = exp->GetWeights(); + Assert( settings ); + + if ( settings[ flex ] != value || + settings[ flex2 ] != value2 || + weights[ flex ] != influ ) + { + exp->PushUndoInformation(); + + active->SetDirty( true ); + + settings[ flex ] = value; + settings[ flex2 ] = value2; + weights[ flex ] = influ; + weights[ flex2 ] = influ; + + exp->PushRedoInformation(); + + g_pExpressionTrayTool->redraw(); + } + } + } + } + + // FIXME: Needs to drive the current actor, not model + + // Go from global to local indices + LocalFlexController_t localflex = FindFlexControllerIndexByName( models->GetActiveStudioModel(), GetGlobalFlexControllerName( flex ) ); + if ( localflex >= 0 ) + { + // Update the face + // FIXME: I'm not sure this is needed anymore.... + models->GetActiveStudioModel()->SetFlexController( localflex, value * influ ); + if (flex2 != flex) + { + LocalFlexController_t localflex2 = FindFlexControllerIndexByName( models->GetActiveStudioModel(), GetGlobalFlexControllerName( flex2 ) ); + if ( localflex2 >= 0 ) + { + models->GetActiveStudioModel()->SetFlexController( localflex2, value2 * influ ); + } + else + { + Assert( 0 ); + } + } + } + else + { + Assert( 0 ); + } + + models->SetSolveHeadTurn( 1 ); + IFacePoserToolWindow::SetActiveTool( this ); + } + } + } + + return iret; +} + +void FlexPanel::initFlexes() +{ + m_nViewableFlexControllerCount = 0; + + memset( nFlexSliderIndex, 0, sizeof( nFlexSliderIndex ) ); + memset( nFlexSliderBarnum, 0, sizeof( nFlexSliderBarnum ) ); + + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if (hdr) + { + for (int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++) + { + slFlexScale[i]->setVisible( false ); + slFlexScale[i]->setLabel( "" ); + slFlexScale[i]->SetMode( false ); + // init to invalid slider index + nFlexSliderIndex[i] = FLEXSLIDER_INVALID_INDEX; + } + + // J is the slider number we're filling in + int j = 0; + for ( LocalFlexController_t k = LocalFlexController_t(0); k < hdr->numflexcontrollers(); k++ ) + { + // Lookup global flex controller index + int controller = hdr->pFlexcontroller( k )->localToGlobal; + Assert( controller != -1 ); + + // Con_Printf( "%i Setting up %s global %i\n", k, hdr->pFlexcontroller(k)->pszName(), controller ); + + slFlexScale[j]->setLabel( hdr->pFlexcontroller(k)->pszName() ); + + if ( nFlexSliderIndex[controller] == FLEXSLIDER_INVALID_INDEX ) + { + //Con_Printf( "Assigning bar %i to barnum %i of slider %s for controller %i\n", + // j, 0, hdr->pFlexcontroller(k)->pszName(), controller ); + + nFlexSliderIndex[controller] = j; + nFlexSliderBarnum[controller] = 0; + } + else + { + Assert( 0 ); + } + + if (hdr->pFlexcontroller(k)->min != hdr->pFlexcontroller(k)->max) + slFlexScale[j]->setRange( 0, hdr->pFlexcontroller(k)->min, hdr->pFlexcontroller(k)->max ); + + if (strncmp( "right_", hdr->pFlexcontroller(k)->pszName(), 6 ) == 0) + { + if (hdr->pFlexcontroller(k)->min != hdr->pFlexcontroller(k)->max) + slFlexScale[j]->setRange( 1, 0.0f, 1.0f ); + slFlexScale[j]->setLabel( &hdr->pFlexcontroller(k)->pszName()[6] ); + + slFlexScale[j]->SetMode( true ); + k++; + controller = hdr->pFlexcontroller( k )->localToGlobal; + Assert( controller != -1 ); + if ( nFlexSliderIndex[controller] == FLEXSLIDER_INVALID_INDEX ) + { + nFlexSliderIndex[controller] = j; + nFlexSliderBarnum[controller] = 1; + + //Con_Printf( "Assigning stereo side of bar %i to barnum %i of slider %s for controller\n", + // j, 1, hdr->pFlexcontroller(k)->pszName(), controller ); + + } + else + { + Assert(0); + } + } + m_nViewableFlexControllerCount++; + + slFlexScale[j]->setVisible( true ); + slFlexScale[j]->redraw(); + + j++; + } + } + + slScrollbar->setRange( 0, m_nViewableFlexControllerCount * LINE_HEIGHT + 5 ); + + int trueh = h2() - GetCaptionHeight(); + PositionControls( w2(), h2() ); + slScrollbar->setPagesize( trueh ); + slScrollbar->setBounds( w2() - 18, GetCaptionHeight(), 18, trueh ); + PositionSliders( 0 ); +} + + +void FlexPanel::OnModelChanged() +{ + ResetSliders( true, false ); + SetEvent( NULL ); + redraw(); +} + +void FlexPanel::SetEvent( CChoreoEvent *event ) +{ + bool bUpdateSliders = false; + + if ( event != NULL ) + { + CChoreoScene *scene = event->GetScene(); + StudioModel *model = FindAssociatedModel( scene, event->GetActor() ); + + if (model == models->GetActiveStudioModel()) + { + bUpdateSliders = true; + } + } + + btnCopyToSliders->setEnabled( bUpdateSliders ); + btnCopyFromSliders->setEnabled( bUpdateSliders ); + return; +} + + +bool FlexPanel::IsValidSlider( int iFlexController ) const +{ + if ( nFlexSliderIndex[ iFlexController ] == FLEXSLIDER_INVALID_INDEX ) + return false; + + return true; +} + +float +FlexPanel::GetSlider( int iFlexController ) +{ + if ( !IsValidSlider( iFlexController ) ) + { + Msg( "GetSlider(%d) invalid controller index\n", iFlexController ); + return 0.0f; + } + + return slFlexScale[ nFlexSliderIndex[ iFlexController ] ]->getValue( nFlexSliderBarnum[ iFlexController ] ); +} + +float FlexPanel::GetSliderRawValue( int iFlexController ) +{ + if ( !IsValidSlider( iFlexController ) ) + { + Msg( "GetSliderRawValue(%d) invalid controller index\n", iFlexController ); + return 0.0f; + } + + return slFlexScale[ nFlexSliderIndex[ iFlexController ] ]->getRawValue( nFlexSliderBarnum[ iFlexController ] ); +} + +void FlexPanel::GetSliderRange( int iFlexController, float& minvalue, float& maxvalue ) +{ + int barnum = nFlexSliderBarnum[ iFlexController ]; + + if ( !IsValidSlider( iFlexController ) ) + { + Msg( "GetSliderRange(%d) invalid controller index\n", iFlexController ); + + minvalue = 0.0f; + maxvalue = 1.0f; + return; + } + + mxExpressionSlider *sl = slFlexScale[ nFlexSliderIndex[ iFlexController ] ]; + Assert( sl ); + minvalue = sl->getMinValue( barnum ); + maxvalue = sl->getMaxValue( barnum ); +} + +void +FlexPanel::SetSlider( int iFlexController, float value ) +{ + if ( !IsValidSlider( iFlexController ) ) + { + Msg( "SetSlider(%d) invalid controller index\n", iFlexController ); + return; + } + + slFlexScale[ nFlexSliderIndex[ iFlexController ] ]->setValue( nFlexSliderBarnum[ iFlexController ], value ); +} + +float +FlexPanel::GetInfluence( int iFlexController ) +{ + if ( !IsValidSlider( iFlexController ) ) + { + Msg( "GetInfluence(%d) invalid controller index\n", iFlexController ); + return 0.0f; + } + + return slFlexScale[ nFlexSliderIndex[ iFlexController ] ]->getInfluence( ); +} + +void +FlexPanel::SetEdited( int iFlexController, bool isEdited ) +{ + if ( !IsValidSlider( iFlexController ) ) + { + Msg( "IsEdited(%d) invalid controller index\n", iFlexController ); + return; + } + + slFlexScale[ nFlexSliderIndex[ iFlexController ] ]->setEdited( nFlexSliderBarnum[ iFlexController ], isEdited ); +} + +bool +FlexPanel::IsEdited( int iFlexController ) +{ + if ( !IsValidSlider( iFlexController ) ) + { + Msg( "IsEdited(%d) invalid controller index\n", iFlexController ); + return 0.0f; + } + + return slFlexScale[ nFlexSliderIndex[ iFlexController ] ]->isEdited( nFlexSliderBarnum[ iFlexController ] ); +} + +void +FlexPanel::SetInfluence( int iFlexController, float value ) +{ + if ( !IsValidSlider( iFlexController ) ) + { + Msg( "SetInfluence(%d) invalid controller index\n", iFlexController ); + return; + } + + // Con_Printf( "SetInfluence( %d, %.0f ) : %d %d\n", iFlexController, value, nFlexSliderIndex[ iFlexController ], nFlexSliderBarnum[ iFlexController ] ); + if ( nFlexSliderBarnum[ iFlexController ] == 0) + { + slFlexScale[ nFlexSliderIndex[ iFlexController ] ]->setInfluence( value ); + } +} + +int +FlexPanel::LookupFlex( int iSlider, int barnum ) +{ + for (int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++) + { + if (nFlexSliderIndex[i] == iSlider && nFlexSliderBarnum[i] == barnum) + { + // char const *name = GetGlobalFlexControllerName( i ); + //Con_Printf( "lookup slider %i bar %i == %s\n", + //iSlider, barnum, name ); + + return i; + } + } + + Con_Printf( "lookup slider %i bar %i failed\n", + iSlider, barnum); + return 0; +} + + +int +FlexPanel::LookupPairedFlex( int iFlexController ) +{ + if ( !IsValidSlider( iFlexController ) ) + { + Msg( "LookupPairedFlex(%d) invalid controller index\n", iFlexController ); + return iFlexController; + } + + if (nFlexSliderBarnum[ iFlexController ] == 1) + { + return iFlexController - 1; + } + else if (nFlexSliderIndex[ iFlexController + 1 ] == nFlexSliderIndex[ iFlexController ]) + { + return iFlexController + 1; + } + return iFlexController; +} + +void +FlexPanel::setExpression( int index ) +{ + if ( !models->GetActiveStudioModel() ) + return; + + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if ( !hdr ) + return; + + CExpClass *active = expressions->GetActiveClass(); + if ( !active ) + return; + + CExpression *exp = active->GetExpression( index ); + if ( !exp ) + return; + + // Con_Printf( "Setting expression to %i:'%s'\n", index, exp->name ); + + float *settings = exp->GetSettings(); + float *weights = exp->GetWeights(); + Assert( settings ); + Assert( weights ); + + for (LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++) + { + int j = hdr->pFlexcontroller( i )->localToGlobal; + if ( j == -1 ) + continue; + + //if ( weights[j] > 0.0f ) + //{ + // Con_Printf( "%i Setting %s to %f\n", j, GetGlobalFlexControllerName( j ), + // settings[ j ] ); + //} + + SetSlider( j, settings[j] ); + SetInfluence( j, weights[j] ); + models->GetActiveStudioModel()->SetFlexController( i, settings[j] * weights[j] ); + } +} + +void FlexPanel::DeleteExpression( int index ) +{ + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if ( !hdr ) + return; + CExpClass *active = expressions->GetActiveClass(); + if ( !active ) + return; + + CExpression *exp = active->GetExpression( index ); + if ( !exp ) + return; + + active->DeleteExpression( exp->name ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : index - +//----------------------------------------------------------------------------- +void FlexPanel::RevertExpression( int index ) +{ + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if ( !hdr ) + return; + + CExpClass *active = expressions->GetActiveClass(); + if ( !active ) + return; + + CExpression *exp = active->GetExpression( index ); + if ( !exp ) + return; + + exp->Revert(); + setExpression( index ); + g_pExpressionTrayTool->redraw(); +} + +void FlexPanel::SaveExpression( int index ) +{ + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if ( !hdr ) + return; + + CExpClass *active = expressions->GetActiveClass(); + if ( !active ) + return; + + CExpression *exp = active->GetExpression( index ); + if ( !exp ) + return; + + int retval = mxMessageBox( this, "Overwrite existing expression?", g_appTitle, MX_MB_YESNO | MX_MB_QUESTION ); + if ( retval != 0 ) + return; + + float *settings = exp->GetSettings(); + float *weights = exp->GetWeights(); + Assert( settings ); + Assert( weights ); + for ( LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++ ) + { + int j = hdr->pFlexcontroller( i )->localToGlobal; + + settings[ j ] = GetSlider( j ); + weights[ j ] = GetInfluence( j ); + } + + exp->CreateNewBitmap( models->GetActiveModelIndex() ); + + exp->ResetUndo(); + + exp->SetDirty( false ); + + g_pExpressionTrayTool->redraw(); +} + +void FlexPanel::CopyControllerSettingsToStructure( CExpression *exp ) +{ + Assert( exp ); + + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if ( hdr ) + { + float *settings = exp->GetSettings(); + float *weights = exp->GetWeights(); + + for (LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++) + { + int j = hdr->pFlexcontroller( i )->localToGlobal; + + settings[ j ] = GetSlider( j ); + weights[ j ] = GetInfluence( j ); + } + } +} + +void FlexPanel::OnSetAll( int state ) +{ + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if ( hdr ) + { + for (LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++) + { + int j = hdr->pFlexcontroller( i )->localToGlobal; + + float setting = GetSlider( j ); + float influence = GetInfluence( j ); + switch ( state ) + { + default: + Assert( 0 ); + break; + case FP_STATE_UNCHECK: + influence = 0.0f; + break; + case FP_STATE_CHECK: + influence = 1.0f; + break; + case FP_STATE_INVERT: + influence = 1.0f - influence; + break; + } + + SetInfluence( j, influence ); + models->GetActiveStudioModel()->SetFlexController( i, setting * influence ); + } + } +} + +void FlexPanel::ResetSliders( bool preserveundo, bool bDirtyClass ) +{ + CExpClass *active = expressions->GetActiveClass(); + + bool needredo = false; + CExpression zeroes; + + CExpression *exp = NULL; + if ( active ) + { + int index = active->GetSelectedExpression(); + if ( index != -1 ) + { + exp = active->GetExpression( index ); + if ( exp ) + { + float *settings = exp->GetSettings(); + Assert( settings ); + + if ( memcmp( settings, zeroes.GetSettings(), GLOBAL_STUDIO_FLEX_CONTROL_COUNT * sizeof( float ) ) ) + { + if ( preserveundo ) + { + exp->PushUndoInformation(); + needredo = true; + } + + if ( bDirtyClass ) + { + active->SetDirty( true ); + } + + g_pExpressionTrayTool->redraw(); + } + } + } + } + + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if ( hdr ) + { + if( exp ) + { + float *settings = exp->GetSettings(); + float *weights = exp->GetWeights(); + + Assert( settings && weights ); + + for ( int i = 0; i < GLOBAL_STUDIO_FLEX_CONTROL_COUNT; i++ ) + { + settings[ i ] = 0.0f; + weights[ i ] = 0.0f; + } + } + + for ( LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++ ) + { + int j = hdr->pFlexcontroller( i )->localToGlobal; + + if ( j == -1 ) + continue; + + SetSlider( j, 0.0f ); + SetInfluence( j, 0.0f ); + SetEdited( j, false ); + models->GetActiveStudioModel()->SetFlexController( i, 0.0f ); + } + } + + if ( exp && needredo && preserveundo ) + { + exp->PushRedoInformation(); + } +} + +void FlexPanel::CopyControllerSettings( void ) +{ + CExpression *exp = expressions->GetCopyBuffer(); + memset( exp, 0, sizeof( *exp ) ); + CopyControllerSettingsToStructure( exp ); +} + +void FlexPanel::PasteControllerSettings( void ) +{ + CExpClass *active = expressions->GetActiveClass(); + if ( !active ) + return; + + bool needredo = false; + CExpression *paste = expressions->GetCopyBuffer(); + if ( !paste ) + return; + + CExpression *exp = NULL; + int index = active->GetSelectedExpression(); + if ( index != -1 ) + { + exp = active->GetExpression( index ); + if ( exp ) + { + float *settings = exp->GetSettings(); + Assert( settings ); + + // UPDATEME + if ( memcmp( settings, paste->GetSettings(), GLOBAL_STUDIO_FLEX_CONTROL_COUNT * sizeof( float ) ) ) + { + exp->PushUndoInformation(); + needredo = true; + + active->SetDirty( true ); + + g_pExpressionTrayTool->redraw(); + } + } + } + + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if ( hdr ) + { + float *settings = paste->GetSettings(); + float *weights = paste->GetWeights(); + Assert( settings ); + Assert( weights ); + + for (LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++) + { + int j = hdr->pFlexcontroller( i )->localToGlobal; + + SetSlider( j, settings[j] ); + SetInfluence( j, weights[j] ); + models->GetActiveStudioModel()->SetFlexController( i, settings[j] * weights[j] ); + } + } + + if ( exp && needredo ) + { + exp->PushRedoInformation(); + } + +} + +void FlexPanel::EditExpression( void ) +{ + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if ( !hdr ) + { + Con_ErrorPrintf( "Can't edit face pose, must load a model first!\n" ); + return; + } + + CExpClass *active = expressions->GetActiveClass(); + if ( !active ) + return; + + int index = active->GetSelectedExpression(); + if ( index == -1 ) + { + Con_ErrorPrintf( "Can't edit face pose, must select a face from list first!\n" ); + return; + } + + CExpression *exp = active->GetExpression( index ); + if ( !exp ) + { + return; + } + + bool namechanged = false; + CExpressionParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Edit Expression" ); + strcpy( params.m_szName, exp->name ); + strcpy( params.m_szDescription, exp->description ); + + if ( !ExpressionProperties( ¶ms ) ) + return; + + namechanged = stricmp( exp->name, params.m_szName ) ? true : false; + + if ( ( strlen( params.m_szName ) <= 0 ) || + !stricmp( params.m_szName, "unnamed" ) ) + { + Con_ErrorPrintf( "You must type in a valid name\n" ); + return; + } + + if ( ( strlen( params.m_szDescription ) <= 0 ) || + !stricmp( params.m_szDescription, "description" ) ) + { + Con_ErrorPrintf( "You must type in a valid description\n" ); + return; + } + + if ( namechanged ) + { + Con_Printf( "Deleting old bitmap %s\n", exp->GetBitmapFilename( models->GetActiveModelIndex() ) ); + + // Remove old bitmap + _unlink( exp->GetBitmapFilename( models->GetActiveModelIndex() ) ); + } + + strcpy( exp->name, params.m_szName ); + strcpy( exp->description, params.m_szDescription ); + + if ( namechanged ) + { + exp->CreateNewBitmap( models->GetActiveModelIndex() ); + } + + active->SetDirty( true ); + + g_pExpressionTrayTool->redraw(); +} + +void FlexPanel::NewExpression( void ) +{ + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if ( !hdr ) + { + Con_ErrorPrintf( "Can't create new face pose, must load a model first!\n" ); + return; + } + + CExpClass *active = expressions->GetActiveClass(); + if ( !active ) + return; + + g_pExpressionTrayTool->Deselect(); + + CExpressionParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Add Expression" ); + strcpy( params.m_szName, "" ); + strcpy( params.m_szDescription, "" ); + + if ( !ExpressionProperties( ¶ms ) ) + return; + + if ( ( strlen( params.m_szName ) <= 0 ) || + !stricmp( params.m_szName, "unnamed" ) ) + { + Con_ErrorPrintf( "You must type in a valid name\n" ); + return; + } + + if ( ( strlen( params.m_szDescription ) <= 0 ) || + !stricmp( params.m_szDescription, "description" ) ) + { + Con_ErrorPrintf( "You must type in a valid description\n" ); + return; + } + + float settings[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ]; + float weights[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ]; + memset( settings, 0, sizeof( settings ) ); + memset( weights, 0, sizeof( settings ) ); + for ( LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++ ) + { + int j = hdr->pFlexcontroller( i )->localToGlobal; + + settings[ j ] = GetSlider( j ); + weights[ j ] = GetInfluence( j ); + } + + active->AddExpression( params.m_szName, params.m_szDescription, settings, weights, true, true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool FlexPanel::PaintBackground( void ) +{ + redraw(); + return false; +} + +void FlexPanel::OnMenu() +{ + POINT pt; + pt.x = btnMenu->x(); + pt.y = btnMenu->y(); + pt.y -= 3 * btnMenu->h2(); + ScreenToClient( (HWND)getHandle(), &pt ); + + mxPopupMenu *pop = new mxPopupMenu(); + + pop->add( "Check All", IDC_FP_CHECK_ALL ); + pop->add( "Uncheck All", IDC_FP_UNCHECK_ALL ); + pop->add( "Invert Selection", IDC_FP_INVERT ); + + pop->popup( this, pt.x, pt.y ); +} diff --git a/utils/hlfaceposer/flexpanel.h b/utils/hlfaceposer/flexpanel.h new file mode 100644 index 0000000..015a5de --- /dev/null +++ b/utils/hlfaceposer/flexpanel.h @@ -0,0 +1,144 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef FLEXPANEL_H +#define FLEXPANEL_H +#ifdef _WIN32 +#pragma once +#endif + +#ifndef INCLUDED_MXWINDOW +#include <mxtk/mxWindow.h> +#endif + +#define IDC_FLEX 7001 +#define IDC_FLEXSCROLL 7101 +#define IDC_EXPRESSIONRESET 7102 + +// NOTE THIS THIS TAKES UP 4 * 96 entries (384) +// #define NEXT_AVAIL 7457 ...etc. +#define IDC_FLEXSCALE 7200 + +#define IDC_FLEXSCALE_LAST 7584 + +#define IDC_FP_UNCHECK_ALL 7800 +#define IDC_FP_CHECK_ALL 7801 +#define IDC_FP_INVERT 7802 +#define IDC_FP_MENU 7803 + +#include "studio.h" + +class mxTab; +class mxChoice; +class mxCheckBox; +class mxSlider; +class mxScrollbar; +class mxLineEdit; +class mxLabel; +class mxButton; +class MatSysWindow; +class TextureWindow; +class mxExpressionSlider; + + +#include "expressions.h" +#include "faceposertoolwindow.h" + +/* + int nameindex; + int numkeys; + int keyindex; + { char key, char weight } +*/ + +class ControlPanel; + +class FlexPanel : public mxWindow, public IFacePoserToolWindow +{ + typedef mxWindow BaseClass; + + mxExpressionSlider *slFlexScale[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ]; + + mxScrollbar *slScrollbar; + + mxButton *btnResetSliders; + mxButton *btnCopyToSliders; + mxButton *btnCopyFromSliders; + mxButton *btnMenu; + + +public: + // CREATORS + FlexPanel (mxWindow *parent); + virtual ~FlexPanel (); + + virtual void redraw(); + virtual bool PaintBackground( void ); + + void SetEvent( CChoreoEvent *event ); + virtual void OnModelChanged(); + + // MANIPULATORS + int handleEvent (mxEvent *event); + + void initFlexes (); + + bool IsValidSlider( int iFlexController ) const; + + float GetSlider( int iFlexController ); + float GetSliderRawValue( int iFlexController ); + void GetSliderRange( int iFlexController, float& minvalue, float& maxvalue ); + + void SetSlider( int iFlexController, float value ); + float GetInfluence( int iFlexController ); + void SetInfluence( int iFlexController, float value ); + void SetEdited( int iFlexController, bool isEdited ); + bool IsEdited( int iFlexController ); + int LookupFlex( int iSlider, int barnum ); + int LookupPairedFlex( int iFlexController ); + + // maps global flex_controller index to UI slider + int nFlexSliderIndex[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ]; + int nFlexSliderBarnum[ GLOBAL_STUDIO_FLEX_CONTROL_COUNT ]; + + void PositionSliders( int sboffset ); + void PositionControls( int width, int height ); + + void EditExpression( void ); + void NewExpression( void ); + + void setExpression( int index ); + void DeleteExpression( int index ); + void SaveExpression( int index ); + void RevertExpression( int index ); + + void CopyControllerSettings( void ); + void PasteControllerSettings( void ); + + void ResetSliders( bool preserveundo, bool bDirtyClass ); + + void CopyControllerSettingsToStructure( CExpression *exp ); + +private: + enum + { + FP_STATE_UNCHECK = 0, + FP_STATE_CHECK, + FP_STATE_INVERT + }; + + void OnSetAll( int state ); + void OnMenu(); + + bool m_bNewExpressionMode; + + // Since we combine left/right into one, this will be less than hdr->numflexcontrollers + int m_nViewableFlexControllerCount; +}; + +extern FlexPanel *g_pFlexPanel; + +#endif // FLEXPANEL_H diff --git a/utils/hlfaceposer/globaleventproperties.cpp b/utils/hlfaceposer/globaleventproperties.cpp new file mode 100644 index 0000000..d5623f6 --- /dev/null +++ b/utils/hlfaceposer/globaleventproperties.cpp @@ -0,0 +1,205 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include <mxtk/mx.h> +#include <stdio.h> +#include "resource.h" +#include "GlobalEventProperties.h" +#include "mdlviewer.h" +#include "hlfaceposer.h" +#include "choreoevent.h" +#include "choreoscene.h" +#include "expressions.h" +#include "choreoactor.h" +#include "ifaceposersound.h" +#include "expclass.h" +#include "scriplib.h" + +static CGlobalEventParams g_Params; + +static void ExtractAutoStateFromParams( CGlobalEventParams *params ) +{ + ParseFromMemory( params->m_szAction, strlen( params->m_szAction ) ); + + params->m_bAutomate = false; + if ( TokenAvailable() ) + { + GetToken( false ); + params->m_bAutomate = !stricmp( token, "automate" ) ? true : false; + } + + if ( params->m_bAutomate ) + { + params->m_szType[ 0 ] = 0; + if ( TokenAvailable() ) + { + GetToken( false ); + strcpy( params->m_szType, token ); + } + + params->m_flWaitTime = 0.0f; + if ( TokenAvailable() ) + { + GetToken( false ); + params->m_flWaitTime = (float)atof( token ); + } + } +} + +static void CreateAutoStateFromControls( CGlobalEventParams *params ) +{ + if ( params->m_bAutomate ) + { + sprintf( params->m_szAction, "automate %s %f", params->m_szType, params->m_flWaitTime ); + } + else + { + sprintf( params->m_szAction, "noaction" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK GlobalEventPropertiesDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + switch(uMsg) + { + case WM_INITDIALOG: + { + g_Params.PositionSelf( hwndDlg ); + + SetDlgItemText( hwndDlg, IDC_EVENTNAME, g_Params.m_szName ); + + SetDlgItemText( hwndDlg, IDC_STARTTIME, va( "%f", g_Params.m_flStartTime ) ); + + switch ( g_Params.m_nType ) + { + default: + Assert(0); + break; + case CChoreoEvent::SECTION: + { + ShowWindow( GetDlgItem( hwndDlg, IDC_LOOPCOUNT ), SW_HIDE ); + ShowWindow( GetDlgItem( hwndDlg, IDC_STATIC_LOOPCOUNT ), SW_HIDE ); + ShowWindow( GetDlgItem( hwndDlg, IDC_LOOPTIME ), SW_HIDE ); + ShowWindow( GetDlgItem( hwndDlg, IDC_STATIC_LOOPTIME ), SW_HIDE ); + + ExtractAutoStateFromParams( &g_Params ); + } + break; + case CChoreoEvent::LOOP: + { + SendMessage( GetDlgItem( hwndDlg, IDC_LOOPCOUNT ), WM_SETTEXT , 0, (LPARAM)va( "%i", g_Params.m_nLoopCount ) ); + SendMessage( GetDlgItem( hwndDlg, IDC_LOOPTIME ), WM_SETTEXT , 0, (LPARAM)va( "%f", g_Params.m_flLoopTime ) ); + + ShowWindow( GetDlgItem( hwndDlg, IDC_CB_AUTOACTION ), SW_HIDE ); + ShowWindow( GetDlgItem( hwndDlg, IDC_DURATION ), SW_HIDE ); + ShowWindow( GetDlgItem( hwndDlg, IDC_CHECK_AUTOCHECK ), SW_HIDE ); + + ShowWindow( GetDlgItem( hwndDlg, IDC_STATIC_AFTER ), SW_HIDE ); + ShowWindow( GetDlgItem( hwndDlg, IDC_STATIC_SECONDS ), SW_HIDE ); + } + break; + case CChoreoEvent::STOPPOINT: + { + ShowWindow( GetDlgItem( hwndDlg, IDC_LOOPCOUNT ), SW_HIDE ); + ShowWindow( GetDlgItem( hwndDlg, IDC_STATIC_LOOPCOUNT ), SW_HIDE ); + ShowWindow( GetDlgItem( hwndDlg, IDC_LOOPTIME ), SW_HIDE ); + ShowWindow( GetDlgItem( hwndDlg, IDC_STATIC_LOOPTIME ), SW_HIDE ); + + ShowWindow( GetDlgItem( hwndDlg, IDC_CB_AUTOACTION ), SW_HIDE ); + ShowWindow( GetDlgItem( hwndDlg, IDC_DURATION ), SW_HIDE ); + ShowWindow( GetDlgItem( hwndDlg, IDC_CHECK_AUTOCHECK ), SW_HIDE ); + + ShowWindow( GetDlgItem( hwndDlg, IDC_STATIC_AFTER ), SW_HIDE ); + ShowWindow( GetDlgItem( hwndDlg, IDC_STATIC_SECONDS ), SW_HIDE ); + } + break; + } + + + SendMessage( GetDlgItem( hwndDlg, IDC_CHECK_AUTOCHECK ), BM_SETCHECK, + ( WPARAM ) g_Params.m_bAutomate ? BST_CHECKED : BST_UNCHECKED, + ( LPARAM )0 ); + + SetDlgItemText( hwndDlg, IDC_DURATION, va( "%f", g_Params.m_flWaitTime ) ); + + SendMessage( GetDlgItem( hwndDlg, IDC_CB_AUTOACTION ), WM_SETTEXT , 0, (LPARAM)g_Params.m_szType ); + // add text to combo box + SendMessage( GetDlgItem( hwndDlg, IDC_CB_AUTOACTION ), CB_ADDSTRING, 0, (LPARAM)"Cancel" ); + SendMessage( GetDlgItem( hwndDlg, IDC_CB_AUTOACTION ), CB_ADDSTRING, 0, (LPARAM)"Resume" ); + + + SetWindowText( hwndDlg, g_Params.m_szDialogTitle ); + + SetFocus( GetDlgItem( hwndDlg, IDC_EVENTNAME ) ); + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + char szTime[ 32 ]; + + SendMessage( GetDlgItem( hwndDlg, IDC_CB_AUTOACTION ), WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szType ), (LPARAM)g_Params.m_szType ); + + GetDlgItemText( hwndDlg, IDC_DURATION, szTime, sizeof( szTime ) ); + g_Params.m_flWaitTime = atof( szTime ); + + g_Params.m_bAutomate = SendMessage( GetDlgItem( hwndDlg, IDC_CHECK_AUTOCHECK ), BM_GETCHECK, 0, 0 ) == BST_CHECKED ? true : false; + + CreateAutoStateFromControls( &g_Params ); + + GetDlgItemText( hwndDlg, IDC_EVENTNAME, g_Params.m_szName, sizeof( g_Params.m_szName ) ); + + GetDlgItemText( hwndDlg, IDC_STARTTIME, szTime, sizeof( szTime ) ); + g_Params.m_flStartTime = atof( szTime ); + + char szLoop[ 32 ]; + GetDlgItemText( hwndDlg, IDC_LOOPCOUNT, szLoop, sizeof( szLoop ) ); + g_Params.m_nLoopCount = atoi( szLoop ); + GetDlgItemText( hwndDlg, IDC_LOOPTIME, szLoop, sizeof( szLoop ) ); + g_Params.m_flLoopTime = (float)atof( szLoop ); + + EndDialog( hwndDlg, 1 ); + } + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int GlobalEventProperties( CGlobalEventParams *params ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_GLOBALEVENTPROPERTIES ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)GlobalEventPropertiesDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/globaleventproperties.h b/utils/hlfaceposer/globaleventproperties.h new file mode 100644 index 0000000..c74160d --- /dev/null +++ b/utils/hlfaceposer/globaleventproperties.h @@ -0,0 +1,48 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef GLOBALEVENTPROPERTIES_H +#define GLOBALEVENTPROPERTIES_H +#ifdef _WIN32 +#pragma once +#endif + +class CChoreoScene; + +#include "basedialogparams.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +struct CGlobalEventParams : public CBaseDialogParams +{ + int m_nType; + + // GlobalEvent descriptive name + char m_szName[ 256 ]; + + // Pause start time + float m_flStartTime; + + // Pause Scene or Cancel Scene ( pause/cancel ) + char m_szAction[ 256 ]; + + bool m_bAutomate; + + char m_szType[ 256 ]; + + // Idle/paused time before action is taken + float m_flWaitTime; + + // For loop events + int m_nLoopCount; + float m_flLoopTime; +}; + +int GlobalEventProperties( CGlobalEventParams *params ); + +#endif // GLOBALEVENTPROPERTIES_H diff --git a/utils/hlfaceposer/hlfaceposer.cpp b/utils/hlfaceposer/hlfaceposer.cpp new file mode 100644 index 0000000..8b8a961 --- /dev/null +++ b/utils/hlfaceposer/hlfaceposer.cpp @@ -0,0 +1,744 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// +#include "cbase.h" +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#include "filesystem.h" +#include "mxtk/mx.h" +#include "mxStatusWindow.h" +#include "filesystem.h" +#include "StudioModel.h" +#include "ControlPanel.h" +#include "MDLViewer.h" +#include "mxExpressionTray.H" +#include "viewersettings.h" +#include "tier1/strtools.h" +#include "faceposer_models.h" +#include "expressions.h" +#include "choreoview.h" +#include "choreoscene.h" +#include "vstdlib/random.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "soundchars.h" +#include "sentence.h" +#include "PhonemeEditor.h" +#include <vgui/ILocalize.h> +#include "filesystem_init.h" +#include "tier2/p4helpers.h" + + +extern vgui::ILocalize *g_pLocalize; + +StudioModel *FindAssociatedModel( CChoreoScene *scene, CChoreoActor *a ); + + +//----------------------------------------------------------------------------- +// Purpose: Takes a full path and determines if the file exists on the disk +// Input : *filename - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool FPFullpathFileExists( const char *filename ) +{ + // Should be a full path + Assert( strchr( filename, ':' ) ); + + struct _stat buf; + int result = _stat( filename, &buf ); + if ( result != -1 ) + return true; + + return false; +} + +// Utility functions mostly +char *FacePoser_MakeWindowsSlashes( char *pname ) +{ + static char returnString[ 4096 ]; + strcpy( returnString, pname ); + pname = returnString; + + while ( *pname ) + { + if ( *pname == '/' ) + { + *pname = '\\'; + } + pname++; + } + + return returnString; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int GetCloseCaptionLanguageId() +{ + return g_viewerSettings.cclanguageid; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : id - +//----------------------------------------------------------------------------- +void SetCloseCaptionLanguageId( int id, bool force /* = false */ ) +{ + Assert( id >= 0 && id < CC_NUM_LANGUAGES ); + bool changed = g_viewerSettings.cclanguageid != id; + g_viewerSettings.cclanguageid = id; + if ( changed || force ) + { + // Switch languages + char const *suffix = CSentence::NameForLanguage( id ); + if ( Q_stricmp( suffix, "unknown_language" ) ) + { + char fn[ MAX_PATH ]; + Q_snprintf( fn, sizeof( fn ), "resource/closecaption_%s.txt", suffix ); + + g_pLocalize->RemoveAll(); + + if ( Q_stricmp( suffix, "english" )&& + filesystem->FileExists( "resource/closecaption_english.txt" ) ) + { + g_pLocalize->AddFile( "resource/closecaption_english.txt", "GAME", true ); + } + + if ( filesystem->FileExists( fn ) ) + { + g_pLocalize->AddFile( fn, "GAME", true ); + } + else + { + Con_ErrorPrintf( "PhonemeEditor::SetCloseCaptionLanguageId Warning, can't find localization file %s\n", fn ); + } + + // Need to redraw the choreoview at least + if ( g_pChoreoView ) + { + g_pChoreoView->InvalidateLayout(); + } + } + } + + if ( g_MDLViewer ) + { + g_MDLViewer->UpdateLanguageMenu( id ); + } +} + + +char *va( const char *fmt, ... ) +{ + va_list args; + static char output[32][1024]; + static int outbuffer = 0; + + outbuffer++; + va_start( args, fmt ); + vprintf( fmt, args ); + vsprintf( output[ outbuffer & 31 ], fmt, args ); + return output[ outbuffer & 31 ]; +} + +void Con_Printf( const char *fmt, ... ) +{ + va_list args; + static char output[1024]; + + va_start( args, fmt ); + vprintf( fmt, args ); + vsprintf( output, fmt, args ); + + if ( !g_pStatusWindow ) + { + return; + } + + g_pStatusWindow->StatusPrint( CONSOLE_COLOR, false, output ); +} + +void Con_ColorPrintf( COLORREF rgb, const char *fmt, ... ) +{ + va_list args; + static char output[1024]; + + va_start( args, fmt ); + vprintf( fmt, args ); + vsprintf( output, fmt, args ); + + if ( !g_pStatusWindow ) + { + return; + } + + g_pStatusWindow->StatusPrint( rgb, false, output ); +} + +void Con_ErrorPrintf( const char *fmt, ... ) +{ + va_list args; + static char output[1024]; + + va_start( args, fmt ); + vprintf( fmt, args ); + vsprintf( output, fmt, args ); + + if ( !g_pStatusWindow ) + { + return; + } + + g_pStatusWindow->StatusPrint( ERROR_COLOR, false, output ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *filename - +//----------------------------------------------------------------------------- +void MakeFileWriteable( const char *filename ) +{ + Assert( filesystem ); + char pFullPathBuf[ 512 ]; + char *pFullPath; + if ( !Q_IsAbsolutePath( filename ) ) + { + pFullPath = (char*)filesystem->RelativePathToFullPath( filename, NULL, pFullPathBuf, sizeof(pFullPathBuf) ); + } + else + { + Q_strncpy( pFullPathBuf, filename, sizeof(pFullPathBuf) ); + pFullPath = pFullPathBuf; + } + + if ( pFullPath ) + { + Q_FixSlashes( pFullPath ); + SetFileAttributes( pFullPath, FILE_ATTRIBUTE_NORMAL ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *filename - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool IsFileWriteable( const char *filename ) +{ + Assert( filesystem ); + char pFullPathBuf[ 512 ]; + char *pFullPath; + if ( !Q_IsAbsolutePath( filename ) ) + { + pFullPath = (char*)filesystem->RelativePathToFullPath( filename, NULL, pFullPathBuf, sizeof(pFullPathBuf) ); + } + else + { + Q_strncpy( pFullPathBuf, filename, sizeof(pFullPathBuf) ); + pFullPath = pFullPathBuf; + } + + if ( pFullPath ) + { + Q_FixSlashes( pFullPath ); + DWORD attrib = GetFileAttributes( pFullPath ); + return ( ( attrib & FILE_ATTRIBUTE_READONLY ) == 0 ); + } + + // Doesn't seem to exist, so yeah, it's writable + return true; +} + +bool MakeFileWriteablePrompt( const char *filename, char const *promptTitle ) +{ + if ( !IsFileWriteable( filename ) ) + { + int retval = mxMessageBox( NULL, va( "File '%s' is Read-Only, make writable?", filename ), + promptTitle, MX_MB_WARNING | MX_MB_YESNO ); + + // Didn't pick yes, bail + if ( retval != 0 ) + return false; + + MakeFileWriteable( filename ); + } + + return true; +} + +void FPCopyFile( const char *source, const char *dest, bool bCheckOut ) +{ + Assert( filesystem ); + char fullpaths[ MAX_PATH ]; + char fullpathd[ MAX_PATH ]; + + if ( !Q_IsAbsolutePath( source ) ) + { + filesystem->RelativePathToFullPath( source, NULL, fullpaths, sizeof(fullpaths) ); + } + else + { + Q_strncpy( fullpaths, source, sizeof(fullpaths) ); + } + + Q_strncpy( fullpathd, fullpaths, MAX_PATH ); + char *pSubdir = Q_stristr( fullpathd, source ); + if ( pSubdir ) + { + *pSubdir = 0; + } + Q_AppendSlash( fullpathd, MAX_PATH ); + Q_strncat( fullpathd, dest, MAX_PATH, MAX_PATH ); + + Q_FixSlashes( fullpaths ); + Q_FixSlashes( fullpathd ); + + if ( bCheckOut ) + { + CP4AutoEditAddFile checkout( fullpathd ); + CopyFile( fullpaths, fullpathd, FALSE ); + } + else + { + CopyFile( fullpaths, fullpathd, FALSE ); + } +} + +bool FacePoser_HasWindowStyle( mxWindow *w, int bits ) +{ + HWND wnd = (HWND)w->getHandle(); + DWORD style = GetWindowLong( wnd, GWL_STYLE ); + return ( style & bits ) ? true : false; +} + +bool FacePoser_HasWindowExStyle( mxWindow *w, int bits ) +{ + HWND wnd = (HWND)w->getHandle(); + DWORD style = GetWindowLong( wnd, GWL_EXSTYLE ); + return ( style & bits ) ? true : false; +} + +void FacePoser_AddWindowStyle( mxWindow *w, int addbits ) +{ + HWND wnd = (HWND)w->getHandle(); + DWORD style = GetWindowLong( wnd, GWL_STYLE ); + style |= addbits; + SetWindowLong( wnd, GWL_STYLE, style ); +} + +void FacePoser_AddWindowExStyle( mxWindow *w, int addbits ) +{ + HWND wnd = (HWND)w->getHandle(); + DWORD style = GetWindowLong( wnd, GWL_EXSTYLE ); + style |= addbits; + SetWindowLong( wnd, GWL_EXSTYLE, style ); +} + +void FacePoser_RemoveWindowStyle( mxWindow *w, int removebits ) +{ + HWND wnd = (HWND)w->getHandle(); + DWORD style = GetWindowLong( wnd, GWL_STYLE ); + style &= ~removebits; + SetWindowLong( wnd, GWL_STYLE, style ); +} + +void FacePoser_RemoveWindowExStyle( mxWindow *w, int removebits ) +{ + HWND wnd = (HWND)w->getHandle(); + DWORD style = GetWindowLong( wnd, GWL_EXSTYLE ); + style &= ~removebits; + SetWindowLong( wnd, GWL_EXSTYLE, style ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *w - +//----------------------------------------------------------------------------- +void FacePoser_MakeToolWindow( mxWindow *w, bool smallcaption ) +{ + FacePoser_AddWindowStyle( w, WS_VISIBLE | WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS ); + if ( smallcaption ) + { + FacePoser_AddWindowExStyle( w, WS_EX_OVERLAPPEDWINDOW ); + FacePoser_AddWindowExStyle( w, WS_EX_TOOLWINDOW ); + } +} + +bool LoadViewerSettingsInt( char const *keyname, int *value ); +bool SaveViewerSettingsInt ( const char *keyname, int value ); + +void FacePoser_LoadWindowPositions( char const *name, bool& visible, int& x, int& y, int& w, int& h, bool& locked, bool& zoomed ) +{ + char subkey[ 512 ]; + int v; + + Q_snprintf( subkey, sizeof( subkey ), "%s - visible", name ); + LoadViewerSettingsInt( subkey, &v ); + visible = v ? true : false; + + Q_snprintf( subkey, sizeof( subkey ), "%s - locked", name ); + LoadViewerSettingsInt( subkey, &v ); + locked = v ? true : false; + + Q_snprintf( subkey, sizeof( subkey ), "%s - zoomed", name ); + LoadViewerSettingsInt( subkey, &v ); + zoomed = v ? true : false; + + Q_snprintf( subkey, sizeof( subkey ), "%s - x", name ); + LoadViewerSettingsInt( subkey, &x ); + Q_snprintf( subkey, sizeof( subkey ), "%s - y", name ); + LoadViewerSettingsInt( subkey, &y ); + Q_snprintf( subkey, sizeof( subkey ), "%s - width", name ); + LoadViewerSettingsInt( subkey, &w ); + Q_snprintf( subkey, sizeof( subkey ), "%s - height", name ); + LoadViewerSettingsInt( subkey, &h ); +} + +void FacePoser_SaveWindowPositions( char const *name, bool visible, int x, int y, int w, int h, bool locked, bool zoomed ) +{ + char subkey[ 512 ]; + Q_snprintf( subkey, sizeof( subkey ), "%s - visible", name ); + SaveViewerSettingsInt( subkey, visible ); + Q_snprintf( subkey, sizeof( subkey ), "%s - locked", name ); + SaveViewerSettingsInt( subkey, locked ); + Q_snprintf( subkey, sizeof( subkey ), "%s - x", name ); + SaveViewerSettingsInt( subkey, x ); + Q_snprintf( subkey, sizeof( subkey ), "%s - y", name ); + SaveViewerSettingsInt( subkey, y ); + Q_snprintf( subkey, sizeof( subkey ), "%s - width", name ); + SaveViewerSettingsInt( subkey, w ); + Q_snprintf( subkey, sizeof( subkey ), "%s - height", name ); + SaveViewerSettingsInt( subkey, h ); + Q_snprintf( subkey, sizeof( subkey ), "%s - zoomed", name ); + SaveViewerSettingsInt( subkey, zoomed ); +} + +static char g_PhonemeRoot[ MAX_PATH ] = { 0 }; +void FacePoser_SetPhonemeRootDir( char const *pchRootDir ) +{ + Q_strncpy( g_PhonemeRoot, pchRootDir, sizeof( g_PhonemeRoot ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void FacePoser_EnsurePhonemesLoaded( void ) +{ + // Don't bother unless a model is loaded, at least... + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if ( !hdr ) + { + return; + } + + char const *ext[] = + { + "", + "_strong", + "_weak", + }; + + for ( int i = 0 ; i < ARRAYSIZE( ext ); ++i ) + { + char clname[ 256 ]; + Q_snprintf( clname, sizeof( clname ), "%sphonemes%s", g_PhonemeRoot, ext[ i ] ); + Q_FixSlashes( clname ); + Q_strlower( clname ); + + if ( !expressions->FindClass( clname, false ) ) + { + char clfile[ MAX_PATH ]; + Q_snprintf( clfile, sizeof( clfile ), "expressions/%sphonemes%s.txt", g_PhonemeRoot, ext[ i ] ); + Q_FixSlashes( clfile ); + Q_strlower( clfile ); + + if ( g_pFileSystem->FileExists( clfile ) ) + { + expressions->LoadClass( clfile ); + CExpClass *cl = expressions->FindClass( clname, false ); + if ( !cl ) + { + Con_Printf( "FacePoser_EnsurePhonemesLoaded: %s missing!!!\n", clfile ); + } + } + } + } +} + +bool FacePoser_ShowFileNameDialog( bool openFile, char *relative, size_t bufsize, char const *subdir, char const *wildcard ) +{ + Assert( relative ); + relative[ 0 ] = 0 ; + Assert( subdir ); + Assert( wildcard ); + + char workingdir[ 256 ]; + Q_getwd( workingdir, sizeof( workingdir ) ); + strlwr( workingdir ); + Q_FixSlashes( workingdir, '/' ); + + // Show file io + bool inWorkingDirectoryAlready = false; + if ( Q_stristr_slash( workingdir, va( "%s%s", GetGameDirectory(), subdir ) ) ) + { + inWorkingDirectoryAlready = true; + } + +// Show file io + const char *fullpath = NULL; + + if ( openFile ) + { + fullpath = mxGetOpenFileName( + 0, + inWorkingDirectoryAlready ? "." : FacePoser_MakeWindowsSlashes( va( "%s%s/", GetGameDirectory(), subdir ) ), + wildcard ); + } + else + { + fullpath = mxGetSaveFileName( + 0, + inWorkingDirectoryAlready ? "." : FacePoser_MakeWindowsSlashes( va( "%s%s/", GetGameDirectory(), subdir ) ), + wildcard ); + } + if ( !fullpath || !fullpath[ 0 ] ) + return false; + + Q_strncpy( relative, fullpath, bufsize ); + return true; +} + +bool FacePoser_ShowOpenFileNameDialog( char *relative, size_t bufsize, char const *subdir, char const *wildcard ) +{ + return FacePoser_ShowFileNameDialog( true, relative, bufsize, subdir, wildcard ); +} + +bool FacePoser_ShowSaveFileNameDialog( char *relative, size_t bufsize, char const *subdir, char const *wildcard ) +{ + return FacePoser_ShowFileNameDialog( false, relative, bufsize, subdir, wildcard ); +} + +//----------------------------------------------------------------------------- +// Purpose: converts an english string to unicode +//----------------------------------------------------------------------------- +int ConvertANSIToUnicode(const char *ansi, wchar_t *unicode, int unicodeBufferSize) +{ + return ::MultiByteToWideChar(CP_ACP, 0, ansi, -1, unicode, unicodeBufferSize); +} + +//----------------------------------------------------------------------------- +// Purpose: converts an unicode string to an english string +//----------------------------------------------------------------------------- +int ConvertUnicodeToANSI(const wchar_t *unicode, char *ansi, int ansiBufferSize) +{ + return ::WideCharToMultiByte(CP_ACP, 0, unicode, -1, ansi, ansiBufferSize, NULL, NULL); +} + +//----------------------------------------------------------------------------- +// Purpose: If FPS is set and "using grid", snap to proper fractional time value +// Input : t - +// Output : float +//----------------------------------------------------------------------------- +float FacePoser_SnapTime( float t ) +{ + if ( !g_pChoreoView ) + return t; + + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( !scene ) + return t; + + return scene->SnapTime( t ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : t - +// Output : char const +//----------------------------------------------------------------------------- +char const *FacePoser_DescribeSnappedTime( float t ) +{ + static char desc[ 128 ]; + Q_snprintf( desc, sizeof( desc ), "%.3f", t ); + + if ( !g_pChoreoView ) + return desc; + + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( !scene ) + return desc; + + t = scene->SnapTime( t ); + + int fps = scene->GetSceneFPS(); + + int ipart = (int)t; + int fracpart = (int)( ( t - (float)ipart ) * (float)fps + 0.5f ); + + int frame = ipart * fps + fracpart; + + if ( fracpart == 0 ) + { + Q_snprintf( desc, sizeof( desc ), "frame %i (time %i s.)", frame, ipart ); + } + else + { + Q_snprintf( desc, sizeof( desc ), "frame %i (time %i + %i/%i s.)", + frame, ipart,fracpart, fps ); + } + + return desc; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int FacePoser_GetSceneFPS( void ) +{ + if ( !g_pChoreoView ) + return 1000; + + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( !scene ) + return 1000; + + return scene->GetSceneFPS(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool FacePoser_IsSnapping( void ) +{ + if ( !g_pChoreoView ) + return false; + + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( !scene ) + return false; + + return scene->IsUsingFrameSnap(); +} + +char const *FacePoser_TranslateSoundNameGender( char const *soundname, gender_t gender ) +{ + if ( Q_stristr( soundname, ".wav" ) ) + return PSkipSoundChars( soundname ); + + return PSkipSoundChars( soundemitter->GetWavFileForSound( soundname, gender ) ); +} + +char const *FacePoser_TranslateSoundName( char const *soundname, StudioModel *model /*= NULL*/ ) +{ + if ( Q_stristr( soundname, ".wav" ) ) + return PSkipSoundChars( soundname ); + + static char temp[ 256 ]; + + if ( model ) + { + Q_strncpy( temp, PSkipSoundChars( soundemitter->GetWavFileForSound( soundname, model->GetFileName() ) ), sizeof( temp ) ); + } + else + { + Q_strncpy( temp, PSkipSoundChars( soundemitter->GetWavFileForSound( soundname, NULL ) ), sizeof( temp ) ); + } + return temp; +} + +char const *FacePoser_TranslateSoundName( CChoreoEvent *event ) +{ + char const *soundname = event->GetParameters(); + if ( Q_stristr( soundname, ".wav" ) ) + return PSkipSoundChars( soundname ); + + // See if we can figure out the .mdl associated to this event's actor + static char temp[ 256 ]; + temp[ 0 ] = 0; + StudioModel *model = NULL; + + CChoreoActor *a = event->GetActor(); + CChoreoScene *s = event->GetScene(); + + if ( a != NULL && + s != NULL ) + { + model = FindAssociatedModel( s, a ); + } + + Q_strncpy( temp, PSkipSoundChars( soundemitter->GetWavFileForSound( soundname, model ? model->GetFileName() : NULL ) ), sizeof( temp ) ); + return temp; +} + + +#if defined( _WIN32 ) || defined( WIN32 ) +#define PATHSEPARATOR(c) ((c) == '\\' || (c) == '/') +#else //_WIN32 +#define PATHSEPARATOR(c) ((c) == '/') +#endif //_WIN32 + +static bool charsmatch( char c1, char c2 ) +{ + if ( tolower( c1 ) == tolower( c2 ) ) + return true; + if ( PATHSEPARATOR( c1 ) && PATHSEPARATOR( c2 ) ) + return true; + return false; +} + + +char *Q_stristr_slash( char const *pStr, char const *pSearch ) +{ + AssertValidStringPtr(pStr); + AssertValidStringPtr(pSearch); + + if (!pStr || !pSearch) + return 0; + + char const* pLetter = pStr; + + // Check the entire string + while (*pLetter != 0) + { + // Skip over non-matches + if ( charsmatch( *pLetter, *pSearch ) ) + { + // Check for match + char const* pMatch = pLetter + 1; + char const* pTest = pSearch + 1; + while (*pTest != 0) + { + // We've run off the end; don't bother. + if (*pMatch == 0) + return 0; + + if ( !charsmatch( *pMatch, *pTest ) ) + break; + + ++pMatch; + ++pTest; + } + + // Found a match! + if (*pTest == 0) + return (char *)pLetter; + } + + ++pLetter; + } + + return 0; +} + +static CUniformRandomStream g_Random; +IUniformRandomStream *random = &g_Random; diff --git a/utils/hlfaceposer/hlfaceposer.h b/utils/hlfaceposer/hlfaceposer.h new file mode 100644 index 0000000..fdbce8c --- /dev/null +++ b/utils/hlfaceposer/hlfaceposer.h @@ -0,0 +1,83 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#if !defined( HLFACEPOSER_H ) +#define HLFACEPOSER_H +#ifdef _WIN32 +#pragma once +#endif + +#include <ctype.h> +#include <float.h> +#include <windows.h> +#include "SoundEmitterSystem/isoundemittersystembase.h" + +#define CONSOLE_COLOR RGB( 82, 173, 216 ) + +#define ERROR_COLOR RGB( 255, 50, 20 ) + +#define FILE_COLOR RGB( 0, 63, 200 ) + +#define MAX_FP_MODELS 16 + +#define SCRUBBER_HANDLE_WIDTH 40 +#define SCRUBBER_HANDLE_HEIGHT 10 + +char *va( PRINTF_FORMAT_STRING const char *fmt, ... ); + +char const *GetGameDirectory(); // e.g. u:\main\game\ep2 +char const *GetGameDirectorySimple(); // e.g. ep2 + +void Con_Printf( PRINTF_FORMAT_STRING const char *fmt, ... ); +void Con_ColorPrintf( COLORREF clr, PRINTF_FORMAT_STRING const char *fmt, ... ); +void Con_ErrorPrintf( PRINTF_FORMAT_STRING const char *fmt, ... ); + +bool FPFullpathFileExists( const char *filename ); +void MakeFileWriteable( const char *filename ); +bool MakeFileWriteablePrompt( const char *filename, char const *promptTitle ); +bool IsFileWriteable( const char *filename ); +void FPCopyFile( const char *source, const char *dest, bool bCheckOut ); +class mxWindow; +void FacePoser_MakeToolWindow( mxWindow *w, bool smallcaption ); +void FacePoser_LoadWindowPositions( char const *name, bool& visible, int& x, int& y, int& w, int& h, bool& locked, bool& zoomed ); +void FacePoser_SaveWindowPositions( char const *name, bool visible, int x, int y, int w, int h, bool locked, bool zoomed ); +void FacePoser_AddWindowStyle( mxWindow *w, int addbits ); +void FacePoser_AddWindowExStyle( mxWindow *w, int addbits ); +void FacePoser_RemoveWindowStyle( mxWindow *w, int removebits ); +void FacePoser_RemoveWindowExStyle( mxWindow *w, int removebits ); +bool FacePoser_HasWindowStyle( mxWindow *w, int bits ); +bool FacePoser_HasWindowExStyle( mxWindow *w, int bits ); + +void FacePoser_EnsurePhonemesLoaded( void ); +void FacePoser_SetPhonemeRootDir( char const *pchRootDir ); + +int ConvertANSIToUnicode(const char *ansi, wchar_t *unicode, int unicodeBufferSize); +int ConvertUnicodeToANSI(const wchar_t *unicode, char *ansi, int ansiBufferSize); + +float FacePoser_SnapTime( float t ); +char const *FacePoser_DescribeSnappedTime( float t ); +int FacePoser_GetSceneFPS( void ); +bool FacePoser_IsSnapping( void ); + +class StudioModel; +char const *FacePoser_TranslateSoundName( char const *soundname, StudioModel *model = NULL ); +class CChoreoEvent; + +char const *FacePoser_TranslateSoundName( CChoreoEvent *event ); +char const *FacePoser_TranslateSoundNameGender( char const *soundname, gender_t gender ); + +extern class IFileSystem *filesystem; +extern class ISceneTokenProcessor *tokenprocessor; + +char *Q_stristr_slash( char const *pStr, char const *pSearch ); + +void SetCloseCaptionLanguageId( int id, bool force = false ); // from sentence.h enum +int GetCloseCaptionLanguageId(); + +bool FacePoser_ShowOpenFileNameDialog( char *relative, size_t bufsize, char const *subdir, char const *wildcard ); +bool FacePoser_ShowSaveFileNameDialog( char *relative, size_t bufsize, char const *subdir, char const *wildcard ); + +#endif // HLFACEPOSER_H diff --git a/utils/hlfaceposer/hlfaceposer.rc b/utils/hlfaceposer/hlfaceposer.rc new file mode 100644 index 0000000..928255a --- /dev/null +++ b/utils/hlfaceposer/hlfaceposer.rc @@ -0,0 +1,898 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_ACTORPROPERTIES DIALOG 0, 0, 194, 81 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Actor Properties" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,7,42,50,14 + PUSHBUTTON "Cancel",IDCANCEL,7,60,50,14 + EDITTEXT IDC_ACTORNAME,7,20,180,14,ES_AUTOHSCROLL + LTEXT "Name:",IDC_STATIC,7,7,180,9 +END + +IDD_INPUTDIALOG DIALOG 0, 0, 380, 61 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,7,40,50,14 + PUSHBUTTON "Cancel",IDCANCEL,66,40,50,14 + EDITTEXT IDC_INPUTSTRING,7,20,366,14,ES_AUTOHSCROLL + LTEXT "Prompt:",IDC_STATIC_PROMPT,7,7,366,9 +END + +IDD_EXPRESSIONPROPERTIES DIALOG 0, 0, 207, 114 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Expression Properties:" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,7,73,50,14 + PUSHBUTTON "Cancel",IDCANCEL,7,93,50,14 + EDITTEXT IDC_EXPRESSIONNAME,7,21,193,14,ES_AUTOHSCROLL + LTEXT "Name:",IDC_STATIC,7,9,193,9 + EDITTEXT IDC_EXPRESSIONDESC,7,53,193,14,ES_AUTOHSCROLL + LTEXT "Description:",IDC_STATIC,7,40,193,9 +END + +IDD_CHANNELPROPERTIES DIALOG 0, 0, 255, 82 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Channel Properties" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,7,42,50,14 + PUSHBUTTON "Cancel",IDCANCEL,7,61,50,14 + EDITTEXT IDC_CHANNELNAME,7,20,106,14,ES_AUTOHSCROLL + LTEXT "Name:",IDC_STATIC,7,7,53,9 + COMBOBOX IDC_ACTORCHOICE,139,20,109,97,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP + LTEXT "Add to Actor:",IDC_STATIC_ACTOR,139,7,53,9 +END + +IDD_EVENTPROPERTIES DIALOGEX 0, 0, 368, 241 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_EVENTNAME,65,9,256,15,ES_AUTOHSCROLL + EDITTEXT IDC_FILENAME,65,30,256,14,ES_AUTOHSCROLL + COMBOBOX IDC_EVENTCHOICES2,65,50,256,184,CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP + CONTROL "Event must complete for paused scene to resume",IDC_CHECK_RESUMECONDITION, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,98,90,230,10 + CONTROL "Absolute:",IDC_ABSOLUTESTART,"Button",BS_AUTORADIOBUTTON,161,110,52,8 + EDITTEXT IDC_STARTTIME,219,107,142,12,ES_AUTOHSCROLL + CONTROL "Relative:",IDC_RELATIVESTART,"Button",BS_AUTORADIOBUTTON | WS_GROUP,161,125,54,8 + COMBOBOX IDC_TAGS,219,122,142,85,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP + CONTROL "End Time:",IDC_CHECK_ENDTIME,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,99,149,62,10 + EDITTEXT IDC_ENDTIME,219,147,142,12,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,7,190,50,14 + PUSHBUTTON "Cancel",IDCANCEL,7,209,50,14 + PUSHBUTTON "Choose File...",IDC_SELECTWAV,7,50,50,14 + COMBOBOX IDC_EVENTCHOICES,65,31,256,144,CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP + LTEXT "Type",IDC_TYPENAME,7,31,57,9 + LTEXT "Name:",IDC_STATIC,8,10,51,12 + LTEXT "Start Time:",IDC_STATIC,111,110,47,9 + LTEXT "INVISIBLE SPLINE PLACEHOLDER",IDC_STATIC_SPLINE,66,170,295,48,NOT WS_VISIBLE + LTEXT "Pitch:",IDC_STATIC_PITCH,156,61,23,10 + LTEXT "Yaw:",IDC_STATIC_YAW,156,76,23,10 + CONTROL "Slider1",IDC_SLIDER_PITCH,"msctls_trackbar32",TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,178,61,135,12 + CONTROL "Slider1",IDC_SLIDER_YAW,"msctls_trackbar32",TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,178,76,135,12 + LTEXT "PitchVal",IDC_STATIC_PITCHVAL,333,61,28,10 + LTEXT "YawVal",IDC_STATIC_YAWVAL,333,76,28,10 + CONTROL "Use Pitch/Yaw",IDC_CHECK_LOOKAT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,81,67,76,11 + CONTROL "Show All Sounds",IDC_SHOW_ALL_SOUNDS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,90,69,10 + COMBOBOX IDC_EVENTCHOICES3,65,68,256,184,CBS_DROPDOWN | CBS_AUTOHSCROLL | NOT WS_VISIBLE | WS_VSCROLL | WS_TABSTOP + LTEXT "",IDC_CHOICES2PROMPT,7,52,57,9,NOT WS_VISIBLE + LTEXT "",IDC_CHOICES3PROMPT,7,69,57,9,NOT WS_VISIBLE + CONTROL "Disabled",IDC_CHECK_DISABLED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,106,74,10 +END + +IDD_PHONEMEPROPERTIES DIALOG 0, 0, 323, 151 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Phoneme/Viseme Properties" +FONT 8, "MS Sans Serif" +BEGIN + EDITTEXT IDC_EDIT_PHONEME,39,130,161,14,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,205,130,50,14 + PUSHBUTTON "Cancel",IDCANCEL,266,130,50,14 + LTEXT "Phoneme/Viseme:",IDC_PHONEMENAME,7,7,309,9 + LTEXT "Code:",IDC_PHONEMETEXTPROMPT,7,129,24,15 + LTEXT "",IDC_STATIC_HELPTEXT,83,110,194,15 +END + +IDD_GLOBALEVENTPROPERTIES DIALOG 0, 0, 307, 114 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Pause" +FONT 8, "MS Sans Serif" +BEGIN + EDITTEXT IDC_EVENTNAME,66,9,234,15,ES_AUTOHSCROLL + EDITTEXT IDC_STARTTIME,112,29,109,12,ES_AUTOHSCROLL + EDITTEXT IDC_DURATION,171,45,46,12,ES_AUTOHSCROLL + CONTROL "Automatically",IDC_CHECK_AUTOCHECK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,47,57,10 + DEFPUSHBUTTON "OK",IDOK,7,74,50,14 + PUSHBUTTON "Cancel",IDCANCEL,7,93,50,14 + LTEXT "Name:",IDC_STATIC,8,10,51,12 + LTEXT "Start Time:",IDC_STATIC,67,31,43,9 + COMBOBOX IDC_CB_AUTOACTION,74,44,70,62,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP + LTEXT "after",IDC_STATIC_AFTER,150,47,20,9 + LTEXT "seconds",IDC_STATIC_SECONDS,225,47,41,9 + EDITTEXT IDC_LOOPCOUNT,159,60,61,12,ES_AUTOHSCROLL + LTEXT "Loop Count (-1 = forever)",IDC_STATIC_LOOPCOUNT,67,62,88,9 + LTEXT "Loop to:",IDC_STATIC_LOOPTIME,67,48,43,9 + EDITTEXT IDC_LOOPTIME,112,46,109,12,ES_AUTOHSCROLL +END + +IDD_FLEXSLIDERS DIALOG 0, 0, 239, 196 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Activate Sliders" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,182,7,50,14 + PUSHBUTTON "Cancel",IDCANCEL,182,24,50,14 + CONTROL "Tree1",IDC_SLIDERS,"SysTreeView32",TVS_SHOWSELALWAYS | TVS_CHECKBOXES | TVS_FULLROWSELECT | WS_BORDER | WS_TABSTOP,7,7,150,182 +END + +IDD_CHOICEDIALOG DIALOG 0, 0, 240, 111 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Choose" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,183,7,50,14 + PUSHBUTTON "Cancel",IDCANCEL,183,24,50,14 + LTEXT "Prompt:",IDC_STATIC_PROMPT,7,7,170,9 + COMBOBOX IDC_CHOICE,7,20,165,84,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP +END + +IDD_EDITPHRASE DIALOG 0, 0, 237, 139 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Edit Phrase" +FONT 8, "Tahoma" +BEGIN + DEFPUSHBUTTON "OK",IDOK,7,118,50,14 + PUSHBUTTON "Cancel",IDCANCEL,67,118,50,14 + LTEXT "Prompt:",IDC_STATIC_PROMPT,7,7,223,9 + EDITTEXT IDC_INPUTSTRING,7,20,223,14,ES_AUTOHSCROLL + LTEXT "Legend:\r\n<clr:r,g,b> start color\r\n<clr> end color\r\n<I> start/end italic\r\n<B> start/end bold\r\n<linger:num> number of seconds to linger\r\n<cr> linebreak",IDC_STATIC,7,38,141,64 +END + +IDD_WAVELOOKUP DIALOG 0, 0, 451, 177 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Sound Lookup" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,394,7,50,14 + PUSHBUTTON "Cancel",IDCANCEL,394,25,50,14 + LTEXT "Sound entries for .wav file xxx:",IDC_STATIC_PROMPT,7,7,168,14 + LISTBOX IDC_SOUNDENTRYLIST,7,49,437,104,LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + LTEXT "Choose one of the following entries instead?",IDC_STATIC,7,34,145,12 + PUSHBUTTON "Add New Entry...",IDC_ADDENTRY,7,156,66,14 +END + +IDD_ADDSOUNDENTRY DIALOG 0, 0, 273, 94 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Add Sound Entry" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,216,7,50,14 + PUSHBUTTON "Cancel",IDCANCEL,216,24,50,14 + LTEXT "Sound Name:",IDC_STATIC,7,7,182,12 + EDITTEXT IDC_SOUNDNAME,7,19,191,14,ES_AUTOHSCROLL + COMBOBOX IDC_SOUNDSCRIPT,7,52,191,99,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP + LTEXT "Script File for Sound:",IDC_STATIC,7,39,66,8 +END + +IDD_CCLOOKUP DIALOG 0, 0, 620, 350 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Close Caption Lookup" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,563,7,50,14 + PUSHBUTTON "Cancel",IDCANCEL,563,25,50,14 + LTEXT "Close Caption Token:",IDC_STATIC,7,10,145,12 + EDITTEXT IDC_CCTOKEN,7,22,392,12,ES_AUTOHSCROLL + CONTROL "List1",IDC_CCTOKENLIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_SHAREIMAGELISTS | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,7,40,606,303 +END + +IDD_EVENTPROPERTIES_EXPRESSION DIALOGEX 0, 0, 368, 241 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_EVENTNAME,65,9,256,15,ES_AUTOHSCROLL + COMBOBOX IDC_EVENTCHOICES2,65,50,256,184,CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP + CONTROL "Event must complete for paused scene to resume",IDC_CHECK_RESUMECONDITION, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,98,90,230,10 + CONTROL "Absolute:",IDC_ABSOLUTESTART,"Button",BS_AUTORADIOBUTTON,161,110,52,8 + EDITTEXT IDC_STARTTIME,219,107,142,12,ES_AUTOHSCROLL + CONTROL "Relative:",IDC_RELATIVESTART,"Button",BS_AUTORADIOBUTTON | WS_GROUP,161,125,54,8 + COMBOBOX IDC_TAGS,219,122,142,85,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP + CONTROL "End Time:",IDC_CHECK_ENDTIME,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,99,149,62,10 + EDITTEXT IDC_ENDTIME,219,147,142,12,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,7,190,50,14 + PUSHBUTTON "Cancel",IDCANCEL,7,209,50,14 + COMBOBOX IDC_EVENTCHOICES,65,31,256,144,CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP + LTEXT "Type",IDC_TYPENAME,7,31,57,9 + LTEXT "Name:",IDC_STATIC,8,10,51,12 + LTEXT "Start Time:",IDC_STATIC,111,110,47,9 + LTEXT "INVISIBLE SPLINE PLACEHOLDER",IDC_STATIC_SPLINE,66,170,295,48,NOT WS_VISIBLE + LTEXT "",IDC_CHOICES2PROMPT,7,52,57,9,NOT WS_VISIBLE + CONTROL "Disabled",IDC_CHECK_DISABLED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,90,74,10 +END + +IDD_EVENTPROPERTIES_LOOKAT DIALOGEX 0, 0, 368, 241 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_EVENTNAME,65,9,256,15,ES_AUTOHSCROLL + CONTROL "Event must complete for paused scene to resume",IDC_CHECK_RESUMECONDITION, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,98,90,230,10 + CONTROL "Absolute:",IDC_ABSOLUTESTART,"Button",BS_AUTORADIOBUTTON,161,110,52,8 + EDITTEXT IDC_STARTTIME,219,107,142,12,ES_AUTOHSCROLL + CONTROL "Relative:",IDC_RELATIVESTART,"Button",BS_AUTORADIOBUTTON | WS_GROUP,161,125,54,8 + COMBOBOX IDC_TAGS,219,122,142,85,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP + CONTROL "End Time:",IDC_CHECK_ENDTIME,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,99,149,62,10 + EDITTEXT IDC_ENDTIME,219,147,142,12,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,7,190,50,14 + PUSHBUTTON "Cancel",IDCANCEL,7,209,50,14 + COMBOBOX IDC_EVENTCHOICES,65,31,256,144,CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP + LTEXT "Type",IDC_TYPENAME,7,31,57,9 + LTEXT "Name:",IDC_STATIC,8,10,51,12 + LTEXT "Start Time:",IDC_STATIC,111,110,47,9 + LTEXT "INVISIBLE SPLINE PLACEHOLDER",IDC_STATIC_SPLINE,66,170,295,48,NOT WS_VISIBLE + LTEXT "Pitch:",IDC_STATIC_PITCH,156,61,23,10 + LTEXT "Yaw:",IDC_STATIC_YAW,156,76,23,10 + CONTROL "Slider1",IDC_SLIDER_PITCH,"msctls_trackbar32",TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,178,61,135,12 + CONTROL "Slider1",IDC_SLIDER_YAW,"msctls_trackbar32",TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,178,76,135,12 + LTEXT "PitchVal",IDC_STATIC_PITCHVAL,333,61,28,10 + LTEXT "YawVal",IDC_STATIC_YAWVAL,333,76,28,10 + CONTROL "Use Pitch/Yaw",IDC_CHECK_LOOKAT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,81,67,76,11 + CONTROL "Disabled",IDC_CHECK_DISABLED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,90,74,10 +END + +IDD_EVENTPROPERTIES_MOVETO DIALOGEX 0, 0, 368, 259 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_EVENTNAME,65,9,256,15,ES_AUTOHSCROLL + COMBOBOX IDC_EVENTCHOICES2,65,50,256,184,CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP + CONTROL "Event must complete for paused scene to resume",IDC_CHECK_RESUMECONDITION, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,98,108,230,10 + CONTROL "Absolute:",IDC_ABSOLUTESTART,"Button",BS_AUTORADIOBUTTON,161,129,52,8 + EDITTEXT IDC_STARTTIME,219,126,142,12,ES_AUTOHSCROLL + CONTROL "Relative:",IDC_RELATIVESTART,"Button",BS_AUTORADIOBUTTON | WS_GROUP,161,143,54,8 + COMBOBOX IDC_TAGS,219,140,142,85,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP + CONTROL "End Time:",IDC_CHECK_ENDTIME,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,99,167,62,10 + EDITTEXT IDC_ENDTIME,219,166,142,12,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,7,209,50,14 + PUSHBUTTON "Cancel",IDCANCEL,7,228,50,14 + COMBOBOX IDC_EVENTCHOICES,65,31,256,144,CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP + LTEXT "Type",IDC_TYPENAME,7,31,57,9 + LTEXT "Name:",IDC_STATIC,8,10,51,12 + LTEXT "Start Time:",IDC_STATIC,111,129,47,9 + LTEXT "INVISIBLE SPLINE PLACEHOLDER",IDC_STATIC_SPLINE,66,188,295,48,NOT WS_VISIBLE + LTEXT "",IDC_CHOICES2PROMPT,7,52,57,9,NOT WS_VISIBLE + CONTROL "Slider1",IDC_SLIDER_DISTANCE,"msctls_trackbar32",TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,99,68,165,12 + LTEXT "Stop Distance:",IDC_STATIC_DISTANCE,7,68,57,10 + LTEXT "DistVal",IDC_STATIC_DISTANCEVAL,65,68,28,10 + CONTROL "Force Short Movements",IDC_CHECK_FORCESHORTMOVEMENT, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,270,69,91,10 + CONTROL "Disabled",IDC_CHECK_DISABLED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,108,74,10 + COMBOBOX IDC_EVENTCHOICES3,66,86,256,144,CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP + LTEXT "Closest To:",IDC_TYPENAME2,7,87,57,9 +END + +IDD_EVENTPROPERTIES_SPEAK DIALOGEX 0, 0, 371, 380 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_EVENTNAME,63,9,265,15,ES_AUTOHSCROLL + CONTROL "Event must complete for paused scene to resume",IDC_CHECK_RESUMECONDITION, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,115,242,213,10 + CONTROL "Absolute:",IDC_ABSOLUTESTART,"Button",BS_AUTORADIOBUTTON,161,262,52,8 + EDITTEXT IDC_STARTTIME,222,259,142,12,ES_AUTOHSCROLL + CONTROL "Relative:",IDC_RELATIVESTART,"Button",BS_AUTORADIOBUTTON | WS_GROUP,161,277,54,8 + COMBOBOX IDC_TAGS,222,274,142,85,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP + CONTROL "End Time:",IDC_CHECK_ENDTIME,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,115,300,62,10 + EDITTEXT IDC_ENDTIME,222,299,142,12,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,7,342,50,14 + PUSHBUTTON "Cancel",IDCANCEL,7,359,50,14 + LTEXT "Type",IDC_TYPENAME,7,31,51,9 + LTEXT "Name:",IDC_STATIC,8,10,51,12 + LTEXT "Start Time:",IDC_STATIC,111,262,47,9 + LTEXT "INVISIBLE SPLINE PLACEHOLDER",IDC_STATIC_SPLINE,69,319,295,48,NOT WS_VISIBLE + CONTROL "Show All Sounds",IDC_SHOW_ALL_SOUNDS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,156,225,69,10 + LISTBOX IDC_SOUNDLIST,63,29,265,108,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_FILTER,63,188,265,120,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP + LTEXT "WAVEFILENAME",IDC_STATIC_WAVEFILENAME,63,160,265,11 + LTEXT "Wave File:",IDC_STATIC,7,160,50,11 + LTEXT "Script File:",IDC_STATIC,7,176,50,11 + LTEXT "SCRIPTFILE",IDC_STATIC_SCRIPTFILE,63,176,265,11 + PUSHBUTTON "Play Sound",IDC_PLAY_SOUND,7,223,66,13 + PUSHBUTTON "Open Source",IDC_OPENSOURCE,83,223,66,13 + EDITTEXT IDC_SOUNDNAME,63,143,265,13,ES_AUTOHSCROLL + LTEXT "Filter:",IDC_STATIC,7,190,50,11 + LTEXT "Sound:",IDC_STATIC,7,144,50,11 + LTEXT "Volume:",IDC_STATIC,7,208,50,11 + COMBOBOX IDC_EVENTCHOICES2,63,206,265,120,CBS_DROPDOWN | CBS_AUTOHSCROLL | CBS_SORT | WS_VSCROLL | WS_TABSTOP + CONTROL "Don't Attenuate Captions",IDC_CAPTION_ATTENUATION, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,242,95,10 + CONTROL "Disabled",IDC_CHECK_DISABLED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,261,74,10 +END + +IDD_EVENTPROPERTIES_GESTURE DIALOGEX 0, 0, 368, 241 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_EVENTNAME,65,9,256,15,ES_AUTOHSCROLL + CONTROL "Event must complete for paused scene to resume",IDC_CHECK_RESUMECONDITION, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,98,90,230,10 + CONTROL "Absolute:",IDC_ABSOLUTESTART,"Button",BS_AUTORADIOBUTTON,161,110,52,8 + EDITTEXT IDC_STARTTIME,219,107,142,12,ES_AUTOHSCROLL + CONTROL "Relative:",IDC_RELATIVESTART,"Button",BS_AUTORADIOBUTTON | WS_GROUP,161,125,54,8 + COMBOBOX IDC_TAGS,219,122,142,85,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP + CONTROL "End Time:",IDC_CHECK_ENDTIME,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,99,149,62,10 + EDITTEXT IDC_ENDTIME,219,147,142,12,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,7,190,50,14 + PUSHBUTTON "Cancel",IDCANCEL,7,209,50,14 + COMBOBOX IDC_EVENTCHOICES,65,31,256,144,CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP + LTEXT "Type",IDC_TYPENAME,7,31,57,9 + LTEXT "Name:",IDC_STATIC,8,10,51,12 + LTEXT "Start Time:",IDC_STATIC,111,110,47,9 + LTEXT "INVISIBLE SPLINE PLACEHOLDER",IDC_STATIC_SPLINE,66,170,295,48,NOT WS_VISIBLE + CONTROL "Sync Exit tag to next gestures Entry tag",IDC_CHECK_SYNCTOFOLLOWINGGESTURE, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,98,74,170,10 + CONTROL "Disabled",IDC_CHECK_DISABLED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,90,74,10 +END + +IDD_EVENTPROPERTIES_SEQUENCE DIALOGEX 0, 0, 368, 241 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_EVENTNAME,65,9,256,15,ES_AUTOHSCROLL + CONTROL "Event must complete for paused scene to resume",IDC_CHECK_RESUMECONDITION, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,98,90,230,10 + CONTROL "Absolute:",IDC_ABSOLUTESTART,"Button",BS_AUTORADIOBUTTON,161,110,52,8 + EDITTEXT IDC_STARTTIME,219,107,142,12,ES_AUTOHSCROLL + CONTROL "Relative:",IDC_RELATIVESTART,"Button",BS_AUTORADIOBUTTON | WS_GROUP,161,125,54,8 + COMBOBOX IDC_TAGS,219,122,142,85,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP + CONTROL "End Time:",IDC_CHECK_ENDTIME,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,99,149,62,10 + EDITTEXT IDC_ENDTIME,219,147,142,12,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,7,190,50,14 + PUSHBUTTON "Cancel",IDCANCEL,7,209,50,14 + COMBOBOX IDC_EVENTCHOICES,65,31,256,144,CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP + LTEXT "Type",IDC_TYPENAME,7,31,57,9 + LTEXT "Name:",IDC_STATIC,8,10,51,12 + LTEXT "Start Time:",IDC_STATIC,111,110,47,9 + LTEXT "INVISIBLE SPLINE PLACEHOLDER",IDC_STATIC_SPLINE,66,170,295,48,NOT WS_VISIBLE + CONTROL "Disabled",IDC_CHECK_DISABLED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,90,74,10 + CONTROL "Play Sequence Overtop Script",IDC_CHECK_PLAYOVERSCRIPT, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,98,73,223,10 +END + +IDD_EVENTPROPERTIES_FACE DIALOGEX 0, 0, 368, 241 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_EVENTNAME,65,9,256,15,ES_AUTOHSCROLL + CONTROL "Event must complete for paused scene to resume",IDC_CHECK_RESUMECONDITION, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,98,90,230,10 + CONTROL "Absolute:",IDC_ABSOLUTESTART,"Button",BS_AUTORADIOBUTTON,161,110,52,8 + EDITTEXT IDC_STARTTIME,219,107,142,12,ES_AUTOHSCROLL + CONTROL "Relative:",IDC_RELATIVESTART,"Button",BS_AUTORADIOBUTTON | WS_GROUP,161,125,54,8 + COMBOBOX IDC_TAGS,219,122,142,85,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP + CONTROL "End Time:",IDC_CHECK_ENDTIME,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,99,149,62,10 + EDITTEXT IDC_ENDTIME,219,147,142,12,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,7,190,50,14 + PUSHBUTTON "Cancel",IDCANCEL,7,209,50,14 + COMBOBOX IDC_EVENTCHOICES,65,31,256,144,CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP + LTEXT "Type",IDC_TYPENAME,7,31,57,9 + LTEXT "Name:",IDC_STATIC,8,10,51,12 + LTEXT "Start Time:",IDC_STATIC,111,110,47,9 + LTEXT "INVISIBLE SPLINE PLACEHOLDER",IDC_STATIC_SPLINE,66,170,295,48,NOT WS_VISIBLE + CONTROL "Lock facing (twist but don't turn)",IDC_CHECK_LOCKBODYFACING, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,65,53,230,10 + CONTROL "Disabled",IDC_CHECK_DISABLED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,90,74,10 +END + +IDD_EVENTPROPERTIES_FIRETRIGGER DIALOGEX 0, 0, 368, 241 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_EVENTNAME,65,9,256,15,ES_AUTOHSCROLL + CONTROL "Event must complete for paused scene to resume",IDC_CHECK_RESUMECONDITION, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,98,90,230,10 + CONTROL "Absolute:",IDC_ABSOLUTESTART,"Button",BS_AUTORADIOBUTTON,161,110,52,8 + EDITTEXT IDC_STARTTIME,219,107,142,12,ES_AUTOHSCROLL + CONTROL "Relative:",IDC_RELATIVESTART,"Button",BS_AUTORADIOBUTTON | WS_GROUP,161,125,54,8 + COMBOBOX IDC_TAGS,219,122,142,85,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP + CONTROL "End Time:",IDC_CHECK_ENDTIME,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,99,149,62,10 + EDITTEXT IDC_ENDTIME,219,147,142,12,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,7,190,50,14 + PUSHBUTTON "Cancel",IDCANCEL,7,209,50,14 + COMBOBOX IDC_EVENTCHOICES,65,31,256,144,CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP + LTEXT "Type",IDC_TYPENAME,7,31,57,9 + LTEXT "Name:",IDC_STATIC,8,10,51,12 + LTEXT "Start Time:",IDC_STATIC,111,110,47,9 + LTEXT "INVISIBLE SPLINE PLACEHOLDER",IDC_STATIC_SPLINE,66,170,295,48,NOT WS_VISIBLE + CONTROL "Disabled",IDC_CHECK_DISABLED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,90,74,10 +END + +IDD_EVENTPROPERTIES_FLEXANIMATION DIALOGEX 0, 0, 368, 241 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_EVENTNAME,65,9,256,15,ES_AUTOHSCROLL + CONTROL "Event must complete for paused scene to resume",IDC_CHECK_RESUMECONDITION, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,98,90,230,10 + CONTROL "Absolute:",IDC_ABSOLUTESTART,"Button",BS_AUTORADIOBUTTON,161,110,52,8 + EDITTEXT IDC_STARTTIME,219,107,142,12,ES_AUTOHSCROLL + CONTROL "Relative:",IDC_RELATIVESTART,"Button",BS_AUTORADIOBUTTON | WS_GROUP,161,125,54,8 + COMBOBOX IDC_TAGS,219,122,142,85,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP + CONTROL "End Time:",IDC_CHECK_ENDTIME,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,99,149,62,10 + EDITTEXT IDC_ENDTIME,219,147,142,12,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,7,190,50,14 + PUSHBUTTON "Cancel",IDCANCEL,7,209,50,14 + LTEXT "Type",IDC_TYPENAME,7,31,57,9 + LTEXT "Name:",IDC_STATIC,8,10,51,12 + LTEXT "Start Time:",IDC_STATIC,111,110,47,9 + LTEXT "INVISIBLE SPLINE PLACEHOLDER",IDC_STATIC_SPLINE,66,170,295,48,NOT WS_VISIBLE + CONTROL "Disabled",IDC_CHECK_DISABLED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,90,74,10 +END + +IDD_EVENTPROPERTIES_SUBSCENE DIALOGEX 0, 0, 368, 241 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_EVENTNAME,65,9,256,15,ES_AUTOHSCROLL + EDITTEXT IDC_FILENAME,65,30,256,14,ES_AUTOHSCROLL + CONTROL "Event must complete for paused scene to resume",IDC_CHECK_RESUMECONDITION, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,98,90,230,10 + CONTROL "Absolute:",IDC_ABSOLUTESTART,"Button",BS_AUTORADIOBUTTON,161,110,52,8 + EDITTEXT IDC_STARTTIME,219,107,142,12,ES_AUTOHSCROLL + CONTROL "Relative:",IDC_RELATIVESTART,"Button",BS_AUTORADIOBUTTON | WS_GROUP,161,125,54,8 + COMBOBOX IDC_TAGS,219,122,142,85,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP + CONTROL "End Time:",IDC_CHECK_ENDTIME,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,99,149,62,10 + EDITTEXT IDC_ENDTIME,219,147,142,12,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,7,190,50,14 + PUSHBUTTON "Cancel",IDCANCEL,7,209,50,14 + PUSHBUTTON "Choose File...",IDC_SELECTWAV,7,50,50,14 + COMBOBOX IDC_EVENTCHOICES,65,31,256,144,CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP + LTEXT "Type",IDC_TYPENAME,7,31,57,9 + LTEXT "Name:",IDC_STATIC,8,10,51,12 + LTEXT "Start Time:",IDC_STATIC,111,110,47,9 + LTEXT "INVISIBLE SPLINE PLACEHOLDER",IDC_STATIC_SPLINE,66,170,295,48,NOT WS_VISIBLE + CONTROL "Disabled",IDC_CHECK_DISABLED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,90,74,10 +END + +IDD_EVENTPROPERTIES_INTERRUPT DIALOGEX 0, 0, 368, 241 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_EVENTNAME,65,9,256,15,ES_AUTOHSCROLL + CONTROL "Absolute:",IDC_ABSOLUTESTART,"Button",BS_AUTORADIOBUTTON,161,110,52,8 + EDITTEXT IDC_STARTTIME,219,107,142,12,ES_AUTOHSCROLL + CONTROL "Relative:",IDC_RELATIVESTART,"Button",BS_AUTORADIOBUTTON | WS_GROUP,161,125,54,8 + COMBOBOX IDC_TAGS,219,122,142,85,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP + CONTROL "End Time:",IDC_CHECK_ENDTIME,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,99,149,62,10 + EDITTEXT IDC_ENDTIME,219,147,142,12,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,7,190,50,14 + PUSHBUTTON "Cancel",IDCANCEL,7,209,50,14 + COMBOBOX IDC_EVENTCHOICES,65,31,256,144,CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP + LTEXT "Type",IDC_TYPENAME,7,31,57,9 + LTEXT "Name:",IDC_STATIC,8,10,51,12 + LTEXT "Start Time:",IDC_STATIC,111,110,47,9 + LTEXT "INVISIBLE SPLINE PLACEHOLDER",IDC_STATIC_SPLINE,66,170,295,48,NOT WS_VISIBLE + CONTROL "Disabled",IDC_CHECK_DISABLED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,90,74,10 +END + +IDD_EVENTPROPERTIES_PERMITRESPONSES DIALOGEX 0, 0, 368, 241 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_EVENTNAME,65,9,256,15,ES_AUTOHSCROLL + CONTROL "Absolute:",IDC_ABSOLUTESTART,"Button",BS_AUTORADIOBUTTON,161,110,52,8 + EDITTEXT IDC_STARTTIME,219,107,142,12,ES_AUTOHSCROLL + CONTROL "Relative:",IDC_RELATIVESTART,"Button",BS_AUTORADIOBUTTON | WS_GROUP,161,125,54,8 + COMBOBOX IDC_TAGS,219,122,142,85,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP + CONTROL "End Time:",IDC_CHECK_ENDTIME,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,99,149,62,10 + EDITTEXT IDC_ENDTIME,219,147,142,12,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,7,190,50,14 + PUSHBUTTON "Cancel",IDCANCEL,7,209,50,14 + COMBOBOX IDC_EVENTCHOICES,65,31,256,144,CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP + LTEXT "Type",IDC_TYPENAME,7,31,57,9 + LTEXT "Name:",IDC_STATIC,8,10,51,12 + LTEXT "Start Time:",IDC_STATIC,111,110,47,9 + LTEXT "INVISIBLE SPLINE PLACEHOLDER",IDC_STATIC_SPLINE,66,170,295,48,NOT WS_VISIBLE + CONTROL "Disabled",IDC_CHECK_DISABLED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,90,74,10 +END + +IDD_EVENTPROPERTIES_GENERIC DIALOGEX 0, 0, 368, 241 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_EVENTNAME,65,9,256,15,ES_AUTOHSCROLL + COMBOBOX IDC_EVENTCHOICES2,65,50,256,184,CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP + CONTROL "Event must complete for paused scene to resume",IDC_CHECK_RESUMECONDITION, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,98,90,230,10 + CONTROL "Absolute:",IDC_ABSOLUTESTART,"Button",BS_AUTORADIOBUTTON,161,110,52,8 + EDITTEXT IDC_STARTTIME,219,107,142,12,ES_AUTOHSCROLL + CONTROL "Relative:",IDC_RELATIVESTART,"Button",BS_AUTORADIOBUTTON | WS_GROUP,161,125,54,8 + COMBOBOX IDC_TAGS,219,122,142,85,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP + CONTROL "End Time:",IDC_CHECK_ENDTIME,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,99,149,62,10 + EDITTEXT IDC_ENDTIME,219,147,142,12,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,7,190,50,14 + PUSHBUTTON "Cancel",IDCANCEL,7,209,50,14 + COMBOBOX IDC_EVENTCHOICES,65,31,256,144,CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP + LTEXT "Type",IDC_TYPENAME,7,31,57,9 + LTEXT "Name:",IDC_STATIC,8,10,51,12 + LTEXT "Start Time:",IDC_STATIC,111,110,47,9 + LTEXT "INVISIBLE SPLINE PLACEHOLDER",IDC_STATIC_SPLINE,66,170,295,48,NOT WS_VISIBLE + CONTROL "Disabled",IDC_CHECK_DISABLED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,90,74,10 +END + +IDD_EDGEPROPERTIES DIALOGEX 0, 0, 341, 115 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Edge Properties" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "OK",IDOK,275,28,50,14 + PUSHBUTTON "Cancel",IDCANCEL,275,46,50,14 + COMBOBOX IDC_LEFT_CURVETYPE,7,37,118,71,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_RIGHT_CURVETYPE,135,37,122,71,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Left Edge Interpolator Type:",IDC_STATIC,7,23,116,8 + LTEXT "Right Edge Interpolator Type:",IDC_STATIC,135,23,123,8 + CONTROL "Left Edge Active",IDC_LEFT_ACTIVE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,7,115,10 + CONTROL "Right Edge Active",IDC_RIGHT_ACTIVE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,135,7,123,10 + LTEXT "Edge Value:",IDC_STATIC,7,52,102,8 + EDITTEXT IDC_LEFT_ZEROVALUE,7,64,118,14,ES_AUTOHSCROLL + EDITTEXT IDC_RIGHT_ZEROVALUE,135,64,122,14,ES_AUTOHSCROLL + PUSHBUTTON "Reset",IDC_LEFT_RESET,35,94,50,14 + PUSHBUTTON "Reset",IDC_RIGHT_RESET,174,94,50,14 +END + +IDD_PROGRESS DIALOGEX 0, 0, 293, 71 +STYLE DS_SETFONT | WS_POPUP | WS_VISIBLE | WS_BORDER +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + LTEXT "",IDC_FP_PROGRESS_TITLE,7,7,279,12 + CONTROL "",IDC_FP_PROGRESS,"msctls_progress32",0x0,7,41,279,8 + LTEXT "",IDC_FP_PROGRESS_TEXT,7,25,222,9 + PUSHBUTTON "Cancel",IDCANCEL,236,51,50,13 + LTEXT "",IDC_FP_PROGRESS_PERCENT,244,25,42,9 + LTEXT "",IDC_FP_PROGRESS_ETA,7,55,203,9 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_ACTORPROPERTIES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 187 + TOPMARGIN, 7 + BOTTOMMARGIN, 74 + END + + IDD_INPUTDIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 373 + TOPMARGIN, 7 + BOTTOMMARGIN, 54 + END + + IDD_EXPRESSIONPROPERTIES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 200 + TOPMARGIN, 7 + BOTTOMMARGIN, 107 + END + + IDD_CHANNELPROPERTIES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 248 + TOPMARGIN, 7 + BOTTOMMARGIN, 75 + END + + IDD_EVENTPROPERTIES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 361 + TOPMARGIN, 7 + BOTTOMMARGIN, 234 + END + + IDD_PHONEMEPROPERTIES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 316 + TOPMARGIN, 7 + BOTTOMMARGIN, 144 + END + + IDD_GLOBALEVENTPROPERTIES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 300 + TOPMARGIN, 7 + BOTTOMMARGIN, 107 + END + + IDD_FLEXSLIDERS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 232 + TOPMARGIN, 7 + BOTTOMMARGIN, 189 + END + + IDD_CHOICEDIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 233 + TOPMARGIN, 7 + BOTTOMMARGIN, 104 + END + + IDD_EDITPHRASE, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 230 + TOPMARGIN, 7 + BOTTOMMARGIN, 132 + END + + IDD_WAVELOOKUP, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 444 + TOPMARGIN, 7 + BOTTOMMARGIN, 170 + END + + IDD_ADDSOUNDENTRY, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 266 + TOPMARGIN, 7 + BOTTOMMARGIN, 87 + END + + IDD_CCLOOKUP, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 613 + TOPMARGIN, 7 + BOTTOMMARGIN, 343 + END + + IDD_EVENTPROPERTIES_EXPRESSION, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 361 + TOPMARGIN, 7 + BOTTOMMARGIN, 234 + END + + IDD_EVENTPROPERTIES_LOOKAT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 361 + TOPMARGIN, 7 + BOTTOMMARGIN, 234 + END + + IDD_EVENTPROPERTIES_MOVETO, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 361 + TOPMARGIN, 7 + BOTTOMMARGIN, 252 + END + + IDD_EVENTPROPERTIES_SPEAK, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 364 + TOPMARGIN, 7 + BOTTOMMARGIN, 373 + END + + IDD_EVENTPROPERTIES_GESTURE, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 361 + TOPMARGIN, 7 + BOTTOMMARGIN, 234 + END + + IDD_EVENTPROPERTIES_SEQUENCE, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 361 + TOPMARGIN, 7 + BOTTOMMARGIN, 234 + END + + IDD_EVENTPROPERTIES_FACE, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 361 + TOPMARGIN, 7 + BOTTOMMARGIN, 234 + END + + IDD_EVENTPROPERTIES_FIRETRIGGER, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 361 + TOPMARGIN, 7 + BOTTOMMARGIN, 234 + END + + IDD_EVENTPROPERTIES_FLEXANIMATION, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 361 + TOPMARGIN, 7 + BOTTOMMARGIN, 234 + END + + IDD_EVENTPROPERTIES_SUBSCENE, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 361 + TOPMARGIN, 7 + BOTTOMMARGIN, 234 + END + + IDD_EVENTPROPERTIES_INTERRUPT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 361 + TOPMARGIN, 7 + BOTTOMMARGIN, 234 + END + + IDD_EVENTPROPERTIES_PERMITRESPONSES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 361 + TOPMARGIN, 7 + BOTTOMMARGIN, 234 + END + + IDD_EVENTPROPERTIES_GENERIC, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 361 + TOPMARGIN, 7 + BOTTOMMARGIN, 234 + END + + IDD_EDGEPROPERTIES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 334 + TOPMARGIN, 7 + BOTTOMMARGIN, 108 + END + + IDD_PROGRESS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 286 + TOPMARGIN, 7 + BOTTOMMARGIN, 64 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog Info +// + +IDD_GLOBALEVENTPROPERTIES DLGINIT +BEGIN + IDC_CB_AUTOACTION, 0x403, 13, 0 +0x6552, 0x7573, 0x656d, 0x5320, 0x6563, 0x656e, "\000" + IDC_CB_AUTOACTION, 0x403, 13, 0 +0x6143, 0x636e, 0x6c65, 0x5320, 0x6563, 0x656e, "\000" + 0 +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// German (Switzerland) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_DES) +#ifdef _WIN32 +LANGUAGE LANG_GERMAN, SUBLANG_GERMAN_SWISS +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +MX_ICON ICON "icon1.ico" + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // German (Switzerland) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/utils/hlfaceposer/hlfaceposer.vpc b/utils/hlfaceposer/hlfaceposer.vpc new file mode 100644 index 0000000..8cf2054 --- /dev/null +++ b/utils/hlfaceposer/hlfaceposer.vpc @@ -0,0 +1,411 @@ +//----------------------------------------------------------------------------- +// HLFACEPOSER.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_exe_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,$SRCDIR\utils\hlfaceposer,$SRCDIR\hlfaceposer,..\common,..\hlmv,$SRCDIR\game\shared,..\SAPI51\Include,.\lipsinc;..\common\shaderdll" + $PreprocessorDefinitions "$BASE;PROTECTED_THINGS_DISABLE" + } + + $Linker + { + $AdditionalDependencies "$BASE comctl32.lib winmm.lib Msimg32.lib" + $EntryPoint "mainCRTStartup" + } +} + +$Project "hlfaceposer" +{ + $Folder "Source Files" + { + $File "ActorProperties.cpp" + $File "ActorProperties.h" + $File "addsoundentry.cpp" + $File "addsoundentry.h" + $File "AnimationBrowser.cpp" + $File "AnimationBrowser.h" + $File "basedialogparams.cpp" + $File "basedialogparams.h" + $File "cbase.h" + $File "cclookup.cpp" + $File "cclookup.h" + $File "ChannelProperties.cpp" + $File "ChannelProperties.h" + $File "choiceproperties.cpp" + $File "choiceproperties.h" + $File "ChoreoView.cpp" + $File "ChoreoView.h" + $File "ChoreoViewColors.h" + $File "CloseCaptionTool.cpp" + $File "CloseCaptionTool.h" + $File "ControlPanel.cpp" + $File "ControlPanel.h" + $File "curveeditorhelpers.h" + $File "..\hlmv\debugdrawmodel.cpp" + $File "EdgeProperties.cpp" + $File "EdgeProperties.h" + $File "EditPhrase.cpp" + $File "EditPhrase.h" + $File "EventProperties.cpp" + $File "EventProperties.h" + $File "eventproperties_expression.cpp" + $File "eventproperties_expression.h" + $File "eventproperties_face.cpp" + $File "eventproperties_face.h" + $File "eventproperties_firetrigger.cpp" + $File "eventproperties_firetrigger.h" + $File "eventproperties_flexanimation.cpp" + $File "eventproperties_flexanimation.h" + $File "eventproperties_generic.cpp" + $File "eventproperties_generic.h" + $File "eventproperties_gesture.cpp" + $File "eventproperties_gesture.h" + $File "eventproperties_interrupt.cpp" + $File "eventproperties_interrupt.h" + $File "eventproperties_lookat.cpp" + $File "eventproperties_lookat.h" + $File "eventproperties_moveto.cpp" + $File "eventproperties_moveto.h" + $File "eventproperties_permitresponses.cpp" + $File "eventproperties_permitresponses.h" + $File "eventproperties_sequence.cpp" + $File "eventproperties_sequence.h" + $File "eventproperties_speak.cpp" + $File "eventproperties_speak.h" + $File "eventproperties_subscene.cpp" + $File "eventproperties_subscene.h" + $File "expclass.cpp" + $File "expclass.h" + $File "expression.cpp" + $File "expression.h" + $File "ExpressionProperties.cpp" + $File "ExpressionProperties.h" + $File "expressions.cpp" + $File "expressions.h" + $File "$SRCDIR\game\shared\expressionsample.h" + $File "ExpressionTool.cpp" + $File "ExpressionTool.h" + $File "faceposer_models.cpp" + $File "faceposer_models.h" + $File "faceposertoolwindow.cpp" + $File "faceposertoolwindow.h" + $File "FacePoserWorkspace.cpp" + $File "fileloaderthread.cpp" + $File "FlexPanel.cpp" + $File "FlexPanel.h" + $File "GestureTool.cpp" + $File "GestureTool.h" + $File "GlobalEventProperties.cpp" + $File "GlobalEventProperties.h" + $File "hlfaceposer.cpp" + $File "hlfaceposer.h" + $File "ICloseCaptionManager.h" + $File "ifaceposersound.h" + $File "ifaceposerworkspace.h" + $File "ifileloader.h" + $File "InputProperties.cpp" + $File "InputProperties.h" + $File "$SRCDIR\public\interpolatortypes.cpp" + $File "$SRCDIR\game\shared\interval.cpp" + $File "matsyswin.cpp" + $File "matsyswin.h" + $File "mdlviewer.cpp" + $File "mdlviewer.h" + $File "mxbitmapbutton.cpp" + $File "mxbitmapbutton.h" + $File "mxbitmaptools.cpp" + $File "mxbitmaptools.h" + $File "mxbitmapwindow.cpp" + $File "mxbitmapwindow.h" + $File "mxexpressionslider.cpp" + $File "mxexpressionslider.h" + $File "mxExpressionTab.cpp" + $File "mxexpressiontab.h" + $File "mxexpressiontray.cpp" + $File "mxexpressiontray.h" + $File "mxstatuswindow.cpp" + $File "mxstatuswindow.h" + $File "$SRCDIR\public\phonemeconverter.cpp" + $File "$SRCDIR\public\phonemeconverter.h" + $File "PhonemeEditor.cpp" + $File "PhonemeEditor.h" + $File "PhonemeEditorColors.h" + $File "PhonemeProperties.cpp" + $File "PhonemeProperties.h" + $File "ProgressDialog.cpp" + $File "ProgressDialog.h" + $File "RampTool.cpp" + $File "RampTool.h" + $File "SceneRampTool.cpp" + $File "SceneRampTool.h" + $File "$SRCDIR\public\sentence.cpp" + $File "$SRCDIR\public\sentence.h" + $File "soundlookup.cpp" + $File "soundlookup.h" + $File "$SRCDIR\public\SoundParametersInternal.cpp" + $File "tabwindow.cpp" + $File "tabwindow.h" + $File "TimelineItem.cpp" + $File "TimelineItem.h" + $File "vcdbrowser.cpp" + $File "vcdbrowser.h" + $File "wavebrowser.cpp" + $File "wavebrowser.h" + $File "wavefile.cpp" + $File "wavefile.h" + + $Folder "Choreo Widgets" + { + $File "ChoreoActorWidget.cpp" + $File "ChoreoActorWidget.h" + $File "ChoreoChannelWidget.cpp" + $File "ChoreoChannelWidget.h" + $File "ChoreoEventWidget.cpp" + $File "ChoreoEventWidget.h" + $File "ChoreoGlobalEventWidget.cpp" + $File "ChoreoGlobalEventWidget.h" + $File "ChoreoWidget.cpp" + $File "ChoreoWidget.h" + $File "ChoreoWidgetDrawHelper.cpp" + $File "ChoreoWidgetDrawHelper.h" + } + + $Folder "Vgui" + { + $File "faceposer_vgui.cpp" + $File "faceposer_vgui.h" + $File "VGuiWnd.cpp" + $File "VGuiWnd.h" + $File "$SRCDIR\public\vgui_controls\vgui_controls.cpp" + } + } + + $Folder "Resource Files" + { + $File "hlfaceposer.rc" + $File "icon1.ico" + $File "resource.h" + } + + $Folder "Shared Source Files" + { + $File "$SRCDIR\public\bone_setup.cpp" + $File "$SRCDIR\public\CollisionUtils.cpp" + $File "$SRCDIR\public\filesystem_helpers.cpp" + $File "$SRCDIR\public\filesystem_init.cpp" + $File "$SRCDIR\game\shared\iscenetokenprocessor.h" + $File "$SRCDIR\public\jigglebones.cpp" + $File "..\hlmv\physmesh.cpp" + $File "..\common\scriplib.cpp" + $File "$SRCDIR\public\soundcombiner.cpp" + $File "$SRCDIR\public\studio.cpp" + $File "..\hlmv\studio_flex.cpp" + $File "..\hlmv\studio_render.cpp" + $File "..\hlmv\studio_utils.cpp" + $File "..\hlmv\ViewerSettings.cpp" + } + + $Folder "Shared Header Files" + { + $File "$SRCDIR\public\mathlib\amd3dx.h" + $File "$SRCDIR\public\basehandle.h" + $File "$SRCDIR\public\tier0\basetypes.h" + $File "$SRCDIR\public\bitvec.h" + $File "$SRCDIR\public\bone_accessor.h" + $File "$SRCDIR\public\bone_setup.h" + $File "$SRCDIR\public\BSPFILE.H" + $File "$SRCDIR\public\bspflags.h" + $File "$SRCDIR\public\mathlib\bumpvects.h" + $File "$SRCDIR\public\tier1\characterset.h" + $File "$SRCDIR\public\tier1\checksum_crc.h" + $File "..\common\cmdlib.h" + $File "$SRCDIR\public\cmodel.h" + $File "$SRCDIR\public\CollisionUtils.h" + $File "$SRCDIR\public\Color.h" + $File "$SRCDIR\public\tier0\commonmacros.h" + $File "$SRCDIR\public\mathlib\compressed_light_cube.h" + $File "$SRCDIR\public\mathlib\compressed_vector.h" + $File "$SRCDIR\public\const.h" + $File "$SRCDIR\public\vphysics\constraints.h" + $File "$SRCDIR\public\tier1\convar.h" + $File "$SRCDIR\public\tier0\dbg.h" + $File "..\hlmv\debugdrawmodel.h" + $File "$SRCDIR\public\tier0\fasttimer.h" + $File "$SRCDIR\public\filesystem.h" + $File "$SRCDIR\public\filesystem_helpers.h" + $File "..\common\FileSystem_Tools.h" + $File "$SRCDIR\public\tier1\fmtstr.h" + $File "$SRCDIR\public\gametrace.h" + $File "$SRCDIR\public\appframework\IAppSystem.h" + $File "$SRCDIR\public\tier0\icommandline.h" + $File "$SRCDIR\public\engine\IEngineSound.h" + $File "$SRCDIR\public\ihandleentity.h" + $File "$SRCDIR\public\vstdlib\IKeyValuesSystem.h" + $File "$SRCDIR\public\vgui\ILocalize.h" + $File "$SRCDIR\public\materialsystem\imaterial.h" + $File "$SRCDIR\public\materialsystem\imaterialproxyfactory.h" + $File "$SRCDIR\public\materialsystem\imaterialsystem.h" + $File "$SRCDIR\public\materialsystem\imaterialsystemhardwareconfig.h" + $File "$SRCDIR\public\materialsystem\imaterialvar.h" + $File "$SRCDIR\public\materialsystem\imesh.h" + $File "$SRCDIR\public\tier1\interface.h" + $File "$SRCDIR\public\interpolatortypes.h" + $File "$SRCDIR\game\shared\interval.h" + $File "$SRCDIR\public\irecipientfilter.h" + $File "$SRCDIR\public\isoundcombiner.h" + $File "$SRCDIR\public\SoundEmitterSystem\isoundemittersystembase.h" + $File "$SRCDIR\public\ISpatialPartition.h" + $File "$SRCDIR\public\istudiorender.h" + $File "$SRCDIR\public\materialsystem\itexture.h" + $File "$SRCDIR\public\jigglebones.h" + $File "$SRCDIR\public\tier1\KeyValues.h" + $File "$SRCDIR\public\tier0\l2cache.h" + $File "mapentities.h" + $File "$SRCDIR\public\materialsystem\materialsystem_config.h" + $File "$SRCDIR\public\mathlib\mathlib.h" + $File "..\hlmv\matsyswin.h" + $File "..\hlmv\mdlviewer.h" + $File "$SRCDIR\public\tier0\mem.h" + $File "$SRCDIR\public\tier0\memalloc.h" + $File "$SRCDIR\public\tier0\memdbgoff.h" + $File "$SRCDIR\public\tier0\memdbgon.h" + $File "$SRCDIR\public\tier1\mempool.h" + $File "$SRCDIR\public\mouthinfo.h" + $File "..\..\public\mxtk\mx.h" + $File "..\..\public\mxtk\mxBmp.h" + $File "..\..\public\mxtk\mxButton.h" + $File "..\..\public\mxtk\mxCheckBox.h" + $File "..\..\public\mxtk\mxChoice.h" + $File "..\..\public\mxtk\mxChooseColor.h" + $File "..\..\public\mxtk\mxEvent.h" + $File "..\..\public\mxtk\mxFileDialog.h" + $File "..\..\public\mxtk\mxGlWindow.h" + $File "..\..\public\mxtk\mxGroupBox.h" + $File "..\..\public\mxtk\mxImage.h" + $File "..\..\public\mxtk\mxInit.h" + $File "..\..\public\mxtk\mxLabel.h" + $File "..\..\public\mxtk\mxLineEdit.h" + $File "..\..\public\mxtk\mxLinkedList.h" + $File "..\..\public\mxtk\mxListBox.h" + $File "..\..\public\mxtk\mxlistview.h" + $File "..\..\public\mxtk\mxMatSysWindow.h" + $File "..\..\public\mxtk\mxMenu.h" + $File "..\..\public\mxtk\mxMenuBar.h" + $File "..\..\public\mxtk\mxMessageBox.h" + $File "..\..\public\mxtk\mxpath.h" + $File "..\..\public\mxtk\mxPcx.h" + $File "..\..\public\mxtk\mxPopupMenu.h" + $File "..\..\public\mxtk\mxProgressBar.h" + $File "..\..\public\mxtk\mxRadioButton.h" + $File "..\..\public\mxtk\mxScrollbar.h" + $File "..\..\public\mxtk\mxSlider.h" + $File "..\..\public\mxtk\mxstring.h" + $File "..\..\public\mxtk\mxTab.h" + $File "..\..\public\mxtk\mxTga.h" + $File "..\..\public\mxtk\mxToggleButton.h" + $File "..\..\public\mxtk\mxToolTip.h" + $File "..\..\public\mxtk\mxTreeView.h" + $File "..\..\public\mxtk\mxWidget.h" + $File "..\..\public\mxtk\mxWindow.h" + $File "$SRCDIR\public\networkvar.h" + $File "$SRCDIR\game\shared\npcevent.h" + $File "$SRCDIR\public\optimize.h" + $File "$SRCDIR\public\phonemeextractor\phonemeextractor.h" + $File "$SRCDIR\public\phyfile.h" + $File "..\common\physdll.h" + $File "..\hlmv\physmesh.h" + $File "$SRCDIR\public\tier0\platform.h" + $File "$SRCDIR\public\tier0\protected_things.h" + $File "$SRCDIR\public\vstdlib\random.h" + $File "..\sapi51\Include\sapi.h" + $File "..\sapi51\Include\sapiddk.h" + $File "..\common\scriplib.h" + $File "$SRCDIR\game\shared\sharedInterface.h" + $File "$SRCDIR\public\soundchars.h" + $File "$SRCDIR\public\soundflags.h" + $File "..\sapi51\Include\Spddkhlp.h" + $File "..\sapi51\Include\spdebug.h" + $File "..\sapi51\Include\sperror.h" + $File "..\sapi51\Include\sphelper.h" + $File "$SRCDIR\public\string_t.h" + $File "$SRCDIR\public\tier1\strtools.h" + $File "$SRCDIR\public\studio.h" + $File "..\hlmv\studio_render.h" + $File "..\hlmv\StudioModel.h" + $File "$SRCDIR\public\texture_group_names.h" + $File "$SRCDIR\public\tier1\utlbuffer.h" + $File "$SRCDIR\public\tier1\utldict.h" + $File "$SRCDIR\public\tier1\utllinkedlist.h" + $File "$SRCDIR\public\tier1\utlmemory.h" + $File "$SRCDIR\public\tier1\utlmultilist.h" + $File "$SRCDIR\public\tier1\utlrbtree.h" + $File "$SRCDIR\public\tier1\utlsymbol.h" + $File "$SRCDIR\public\tier1\utlvector.h" + $File "$SRCDIR\public\vcollide.h" + $File "$SRCDIR\public\vcollide_parse.h" + $File "$SRCDIR\public\mathlib\vector.h" + $File "$SRCDIR\public\mathlib\vector2d.h" + $File "$SRCDIR\public\mathlib\vector4d.h" + $File "$SRCDIR\public\vgui\VGUI.h" + $File "..\hlmv\ViewerSettings.h" + $File "$SRCDIR\public\mathlib\vmatrix.h" + $File "$SRCDIR\public\vphysics_interface.h" + $File "$SRCDIR\public\mathlib\vplane.h" + $File "$SRCDIR\public\tier0\vprof.h" + $File "$SRCDIR\public\vstdlib\vstdlib.h" + $File "$SRCDIR\public\zip_uncompressed.h" + } + + $Folder "Choreography" + { + $File "$SRCDIR\game\shared\choreoactor.h" + $File "$SRCDIR\game\shared\choreochannel.h" + $File "$SRCDIR\game\shared\choreoevent.h" + $File "$SRCDIR\game\shared\choreoscene.h" + $File "$SRCDIR\game\shared\ichoreoeventcallback.h" + } + + $Folder "Audio Code" + { + $File "AudioWaveOutput.h" + $File "snd_audio_source.cpp" + $File "snd_audio_source.h" + $File "snd_wave_mixer.cpp" + $File "snd_wave_mixer.h" + $File "snd_wave_mixer_adpcm.cpp" + $File "snd_wave_mixer_adpcm.h" + $File "snd_wave_mixer_private.h" + $File "snd_wave_source.cpp" + $File "snd_wave_source.h" + $File "sound.cpp" + $File "sound.h" + } + + $Folder "Link Libraries" + { + $Lib appframework + $Lib bitmap + $Lib choreoobjects + $Lib mathlib + $Lib $LIBCOMMON\mxtoolkitwin32 + $Lib tier2 + $Lib tier3 + $Lib datamodel + $Lib dme_controls + $Lib dmserializers + $Lib matsys_controls + $Lib movieobjects + $Lib vgui_controls + $Lib $LIBCOMMON\lzma + } +} diff --git a/utils/hlfaceposer/ico00001.ico b/utils/hlfaceposer/ico00001.ico Binary files differnew file mode 100644 index 0000000..d766cf9 --- /dev/null +++ b/utils/hlfaceposer/ico00001.ico diff --git a/utils/hlfaceposer/ico00002.ico b/utils/hlfaceposer/ico00002.ico Binary files differnew file mode 100644 index 0000000..6cda0f6 --- /dev/null +++ b/utils/hlfaceposer/ico00002.ico diff --git a/utils/hlfaceposer/ico00003.ico b/utils/hlfaceposer/ico00003.ico Binary files differnew file mode 100644 index 0000000..b61e1b1 --- /dev/null +++ b/utils/hlfaceposer/ico00003.ico diff --git a/utils/hlfaceposer/ico00004.ico b/utils/hlfaceposer/ico00004.ico Binary files differnew file mode 100644 index 0000000..ff2e77c --- /dev/null +++ b/utils/hlfaceposer/ico00004.ico diff --git a/utils/hlfaceposer/ico00005.ico b/utils/hlfaceposer/ico00005.ico Binary files differnew file mode 100644 index 0000000..1fa01d2 --- /dev/null +++ b/utils/hlfaceposer/ico00005.ico diff --git a/utils/hlfaceposer/ico00006.ico b/utils/hlfaceposer/ico00006.ico Binary files differnew file mode 100644 index 0000000..8251647 --- /dev/null +++ b/utils/hlfaceposer/ico00006.ico diff --git a/utils/hlfaceposer/ico00007.ico b/utils/hlfaceposer/ico00007.ico Binary files differnew file mode 100644 index 0000000..35868d9 --- /dev/null +++ b/utils/hlfaceposer/ico00007.ico diff --git a/utils/hlfaceposer/icon1.ico b/utils/hlfaceposer/icon1.ico Binary files differnew file mode 100644 index 0000000..01d03a8 --- /dev/null +++ b/utils/hlfaceposer/icon1.ico diff --git a/utils/hlfaceposer/icon2.ico b/utils/hlfaceposer/icon2.ico Binary files differnew file mode 100644 index 0000000..41443c5 --- /dev/null +++ b/utils/hlfaceposer/icon2.ico diff --git a/utils/hlfaceposer/ifaceposersound.h b/utils/hlfaceposer/ifaceposersound.h new file mode 100644 index 0000000..59e9cb9 --- /dev/null +++ b/utils/hlfaceposer/ifaceposersound.h @@ -0,0 +1,55 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#ifndef IFACEPOSERSOUND_H +#define IFACEPOSERSOUND_H +#ifdef _WIN32 +#pragma once +#endif + +class StudioModel; +class CAudioSource; +class CAudioMixer; +class CAudioOuput; + +class IFacePoserSound +{ +public: + + virtual void Init( void ) = 0; + virtual void Shutdown( void ) = 0; + virtual void Update( float time ) = 0; + virtual void Flush( void ) = 0; + + virtual CAudioSource *LoadSound( const char *wavfile ) = 0; + + virtual void PlaySound( StudioModel *model, float volume, const char *wavfile, CAudioMixer **ppMixer ) = 0; + virtual void PlaySound( CAudioSource *source, float volume, CAudioMixer **ppMixer ) = 0; + virtual void PlayPartialSound( StudioModel *model, float volume, const char *wavfile, CAudioMixer **ppMixer, int startSample, int endSample ) = 0; + + virtual bool IsSoundPlaying( CAudioMixer *pMixer ) = 0; + virtual CAudioMixer *FindMixer( CAudioSource *source ) = 0; + + virtual void StopAll( void ) = 0; + virtual void StopSound( CAudioMixer *mixer ) = 0; + + virtual void RenderWavToDC( HDC dc, RECT& outrect, COLORREF clr, + float starttime, float endtime, CAudioSource *pWave, + bool selected = false, int selectionstart = 0, int selectionend = 0 ) = 0; + + virtual float GetAmountofTimeAhead( void ) = 0; + virtual int GetNumberofSamplesAhead( void ) = 0; + + // virtual void InstallPhonemecallback( IPhonemeTag *pTagInterface ) = 0; + + virtual CAudioOuput *GetAudioOutput( void ) = 0; + + virtual void EnsureNoModelReferences( CAudioSource *source ) = 0; +}; + +extern IFacePoserSound *sound; + +#endif // IFACEPOSERSOUND_H diff --git a/utils/hlfaceposer/ifaceposerworkspace.h b/utils/hlfaceposer/ifaceposerworkspace.h new file mode 100644 index 0000000..83e3482 --- /dev/null +++ b/utils/hlfaceposer/ifaceposerworkspace.h @@ -0,0 +1,40 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef IFACEPOSERWORKSPACE_H +#define IFACEPOSERWORKSPACE_H +#ifdef _WIN32 +#pragma once +#endif + +class IWorkspaceFiles +{ +public: + enum + { + EXPRESSION = 0, + CHOREODATA, + MODELDATA, + + NUM_FILE_TYPES + }; + + virtual void Init( char const *pchShortName ) = 0; + + // Restore + virtual int GetNumStoredFiles( int type ) = 0; + virtual const char *GetStoredFile( int type, int number ) = 0; + + // Save + virtual void StartStoringFiles( int type ) = 0; + virtual void FinishStoringFiles( int type ) = 0; + virtual void StoreFile( int type, const char *filename ) = 0; +}; + +extern IWorkspaceFiles *workspacefiles; + +#endif // IFACEPOSERWORKSPACE_H diff --git a/utils/hlfaceposer/ifileloader.h b/utils/hlfaceposer/ifileloader.h new file mode 100644 index 0000000..8f13b26 --- /dev/null +++ b/utils/hlfaceposer/ifileloader.h @@ -0,0 +1,31 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef IFILELOADER_H +#define IFILELOADER_H +#ifdef _WIN32 +#pragma once +#endif + +#include "utlvector.h" + +class CWaveFile; + +class IFileLoader +{ +public: + virtual void AddWaveFilesToThread( CUtlVector< CWaveFile * >& wavefiles ) = 0; + + virtual int ProcessCompleted() = 0; + + virtual void Start() = 0; + + virtual int GetPendingLoadCount() = 0; +}; + +extern IFileLoader *fileloader; + +#endif // IFILELOADER_H diff --git a/utils/hlfaceposer/inputproperties.cpp b/utils/hlfaceposer/inputproperties.cpp new file mode 100644 index 0000000..551d6f4 --- /dev/null +++ b/utils/hlfaceposer/inputproperties.cpp @@ -0,0 +1,80 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "resource.h" +#include "InputProperties.h" +#include "ChoreoView.h" +#include "choreoactor.h" +#include "mdlviewer.h" + +static CInputParams g_Params; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK InputPropertiesDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + switch(uMsg) + { + case WM_INITDIALOG: + // Insert code here to put the string (to find and replace with) + // into the edit controls. + // ... + { + g_Params.PositionSelf( hwndDlg ); + + SetDlgItemText( hwndDlg, IDC_INPUTSTRING, g_Params.m_szInputText ); + SetDlgItemText( hwndDlg, IDC_STATIC_PROMPT, g_Params.m_szPrompt ); + + SetWindowText( hwndDlg, g_Params.m_szDialogTitle ); + + SetFocus( GetDlgItem( hwndDlg, IDC_INPUTSTRING ) ); + SendMessage( GetDlgItem( hwndDlg, IDC_INPUTSTRING ), EM_SETSEL, 0, MAKELONG(0, 0xffff) ); + + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + g_Params.m_szInputText[ 0 ] = 0; + GetDlgItemText( hwndDlg, IDC_INPUTSTRING, g_Params.m_szInputText, sizeof( g_Params.m_szInputText ) ); + EndDialog( hwndDlg, 1 ); + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int InputProperties( CInputParams *params ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_INPUTDIALOG ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)InputPropertiesDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/inputproperties.h b/utils/hlfaceposer/inputproperties.h new file mode 100644 index 0000000..250ccfa --- /dev/null +++ b/utils/hlfaceposer/inputproperties.h @@ -0,0 +1,29 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef INPUTPROPERTIES_H +#define INPUTPROPERTIES_H +#ifdef _WIN32 +#pragma once +#endif + +#include "basedialogparams.h" +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +struct CInputParams : public CBaseDialogParams +{ + char m_szPrompt[ 256 ]; + + // i/o input text + char m_szInputText[ 1024 ]; +}; + +// Display/create dialog +int InputProperties( CInputParams *params ); + +#endif // INPUTPROPERTIES_H diff --git a/utils/hlfaceposer/mapentities.h b/utils/hlfaceposer/mapentities.h new file mode 100644 index 0000000..7362701 --- /dev/null +++ b/utils/hlfaceposer/mapentities.h @@ -0,0 +1,29 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef MAPENTITIES_H +#define MAPENTITIES_H +#ifdef _WIN32 +#pragma once +#endif + +class Vector; + +class IMapEntities +{ +public: + virtual void CheckUpdateMap( char const *mapname ) = 0; + virtual bool LookupOrigin( char const *name, Vector& origin, QAngle& angles ) = 0; + + virtual int Count() = 0; + virtual char const *GetName( int number ) = 0; + +}; + +extern IMapEntities *mapentities; + +#endif // MAPENTITIES_H diff --git a/utils/hlfaceposer/matsyswin.cpp b/utils/hlfaceposer/matsyswin.cpp new file mode 100644 index 0000000..7675ddf --- /dev/null +++ b/utils/hlfaceposer/matsyswin.cpp @@ -0,0 +1,806 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include <mxtk/mx.h> +#include <mxtk/mxMessageBox.h> +#include <mxtk/mxTga.h> +#include <mxtk/mxPcx.h> +#include <mxtk/mxBmp.h> +#include <mxtk/mxMatSysWindow.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <string.h> +#include "MatSysWin.h" +#include "MDLViewer.h" +#include "StudioModel.h" +#include "ControlPanel.h" +#include "ViewerSettings.h" +#include "materialsystem/imaterialsystem.h" +#include "materialsystem/imaterialproxyfactory.h" +#include "filesystem.h" +#include <keyvalues.h> +#include "materialsystem/imesh.h" +#include "expressions.h" +#include "hlfaceposer.h" +#include "ifaceposersound.h" +#include "materialsystem/IMaterialSystemHardwareConfig.h" +#include "materialsystem/itexture.h" +#include "materialsystem/MaterialSystem_Config.h" +#include "istudiorender.h" +#include "choreowidgetdrawhelper.h" +#include "faceposer_models.h" +#include "tier0/icommandline.h" +#include "mathlib/vmatrix.h" +#include "vstdlib/cvar.h" + +IFileSystem *filesystem = NULL; + +extern char g_appTitle[]; + +// FIXME: move all this to mxMatSysWin + +class DummyMaterialProxyFactory : public IMaterialProxyFactory +{ +public: + virtual IMaterialProxy *CreateProxy( const char *proxyName ) {return NULL;} + virtual void DeleteProxy( IMaterialProxy *pProxy ) {} +}; +DummyMaterialProxyFactory g_DummyMaterialProxyFactory; + + +static void ReleaseMaterialSystemObjects() +{ + StudioModel::ReleaseStudioModel(); + models->ReleaseModels(); +} + +static void RestoreMaterialSystemObjects( int nChangeFlags ) +{ + StudioModel::RestoreStudioModel(); + models->RestoreModels(); +} + +void InitMaterialSystemConfig(MaterialSystem_Config_t *pConfig) +{ + pConfig->SetFlag( MATSYS_VIDCFG_FLAGS_USING_MULTIPLE_WINDOWS, true ); +} + +Vector g_vright( 50, 50, 0 ); // needs to be set to viewer's right in order for chrome to work + +IMaterial *g_materialBackground = NULL; +IMaterial *g_materialWireframe = NULL; +IMaterial *g_materialWireframeVertexColor = NULL; +IMaterial *g_materialWireframeVertexColorNoCull = NULL; +IMaterial *g_materialDebugCopyBaseTexture = NULL; +IMaterial *g_materialFlatshaded = NULL; +IMaterial *g_materialSmoothshaded = NULL; +IMaterial *g_materialBones = NULL; +IMaterial *g_materialLines = NULL; +IMaterial *g_materialFloor = NULL; +IMaterial *g_materialVertexColor = NULL; +IMaterial *g_materialShadow = NULL; + +MatSysWindow *g_pMatSysWindow = 0; + +#define MATSYSWIN_NAME "3D View" + +MatSysWindow::MatSysWindow (mxWindow *parent, int x, int y, int w, int h, const char *label, int style) +: IFacePoserToolWindow( "3D View", "3D View" ), mxMatSysWindow ( parent, x, y, w, h, label, style ) +{ + g_pMaterialSystem->SetMaterialProxyFactory( &g_DummyMaterialProxyFactory ); + + SetAutoProcess( true ); + + setLabel( MATSYSWIN_NAME ); + + m_bSuppressSwap = false; + + m_hWnd = (HWND)getHandle(); + + Con_Printf( "Setting material system video mode\n" ); + + MaterialSystem_Config_t config; + config = g_pMaterialSystem->GetCurrentConfigForVideoCard(); + InitMaterialSystemConfig(&config); + g_pMaterialSystem->OverrideConfig( config, false ); + +// config.m_VideoMode.m_Width = config.m_VideoMode.m_Height = 0; + config.SetFlag( MATSYS_VIDCFG_FLAGS_WINDOWED, true ); + config.SetFlag( MATSYS_VIDCFG_FLAGS_RESIZING, true ); + + if (!g_pMaterialSystem->SetMode( ( void * )m_hWnd, config ) ) + { + return; + } + g_pMaterialSystem->AddReleaseFunc( ReleaseMaterialSystemObjects ); + g_pMaterialSystem->AddRestoreFunc( RestoreMaterialSystemObjects ); + + Con_Printf( "Loading debug materials\n" ); + + ITexture *pCubemapTexture = g_pMaterialSystem->FindTexture( "hlmv/cubemap", NULL, true ); + pCubemapTexture->IncrementReferenceCount(); + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->BindLocalCubemap( pCubemapTexture ); + + g_materialBackground = g_pMaterialSystem->FindMaterial("particle/particleapp_background", TEXTURE_GROUP_OTHER, true); + g_materialWireframe = g_pMaterialSystem->FindMaterial("debug/debugmrmwireframe", TEXTURE_GROUP_OTHER, true); + g_materialWireframeVertexColor = g_pMaterialSystem->FindMaterial("debug/debugwireframevertexcolor", TEXTURE_GROUP_OTHER, true); + + // test: create this from code - you need a vmt to make $nocull 1 happen, can't do it from the render context + { + KeyValues *pVMTKeyValues = new KeyValues( "Wireframe" ); + pVMTKeyValues->SetInt("$ignorez", 1); + pVMTKeyValues->SetInt("$nocull", 1); + pVMTKeyValues->SetInt("$vertexcolor", 1); + pVMTKeyValues->SetInt("$decal", 1); + g_materialWireframeVertexColorNoCull = g_pMaterialSystem->CreateMaterial( "debug/wireframenocull", pVMTKeyValues ); + } + { + KeyValues *pVMTKeyValues = new KeyValues( "UnlitGeneric" ); + pVMTKeyValues->SetString("$basetexture", "vgui/white" ); + g_materialDebugCopyBaseTexture = g_pMaterialSystem->CreateMaterial( "debug/copybasetexture", pVMTKeyValues ); + + } + + g_materialFlatshaded = g_pMaterialSystem->FindMaterial("debug/debugdrawflatpolygons", TEXTURE_GROUP_OTHER, true); + g_materialSmoothshaded = g_pMaterialSystem->FindMaterial("debug/debugmrmfullbright2", TEXTURE_GROUP_OTHER, true); + g_materialBones = g_pMaterialSystem->FindMaterial("debug/debugmrmwireframe", TEXTURE_GROUP_OTHER, true); + g_materialLines = g_pMaterialSystem->FindMaterial("debug/debugwireframevertexcolor", TEXTURE_GROUP_OTHER, true); + g_materialFloor = g_pMaterialSystem->FindMaterial("hlmv/floor", TEXTURE_GROUP_OTHER, true); + g_materialVertexColor = g_pMaterialSystem->FindMaterial("debug/debugvertexcolor", TEXTURE_GROUP_OTHER, true); + g_materialShadow = g_pMaterialSystem->FindMaterial("hlmv/shadow", TEXTURE_GROUP_OTHER, true); + + if (!parent) + setVisible (true); + else + mx::setIdleWindow (this); + + m_bSuppressResize = false; + + m_stickyDepth = 0; + m_bIsSticky = false; + m_snapshotDepth = 0; +} + + + +MatSysWindow::~MatSysWindow () +{ + mx::setIdleWindow (0); +} + +void MatSysWindow::redraw() +{ + BaseClass::redraw(); +return; + if ( IsLocked() ) + { + RECT bounds; + GetClientRect( (HWND)getHandle(), &bounds ); + bounds.bottom = bounds.top + GetCaptionHeight(); + CChoreoWidgetDrawHelper helper( this, bounds ); + HandleToolRedraw( helper ); + } +} + +#define MAX_FPS 250.0f +#define MIN_TIMESTEP ( 1.0f / MAX_FPS ) + +double realtime = 0.0f; + +void MatSysWindow::Frame( void ) +{ + static bool recursion_guard = false; + + static double prev = 0.0; + double curr = (double) mx::getTickCount () / 1000.0; + double dt = ( curr - prev ); + + if ( recursion_guard ) + return; + + recursion_guard = true; + + // clamp to MAX_FPS + if ( dt >= 0.0 && dt < MIN_TIMESTEP ) + { + Sleep( max( 0, (int)( ( MIN_TIMESTEP - dt ) * 1000.0f ) ) ); + + recursion_guard = false; + return; + } + + if ( prev != 0.0 ) + { + dt = min( 0.1, dt ); + + g_MDLViewer->Think( dt ); + + realtime += dt; + } + + prev = curr; + + DrawFrame(); + + recursion_guard = false; +} + +void MatSysWindow::DrawFrame( void ) +{ + if (!g_viewerSettings.pause) + { + redraw (); + } +} + +int MatSysWindow::handleEvent (mxEvent *event) +{ + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + int iret = 0; + + if ( HandleToolEvent( event ) ) + { + return iret; + } + + static float oldrx = 0, oldry = 0, oldtz = 50, oldtx = 0, oldty = 0; + static float oldlrx = 0, oldlry = 0; + static int oldx, oldy; + + switch (event->event) + { + case mxEvent::Idle: + { + iret = 1; + + Frame(); + } + break; + + case mxEvent::MouseDown: + { + StudioModel *pModel = models->GetActiveStudioModel(); + if (!pModel) + break; + oldrx = pModel->m_angles[0]; + oldry = pModel->m_angles[1]; + oldtx = pModel->m_origin[0]; + oldty = pModel->m_origin[1]; + oldtz = pModel->m_origin[2]; + oldx = (short)event->x; + oldy = (short)event->y; + oldlrx = g_viewerSettings.lightrot[0]; + oldlry = g_viewerSettings.lightrot[1]; + g_viewerSettings.pause = false; + + float r = 1.0/3.0 * min( w(), h() ); + + float d = sqrt( ( float )( (event->x - w()/2) * (event->x - w()/2) + (event->y - h()/2) * (event->y - h()/2) ) ); + + if (d < r) + g_viewerSettings.rotating = false; + else + g_viewerSettings.rotating = true; + + iret = 1; + } + break; + + case mxEvent::MouseDrag: + { + StudioModel *pModel = models->GetActiveStudioModel(); + if (!pModel) + break; + + if (event->buttons & mxEvent::MouseLeftButton) + { + if (event->modifiers & mxEvent::KeyShift) + { + pModel->m_origin[1] = oldty - (float) ((short)event->x - oldx) * 0.1; + pModel->m_origin[2] = oldtz + (float) ((short)event->y - oldy) * 0.1; + } + else if (event->modifiers & mxEvent::KeyCtrl) + { + float ry = (float) (event->y - oldy); + float rx = (float) (event->x - oldx); + oldx = event->x; + oldy = event->y; + + QAngle movement = QAngle( ry, rx, 0 ); + + matrix3x4_t tmp1, tmp2, tmp3; + AngleMatrix( g_viewerSettings.lightrot, tmp1 ); + AngleMatrix( movement, tmp2 ); + ConcatTransforms( tmp2, tmp1, tmp3 ); + MatrixAngles( tmp3, g_viewerSettings.lightrot ); + } + else + { + if (!g_viewerSettings.rotating) + { + float ry = (float) (event->y - oldy); + float rx = (float) (event->x - oldx); + oldx = event->x; + oldy = event->y; + + QAngle movement; + matrix3x4_t tmp1, tmp2, tmp3; + + movement = QAngle( 0, rx, 0 ); + AngleMatrix( pModel->m_angles, tmp1 ); + AngleMatrix( movement, tmp2 ); + ConcatTransforms( tmp1, tmp2, tmp3 ); + MatrixAngles( tmp3, pModel->m_angles ); + + movement = QAngle( ry, 0, 0 ); + AngleMatrix( pModel->m_angles, tmp1 ); + AngleMatrix( movement, tmp2 ); + ConcatTransforms( tmp2, tmp1, tmp3 ); + MatrixAngles( tmp3, pModel->m_angles ); + } + else + { + float ang1 = (180 / 3.1415) * atan2( oldx - w()/2.0, oldy - h()/2.0 ); + float ang2 = (180 / 3.1415) * atan2( event->x - w()/2.0, event->y - h()/2.0 ); + oldx = event->x; + oldy = event->y; + + QAngle movement = QAngle( 0, 0, ang2 - ang1 ); + + matrix3x4_t tmp1, tmp2, tmp3; + AngleMatrix( pModel->m_angles, tmp1 ); + AngleMatrix( movement, tmp2 ); + ConcatTransforms( tmp2, tmp1, tmp3 ); + MatrixAngles( tmp3, pModel->m_angles ); + } + } + } + else if (event->buttons & mxEvent::MouseRightButton) + { + pModel->m_origin[0] = oldtx + (float) ((short)event->y - oldy) * 0.1; + pModel->m_origin[0] = clamp( pModel->m_origin[0], 8.0f, 1024.0f ); + } + redraw (); + + iret = 1; + } + break; + + case mxEvent::KeyDown: + { + iret = 1; + switch (event->key) + { + default: + iret = 0; + break; + case 116: // F5 + { + g_MDLViewer->Refresh(); + } + break; + case 32: + { + int iSeq = models->GetActiveStudioModel()->GetSequence(); + if (iSeq == models->GetActiveStudioModel()->SetSequence (iSeq + 1)) + { + g_pControlPanel->setSequence( 0 ); + } + else + { + g_pControlPanel->setSequence( iSeq + 1 ); + } + } + break; + + case 27: + if (!getParent ()) // fullscreen mode ? + mx::quit (); + break; + + case 'g': + g_viewerSettings.showGround = !g_viewerSettings.showGround; + break; + + case 'h': + g_viewerSettings.showHitBoxes = !g_viewerSettings.showHitBoxes; + break; + + case 'o': + g_viewerSettings.showBones = !g_viewerSettings.showBones; + break; + + case 'b': + g_viewerSettings.showBackground = !g_viewerSettings.showBackground; + break; + + case 'm': + g_viewerSettings.showMovement = !g_viewerSettings.showMovement; + break; + + case '1': + case '2': + case '3': + case '4': + g_viewerSettings.renderMode = event->key - '1'; + break; + + case '-': + g_viewerSettings.speedScale -= 0.1f; + if (g_viewerSettings.speedScale < 0.0f) + g_viewerSettings.speedScale = 0.0f; + break; + + case '+': + g_viewerSettings.speedScale += 0.1f; + if (g_viewerSettings.speedScale > 5.0f) + g_viewerSettings.speedScale = 5.0f; + break; + } + } + break; + } // switch (event->event) + + return iret; +} + + + +void +drawFloor () +{ + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->Bind(g_materialFloor); + pRenderContext->MatrixMode(MATERIAL_MODEL); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + pRenderContext->MatrixMode(MATERIAL_VIEW); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + { + IMesh* pMesh = pRenderContext->GetDynamicMesh(); + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); + + float dist=-15000.0f; + float tMin=0, tMax=1; + + meshBuilder.Position3f(-dist, dist, dist); + meshBuilder.TexCoord2f( 0, tMin,tMax ); + meshBuilder.Color4ub( 255, 255, 255, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( dist, dist, dist); + meshBuilder.TexCoord2f( 0, tMax,tMax ); + meshBuilder.Color4ub( 255, 255, 255, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( dist,-dist, dist); + meshBuilder.TexCoord2f( 0, tMax,tMin ); + meshBuilder.Color4ub( 255, 255, 255, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f(-dist,-dist, dist); + meshBuilder.TexCoord2f( 0, tMin,tMin ); + meshBuilder.Color4ub( 255, 255, 255, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + pMesh->Draw(); + } + pRenderContext->MatrixMode(MATERIAL_MODEL); + pRenderContext->PopMatrix(); + pRenderContext->MatrixMode(MATERIAL_VIEW); + pRenderContext->PopMatrix(); +} + + + +void +setupRenderMode () +{ +} + +void MatSysWindow::SuppressBufferSwap( bool bSuppress ) +{ + m_bSuppressSwap = bSuppress; +} + +void MatSysWindow::draw () +{ + int i; + + g_pMaterialSystem->BeginFrame( 0 ); + CUtlVector< StudioModel * > modellist; + + modellist.AddToTail( models->GetActiveStudioModel() ); + + if ( models->CountVisibleModels() > 0 ) + { + modellist.RemoveAll(); + for ( i = 0; i < models->Count(); i++ ) + { + if ( models->IsModelShownIn3DView( i ) ) + { + modellist.AddToTail( models->GetStudioModel( i ) ); + } + } + } + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->ClearBuffers(true, true); + + int captiony = GetCaptionHeight(); + int viewh = h2() - captiony; + + g_pMaterialSystem->SetView( (HWND)getHandle() ); + + pRenderContext->Viewport( 0, captiony, w2(), viewh ); + + pRenderContext->MatrixMode( MATERIAL_PROJECTION ); + pRenderContext->LoadIdentity( ); + pRenderContext->PerspectiveX(20.0f, (float)w2() / (float)viewh, 1.0f, 20000.0f); + + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->LoadIdentity( ); + // FIXME: why is this needed? Doesn't SetView() override this? + pRenderContext->Rotate( -90, 1, 0, 0 ); // put Z going up + pRenderContext->Rotate( -90, 0, 0, 1 ); + + int modelcount = modellist.Count(); + int countover2 = modelcount / 2; + int ydelta = g_pControlPanel->GetModelGap(); + int yoffset = -countover2 * ydelta; + for ( i = 0 ; i < modelcount; i++ ) + { + modellist[ i ]->IncrementFramecounter( ); + + Vector oldtrans = modellist[ i ]->m_origin; + + modellist[ i ]->m_origin[ 1 ] = oldtrans[ 1 ] + yoffset; + yoffset += ydelta; + + modellist[ i ]->GetStudioRender()->BeginFrame(); + modellist[ i ]->DrawModel(); + modellist[ i ]->GetStudioRender()->EndFrame(); + + modellist[ i ]->m_origin = oldtrans; + } + + // + // draw ground + // + if (g_viewerSettings.showGround) + { + drawFloor (); + } + + if (!m_bSuppressSwap) + { + g_pMaterialSystem->SwapBuffers(); + } + + g_pMaterialSystem->EndFrame(); +} + +void MatSysWindow::EnableStickySnapshotMode( ) +{ + m_stickyDepth++; +} + +void MatSysWindow::DisableStickySnapshotMode( ) +{ + if (--m_stickyDepth == 0) + { + if (m_bIsSticky) + { + m_bIsSticky = false; + + HWND wnd = (HWND)getHandle(); + + // Move back to original position + SetWindowPlacement( wnd, &m_wp ); + + SuppressResize( false ); + + SetCursor( m_hPrevCursor ); + } + } +} + + +void MatSysWindow::PushSnapshotMode( int nSnapShotSize ) +{ + if (m_snapshotDepth++ == 0) + { + if (m_stickyDepth) + { + if (m_bIsSticky) + return; + + m_bIsSticky = true; + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_WAIT ) ); + } + + SuppressResize( true ); + + RECT rcClient; + HWND wnd = (HWND)getHandle(); + + GetWindowPlacement( wnd, &m_wp ); + + GetClientRect( wnd, &rcClient ); + + MoveWindow( wnd, 0, 0, nSnapShotSize + 16, nSnapShotSize + 16, TRUE ); + } +} + + +void MatSysWindow::PopSnapshotMode( ) +{ + if (--m_snapshotDepth == 0) + { + if (m_stickyDepth == 0) + { + HWND wnd = (HWND)getHandle(); + + // Move back to original position + SetWindowPlacement( wnd, &m_wp ); + + SuppressResize( false ); + } + } +} + + +void MatSysWindow::TakeSnapshotRect( const char *pFilename, int x, int y, int w, int h ) +{ + int i; + HANDLE hf; + BITMAPFILEHEADER hdr; + BITMAPINFOHEADER bi; + DWORD dwTmp, imageSize; + byte *hp, b, *pBlue, *pRed; + + w = ( w + 3 ) & ~3; + + imageSize = w * h * 3; + // Create the file + hf = CreateFile( pFilename, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); + if( hf == INVALID_HANDLE_VALUE ) + { + return; + } + + // file header + hdr.bfType = 0x4d42; // 'BM' + hdr.bfSize = (DWORD) ( sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + imageSize ); + hdr.bfReserved1 = 0; + hdr.bfReserved2 = 0; + hdr.bfOffBits = (DWORD) ( sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) ); + + if( !WriteFile( hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER), (LPDWORD) &dwTmp, NULL ) ) + Error( "Couldn't write file header to snapshot.\n" ); + + // bitmap header + bi.biSize = sizeof(BITMAPINFOHEADER); + bi.biWidth = w; + bi.biHeight = h; + bi.biPlanes = 1; + bi.biBitCount = 24; + bi.biCompression = BI_RGB; + bi.biSizeImage = 0; //vid.rowbytes * vid.height; + bi.biXPelsPerMeter = 0; + bi.biYPelsPerMeter = 0; + bi.biClrUsed = 0; + bi.biClrImportant = 0; + + if( !WriteFile( hf, (LPVOID) &bi, sizeof(BITMAPINFOHEADER), (LPDWORD) &dwTmp, NULL ) ) + Error( "Couldn't write bitmap header to snapshot.\n" ); + + // bitmap bits + hp = (byte *) malloc(imageSize); + + if (hp == NULL) + Error( "Couldn't allocate bitmap header to snapshot.\n" ); + + // Get Bits from the renderer + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->ReadPixels( x, y, w, h, hp, IMAGE_FORMAT_RGB888 ); + + // Invert vertically for BMP format + for (i = 0; i < h / 2; i++) + { + byte *top = hp + i * w * 3; + byte *bottom = hp + (h - i - 1) * w * 3; + for (int j = 0; j < w * 3; j++) + { + b = *top; + *top = *bottom; + *bottom = b; + top++; + bottom++; + } + } + + // Reverse Red and Blue + pRed = hp; + pBlue = pRed + 2; + for(i = 0; i < w * h;i++) + { + b = *pRed; + *pRed = *pBlue; + *pBlue = b; + pBlue += 3; + pRed += 3; + } + + if( !WriteFile( hf, (LPVOID)hp, imageSize, (LPDWORD) &dwTmp, NULL ) ) + Error( "Couldn't write bitmap data snapshot.\n" ); + + free(hp); + + // clean up + if( !CloseHandle( hf ) ) + Error( "Couldn't close file for snapshot.\n" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool MatSysWindow::IsSuppressingResize( void ) +{ + return m_bSuppressResize; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : suppress - +//----------------------------------------------------------------------------- +void MatSysWindow::SuppressResize( bool suppress ) +{ + m_bSuppressResize = suppress; +} + +void +MatSysWindow::TakeScreenShot (const char *filename) +{ + redraw (); + int w = w2 (); + int h = h2 (); + + mxImage *image = new mxImage (); + if (image->create (w, h, 24)) + { +#if 0 + glReadBuffer (GL_FRONT); + glReadPixels (0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, image->data); +#else + HDC hdc = GetDC ((HWND) getHandle ()); + byte *data = (byte *) image->data; + int i = 0; + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + COLORREF cref = GetPixel (hdc, x, y); + data[i++] = (byte) ((cref >> 0)& 0xff); + data[i++] = (byte) ((cref >> 8) & 0xff); + data[i++] = (byte) ((cref >> 16) & 0xff); + } + } + ReleaseDC ((HWND) getHandle (), hdc); +#endif + if (!mxTgaWrite (filename, image)) + mxMessageBox (this, "Error writing screenshot.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + + delete image; + } +} diff --git a/utils/hlfaceposer/matsyswin.h b/utils/hlfaceposer/matsyswin.h new file mode 100644 index 0000000..3985168 --- /dev/null +++ b/utils/hlfaceposer/matsyswin.h @@ -0,0 +1,210 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef MATSYSWIN_H +#define MATSYSWIN_H +#pragma once + + +#include <mxtk/mxMatSysWindow.h> +#include "materialsystem/imaterialsystem.h" +#include "faceposertoolwindow.h" +#include "interface.h" + + +class MatSysWindow : public mxMatSysWindow, public IFacePoserToolWindow +{ + typedef mxMatSysWindow BaseClass; +public: + + // CREATORS + MatSysWindow( mxWindow *parent, int x, int y, int w, int h, const char *label, int style ); + ~MatSysWindow( ); + + // MANIPULATORS + virtual int handleEvent( mxEvent *event ); + virtual void draw( ); + + virtual void redraw(); + + void EnableStickySnapshotMode( void ); + void DisableStickySnapshotMode( void ); + void PushSnapshotMode( int nSnapShotSize ); + void PopSnapshotMode( void ); + + void TakeSnapshotRect( const char *pFilename, int x, int y, int w, int h ); + bool IsSuppressingResize( void ); + void SuppressResize( bool suppress ); + + void TakeScreenShot(const char *filename); + + void Frame( void ); + void DrawFrame( void ); + + void SuppressBufferSwap( bool bSuppress ); + + void *m_hWnd; + +private: + bool m_bSuppressResize; + bool m_bSuppressSwap; + + // stack and sticky window mode + int m_stickyDepth; + bool m_bIsSticky; + int m_snapshotDepth; + WINDOWPLACEMENT m_wp; + HCURSOR m_hPrevCursor; +}; + +extern MatSysWindow *g_pMatSysWindow; + +extern IMaterialSystem *g_pMaterialSystem; +extern IMaterial *g_materialBackground; +extern IMaterial *g_materialWireframe; +extern IMaterial *g_materialWireframe; +extern IMaterial *g_materialWireframeVertexColor; +extern IMaterial *g_materialWireframeVertexColorNoCull; +extern IMaterial *g_materialDebugCopyBaseTexture; +extern IMaterial *g_materialFlatshaded; +extern IMaterial *g_materialSmoothshaded; +extern IMaterial *g_materialBones; +extern IMaterial *g_materialLines; +extern IMaterial *g_materialFloor; + + +#if 0 + +typedef struct +{ + int width; + int height; + int bpp; + int flags; + int frequency; +} screen_res_t; + + + +typedef struct +{ + int width; + int height; + int bpp; +} devinfo_t; + + + +class MaterialSystemApp +{ +public: + + MaterialSystemApp(); + ~MaterialSystemApp(); + + void Term(); + + // Post a message to shutdown the app. + void AppShutdown(); + + int WinMain(void *hInstance, void *hPrevInstance, char *szCmdLine, int iCmdShow); + long WndProc(void *hwnd, long iMsg, long wParam, long lParam); + + int FindNumParameter(const char *s, int defaultVal=-1); + bool FindParameter(const char *s); + const char* FindParameterArg(const char *s); + + void SetTitleText(PRINTF_FORMAT_STRING const char *fmt, ...); + + +private: + + bool InitMaterialSystem(); + void Clear(); + + bool CreateMainWindow(int width, int height, int bpp, bool fullscreen); + + void RenderScene(); + + void MouseCapture(); + void MouseRelease(); + + void GetParameters(); + + +public: + IMaterialSystem *m_pMaterialSystem; + void *m_hMaterialSystemInst; + + devinfo_t m_DevInfo; + + void *m_hInstance; + int m_iCmdShow; + void *m_hWnd; + void *m_hDC; + bool m_bActive; + bool m_bFullScreen; + int m_width; + int m_height; + int m_centerx; // for mouse offset calculations + int m_centery; + int m_bpp; + BOOL m_bChangeBPP; + BOOL m_bAllowSoft; + BOOL m_bPaused; + int m_glnWidth; + int m_glnHeight; + float m_gldAspect; + float m_NearClip; + float m_FarClip; + float m_fov; + + screen_res_t *m_pResolutions; + int m_iResCount; + + int m_iVidMode; +}; + + +// ---------------------------------------------------------------------------------------- // +// Global functions +// ---------------------------------------------------------------------------------------- // + +// Show an error dialog and quit. +bool Sys_Error(PRINTF_FORMAT_STRING const char *pMsg, ...); + +// Print to the trace window. +void con_Printf(PRINTF_FORMAT_STRING const char *pMsg, ...); + +// Returns true if the key is down. +bool IsKeyDown(char key); + + + +extern MaterialSystemApp g_MaterialSystemApp; + + +#ifdef __cplusplus +extern "C" { +#endif + +extern unsigned int g_Time; + +#ifdef __cplusplus +}; +#endif + + +#endif + +#endif // GLAPP_H diff --git a/utils/hlfaceposer/mdlviewer.cpp b/utils/hlfaceposer/mdlviewer.cpp new file mode 100644 index 0000000..7c4d2ba --- /dev/null +++ b/utils/hlfaceposer/mdlviewer.cpp @@ -0,0 +1,2752 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//===========================================================================// +#include "cbase.h" +#include <direct.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <mxtk/mx.h> +#include <mxtk/mxTga.h> +#include <mxtk/mxEvent.h> +#include "mdlviewer.h" +#include "ViewerSettings.h" +#include "MatSysWin.h" +#include "ControlPanel.h" +#include "FlexPanel.h" +#include "StudioModel.h" +#include "mxExpressionTray.h" +#include "mxStatusWindow.h" +#include "ChoreoView.h" +#include "ifaceposersound.h" +#include "ifaceposerworkspace.h" +#include "expclass.h" +#include "PhonemeEditor.h" +#include "filesystem.h" +#include "ExpressionTool.h" +#include "ControlPanel.h" +#include "choreowidgetdrawhelper.h" +#include "choreoviewcolors.h" +#include "tabwindow.h" +#include "faceposer_models.h" +#include "choiceproperties.h" +#include "choreoscene.h" +#include "choreoactor.h" +#include "tier1/strtools.h" +#include "InputProperties.h" +#include "GestureTool.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "inputsystem/iinputsystem.h" +#include "RampTool.h" +#include "SceneRampTool.h" +#include "tier0/icommandline.h" +#include "phonemeextractor/PhonemeExtractor.h" +#include "animationbrowser.h" +#include "CloseCaptionTool.h" +#include "wavebrowser.h" +#include "vcdbrowser.h" +#include "ifilesystemopendialog.h" +#include <vgui/ILocalize.h> +#include <vgui/IVGui.h> +#include "appframework/appframework.h" +#include "icvar.h" +#include "vstdlib/cvar.h" +#include "istudiorender.h" +#include "materialsystem/imaterialsystem.h" +#include "vphysics_interface.h" +#include "Datacache/imdlcache.h" +#include "datacache/idatacache.h" +#include "filesystem_init.h" +#include "materialsystem/imaterialsystemhardwareconfig.h" +#include "tier1/strtools.h" +#include "appframework/tier3app.h" +#include "faceposer_vgui.h" +#include "vguiwnd.h" +#include "vgui_controls/Frame.h" +#include "vgui/ISurface.h" +#include "p4lib/ip4.h" +#include "tier2/p4helpers.h" +#include "ProgressDialog.h" +#include "scriplib.h" + +#define WINDOW_TAB_OFFSET 24 + +MDLViewer *g_MDLViewer = 0; +char g_appTitle[] = "Half-Life Face Poser"; +static char recentFiles[8][256] = { "", "", "", "", "", "", "", "" }; + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Singleton interfaces +//----------------------------------------------------------------------------- +IPhysicsSurfaceProps *physprop; +IPhysicsCollision *physcollision; +IStudioDataCache *g_pStudioDataCache; +vgui::ILocalize *g_pLocalize = NULL; +ISoundEmitterSystemBase *soundemitter = NULL; +CreateInterfaceFn g_Factory; +IFileSystem *g_pFileSystem = NULL; + +bool g_bInError = false; + +static char gamedir[MAX_PATH]; // full path to gamedir U:\main\game\ep2 +static char gamedirsimple[MAX_PATH]; // just short name: ep2 + +// Filesystem dialog module wrappers. +CSysModule *g_pFSDialogModule = 0; +CreateInterfaceFn g_FSDialogFactory = 0; + +#include "vgui_controls/TextEntry.h" +#include "vgui_controls/Button.h" +#include "vgui_controls/Label.h" +#include "vgui_controls/ComboBox.h" +#include "tier1/fmtstr.h" + +class CFacePoserVguiFrame : public Frame +{ + DECLARE_CLASS_SIMPLE( CFacePoserVguiFrame, Frame ); + +public: + CFacePoserVguiFrame( Panel *parent, const char *panelName ) : + BaseClass( parent, panelName ) + { + SetTitle( panelName, true ); + + SetTitleBarVisible( false ); + + SetSizeable( false ); + SetMoveable( false ); + + SetPaintBackgroundEnabled( true ); + SetCloseButtonVisible( false ); + m_pEntry = new TextEntry( this, "textentry" ); + m_pEntry->AddActionSignalTarget( this ); + m_pButton = new Button( this, "button", "Button1", this ); + m_pButton->SetCommand( new KeyValues( "OnButtonPressed" ) ); + m_pLabel = new Label( this, "label", "..." ); + + m_pCombo = new ComboBox( this, "combo", 5, true ); + for ( int i = 0; i < 10; ++i ) + { + m_pCombo->AddItem( CFmtStr( "item%02d", i + 1 ), NULL ); + } + } + + MESSAGE_FUNC( OnButtonPressed, "OnButtonPressed" ) + { + Msg( "OnButtonPressed\n" ); + } + + MESSAGE_FUNC_PARAMS( OnTextChanged, "TextChanged", str ) + { + char sz[ 256 ]; + m_pEntry->GetText( sz, sizeof( sz ) ); + m_pLabel->SetText( sz ); + + m_pCombo->GetText( sz, sizeof( sz ) ); + Msg( "Combo %s\n", sz ); + } + + virtual void PerformLayout() + { + BaseClass::PerformLayout(); + + int w, h; + GetSize( w, h ); + + int y = 30; + int skip = 20; + m_pEntry->SetBounds( 5, y, w, skip - 2 ); + y += skip; + m_pButton->SetBounds( 5, y, w, skip - 2 ); + y += skip; + m_pCombo->SetBounds( 5, y, w, skip - 2 ); + y += skip; + m_pLabel->SetBounds( 5, y, w, skip - 2 ); + y += skip; + + } + +private: + + TextEntry *m_pEntry; + Button *m_pButton; + Label *m_pLabel; + ComboBox *m_pCombo; +}; + +class TestWindow : public CVGuiPanelWnd, public IFacePoserToolWindow +{ + typedef CVGuiPanelWnd BaseClass; + +public: + + TestWindow( mxWindow *parent, int x, int y, int w, int h) : + BaseClass(parent, x, y, w, h ), + IFacePoserToolWindow( "FacePoser Frame", "FacePoser Frame" ) + { + CFacePoserVguiFrame *f = new CFacePoserVguiFrame( NULL, "FacePoser Frame" ); + + SetParentWindow( this ); + SetMainPanel( f ); + f->SetVisible( true ); + f->SetPaintBackgroundEnabled( true ); + + FacePoser_MakeToolWindow( this, true ); + } + + virtual int handleEvent( mxEvent *event ) + { + if ( HandleToolEvent( event ) ) + return 1; + return BaseClass::handleEvent( event ); + } +}; + + +//----------------------------------------------------------------------------- +// FIXME: Remove this crap (from cmdlib.cpp) +// We can't include cmdlib owing to appframework incompatibilities +//----------------------------------------------------------------------------- +void Q_mkdir( const char *path ) +{ +#if defined( _WIN32 ) || defined( WIN32 ) + if (_mkdir (path) != -1) + return; +#else + if (mkdir (path, 0777) != -1) + return; +#endif + if (errno != EEXIST) + { + Error ("mkdir %s: %s",path, strerror(errno)); + } +} + +void CreatePath( const char *relative ) +{ + char fullpath[ 512 ]; + Q_snprintf( fullpath, sizeof( fullpath ), "%s%s", GetGameDirectory(), relative ); + + char *path = fullpath; + + char *ofs, c; + + if (path[1] == ':') + { + path += 2; + } + + for (ofs = const_cast<char*>(path+1); *ofs ; ofs++) + { + c = *ofs; + if (c == '/' || c == '\\') + { + // create the directory, but not if it's actually a filename with a dot in it!!! + *ofs = 0; + if ( !Q_stristr( path, "." ) ) + { + Q_mkdir (path); + } + *ofs = c; + } + } +} + +//----------------------------------------------------------------------------- +// LoadFile +//----------------------------------------------------------------------------- +int LoadFile (const char *filename, void **bufferptr) +{ + FileHandle_t f = filesystem->Open( filename, "rb" ); + int length = filesystem->Size( f ); + void *buffer = malloc (length+1); + ((char *)buffer)[length] = 0; + if ( filesystem->Read (buffer, length, f) != (int)length ) + { + Error ("File read failure"); + } + filesystem->Close (f); + + *bufferptr = buffer; + return length; +} + +char *ExpandPath(char *path) +{ + static char full[1024]; + if (path[0] == '/' || path[0] == '\\' || path[1] == ':') + return path; + + V_sprintf_safe( full, "%s%s", gamedir, path ); + return full; +} + + +//----------------------------------------------------------------------------- +// This is here because scriplib.cpp is included in this project but cmdlib.cpp +// is not, but scriplib.cpp uses some stuff from cmdlib.cpp, same with +// LoadFile and ExpandPath above. The only thing that currently uses this +// is $include in scriptlib, if this function returns 0, $include will +// behave the way it did before this change +//----------------------------------------------------------------------------- +int CmdLib_ExpandWithBasePaths( CUtlVector< CUtlString > &expandedPathList, const char *pszPath ) +{ + return 0; +} + + +//----------------------------------------------------------------------------- +// FIXME: Move into appsystem framework +//----------------------------------------------------------------------------- +void LoadFileSystemDialogModule() +{ + Assert( !g_pFSDialogModule ); + + // Load the module with the file system open dialog. + const char *pDLLName = "FileSystemOpenDialog.dll"; + g_pFSDialogModule = Sys_LoadModule( pDLLName ); + if ( g_pFSDialogModule ) + { + g_FSDialogFactory = Sys_GetFactory( g_pFSDialogModule ); + } + + if ( !g_pFSDialogModule || !g_FSDialogFactory ) + { + if ( g_pFSDialogModule ) + { + Sys_UnloadModule( g_pFSDialogModule ); + g_pFSDialogModule = NULL; + } + } +} + +void UnloadFileSystemDialogModule() +{ + if ( g_pFSDialogModule ) + { + Sys_UnloadModule( g_pFSDialogModule ); + g_pFSDialogModule = 0; + } +} + + + +void +MDLViewer::initRecentFiles () +{ + for (int i = 0; i < 8; i++) + { + if (strlen (recentFiles[i])) + { + mb->modify (IDC_FILE_RECENTFILES1 + i, IDC_FILE_RECENTFILES1 + i, recentFiles[i]); + } + else + { + mb->modify (IDC_FILE_RECENTFILES1 + i, IDC_FILE_RECENTFILES1 + i, "(empty)"); + mb->setEnabled (IDC_FILE_RECENTFILES1 + i, false); + } + } +} + + +#define RECENTFILESPATH "/hlfaceposer.rf" +void +MDLViewer::loadRecentFiles () +{ + char path[256]; + strcpy (path, mx::getApplicationPath ()); + strcat (path, RECENTFILESPATH); + FILE *file = fopen (path, "rb"); + if (file) + { + fread (recentFiles, sizeof recentFiles, 1, file); + fclose (file); + } +} + + + +void +MDLViewer::saveRecentFiles () +{ + char path[256]; + + strcpy (path, mx::getApplicationPath ()); + strcat (path, RECENTFILESPATH); + + FILE *file = fopen (path, "wb"); + if (file) + { + fwrite (recentFiles, sizeof recentFiles, 1, file); + fclose (file); + } +} + +bool MDLViewer::AreSoundScriptsDirty() +{ + // Save any changed sound script files + int c = soundemitter->GetNumSoundScripts(); + for ( int i = 0; i < c; i++ ) + { + if ( soundemitter->IsSoundScriptDirty( i ) ) + { + return true; + } + } + return false; +} + +bool MDLViewer::CanClose() +{ + Con_Printf( "Checking for vcd changes...\n" ); + + if ( m_bVCDSaved ) + { + int retval = mxMessageBox( NULL, "Rebuild scenes.image?", g_appTitle, MX_MB_YESNOCANCEL ); + if ( retval == 2 ) + { + return false; + } + + m_bVCDSaved = false; + if ( retval == 0 ) // YES + { + OnRebuildScenesImage(); + } + } + + Con_Printf( "Checking for sound script changes...\n" ); + + // Save any changed sound script files + int c = soundemitter->GetNumSoundScripts(); + for ( int i = 0; i < c; i++ ) + { + if ( !soundemitter->IsSoundScriptDirty( i ) ) + continue; + + char const *scriptname = soundemitter->GetSoundScriptName( i ); + if ( !scriptname ) + continue; + + if ( !filesystem->FileExists( scriptname ) || + !filesystem->IsFileWritable( scriptname ) ) + { + continue; + } + + int retval = mxMessageBox( NULL, va( "Save changes to sound script '%s'?", scriptname ), g_appTitle, MX_MB_YESNOCANCEL ); + if ( retval == 2 ) + { + return false; + } + + if ( retval == 0 ) + { + soundemitter->SaveChangesToSoundScript( i ); + } + } + + SaveWindowPositions(); + + models->SaveModelList(); + models->CloseAllModels(); + + return true; +} + +bool MDLViewer::Closing( void ) +{ + return true; +} + +#define IDC_GRIDSETTINGS_FPS 1001 +#define IDC_GRIDSETTINGS_SNAP 1002 + +class CFlatButton : public mxButton +{ +public: + CFlatButton( mxWindow *parent, int id ) + : mxButton( parent, 0, 0, 0, 0, "", id ) + { + HWND wnd = (HWND)getHandle(); + DWORD exstyle = GetWindowLong( wnd, GWL_EXSTYLE ); + exstyle |= WS_EX_CLIENTEDGE; + SetWindowLong( wnd, GWL_EXSTYLE, exstyle ); + + DWORD style = GetWindowLong( wnd, GWL_STYLE ); + style &= ~WS_BORDER; + SetWindowLong( wnd, GWL_STYLE, style ); + + } +}; + +class CMDLViewerGridSettings : public mxWindow +{ +public: + typedef mxWindow BaseClass; + + CMDLViewerGridSettings( mxWindow *parent, int x, int y, int w, int h ) : + mxWindow( parent, x, y, w, h ) + { + FacePoser_AddWindowStyle( this, WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS ); + m_btnFPS = new CFlatButton( this, IDC_GRIDSETTINGS_FPS ); + m_btnGridSnap = new CFlatButton( this, IDC_GRIDSETTINGS_SNAP ); + + } + + void Init( void ) + { + if ( g_pChoreoView ) + { + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( scene ) + { + char sz[ 256 ]; + Q_snprintf( sz, sizeof( sz ), "%i fps", scene->GetSceneFPS() ); + m_btnFPS->setLabel( sz ); + + Q_snprintf( sz, sizeof( sz ), "snap: %s", scene->IsUsingFrameSnap() ? "on" : "off" ); + m_btnGridSnap->setLabel( sz ); + + m_btnFPS->setVisible( true ); + m_btnGridSnap->setVisible( true ); + return; + } + } + + m_btnFPS->setVisible( false ); + m_btnGridSnap->setVisible( false ); + } + + virtual int handleEvent( mxEvent *event ) + { + int iret = 0; + switch ( event->event ) + { + default: + break; + case mxEvent::Size: + { + int leftedge = w2() * 0.45f; + m_btnFPS->setBounds( 0, 0, leftedge, h2() ); + m_btnGridSnap->setBounds( leftedge, 0, w2() - leftedge, h2() ); + iret = 1; + } + break; + case mxEvent::Action: + { + iret = 1; + switch ( event->action ) + { + default: + iret = 0; + break; + case IDC_GRIDSETTINGS_FPS: + { + if ( g_pChoreoView ) + { + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( scene ) + { + int currentFPS = scene->GetSceneFPS(); + + CInputParams params; + memset( ¶ms, 0, sizeof( params ) ); + + strcpy( params.m_szDialogTitle, "Change FPS" ); + + Q_snprintf( params.m_szInputText, sizeof( params.m_szInputText ), + "%i", currentFPS ); + + strcpy( params.m_szPrompt, "Current FPS:" ); + + if ( InputProperties( ¶ms ) ) + { + int newFPS = atoi( params.m_szInputText ); + + if ( ( newFPS > 0 ) && ( newFPS != currentFPS ) ) + { + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Change Scene FPS" ); + scene->SetSceneFPS( newFPS ); + g_pChoreoView->PushRedo( "Change Scene FPS" ); + Init(); + + Con_Printf( "FPS changed to %i\n", newFPS ); + } + } + + } + } + } + break; + case IDC_GRIDSETTINGS_SNAP: + { + if ( g_pChoreoView ) + { + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( scene ) + { + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( "Change Snap Frame" ); + + scene->SetUsingFrameSnap( !scene->IsUsingFrameSnap() ); + + g_pChoreoView->PushRedo( "Change Snap Frame" ); + + Init(); + + Con_Printf( "Time frame snapping: %s\n", + scene->IsUsingFrameSnap() ? "on" : "off" ); + } + } + + + } + break; + } + } + } + return iret; + } + + bool PaintBackground( void ) + { + CChoreoWidgetDrawHelper drawHelper( this ); + RECT rc; + drawHelper.GetClientRect( rc ); + drawHelper.DrawFilledRect( GetSysColor( COLOR_BTNFACE ), rc ); + return false; + } + +private: + + CFlatButton *m_btnFPS; + CFlatButton *m_btnGridSnap; +}; + + +#define IDC_MODELTAB_LOAD 1000 +#define IDC_MODELTAB_CLOSE 1001 +#define IDC_MODELTAB_CLOSEALL 1002 +#define IDC_MODELTAB_CENTERONFACE 1003 +#define IDC_MODELTAB_ASSOCIATEACTOR 1004 +#define IDC_MODELTAB_TOGGLE3DVIEW 1005 +#define IDC_MODELTAB_SHOWALL 1006 +#define IDC_MODELTAB_HIDEALL 1007 +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CMDLViewerModelTab : public CTabWindow +{ +public: + typedef CTabWindow BaseClass; + + CMDLViewerModelTab( mxWindow *parent, int x, int y, int w, int h, int id = 0, int style = 0 ) : + CTabWindow( parent, x, y, w, h, id, style ) + { + SetInverted( true ); + } + + virtual void ShowRightClickMenu( int mx, int my ) + { + mxPopupMenu *pop = new mxPopupMenu(); + Assert( pop ); + + char const *current = ""; + char const *filename = ""; + int idx = getSelectedIndex(); + if ( idx >= 0 ) + { + current = models->GetModelName( idx ); + filename = models->GetModelFileName( idx ); + } + + if ( models->Count() < MAX_FP_MODELS ) + { + pop->add( "Load Model...", IDC_MODELTAB_LOAD ); + } + if ( idx >= 0 ) + { + pop->add( va( "Close '%s'", current ), IDC_MODELTAB_CLOSE ); + } + if ( models->Count() > 0 ) + { + pop->add( "Close All", IDC_MODELTAB_CLOSEALL ); + } + if ( idx >= 0 ) + { + pop->addSeparator(); + pop->add( va( "Center %s's face", current ), IDC_MODELTAB_CENTERONFACE ); + + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( scene ) + { + // See if there is already an actor with this model associated + int c = scene->GetNumActors(); + bool hasassoc = false; + for ( int i = 0; i < c; i++ ) + { + CChoreoActor *a = scene->GetActor( i ); + Assert( a ); + + if ( stricmp( a->GetFacePoserModelName(), filename ) ) + continue; + hasassoc = true; + break; + } + + if ( hasassoc ) + { + pop->add( va( "Change associated actor for %s", current ), IDC_MODELTAB_ASSOCIATEACTOR ); + } + else + { + pop->add( va( "Associate actor to %s", current ), IDC_MODELTAB_ASSOCIATEACTOR ); + } + } + + pop->addSeparator(); + + bool visible = models->IsModelShownIn3DView( idx ); + if ( visible ) + { + pop->add( va( "Remove %s from 3D View", current ), IDC_MODELTAB_TOGGLE3DVIEW ); + } + else + { + pop->add( va( "Show %s in 3D View", current ), IDC_MODELTAB_TOGGLE3DVIEW ); + } + } + if ( models->Count() > 0 ) + { + pop->addSeparator(); + pop->add( "Show All", IDC_MODELTAB_SHOWALL ); + pop->add( "Hide All", IDC_MODELTAB_HIDEALL ); + } + + // Convert click position + POINT pt; + pt.x = mx; + pt.y = my; + + // Convert coordinate space + pop->popup( this, pt.x, pt.y ); + } + + virtual int handleEvent( mxEvent *event ) + { + int iret = 0; + switch ( event->event ) + { + default: + break; + case mxEvent::Action: + { + iret = 1; + switch ( event->action ) + { + default: + iret = 0; + break; + case IDC_MODELTAB_SHOWALL: + case IDC_MODELTAB_HIDEALL: + { + bool show = ( event->action == IDC_MODELTAB_SHOWALL ) ? true : false; + int c = models->Count(); + for ( int i = 0; i < c ; i++ ) + { + models->ShowModelIn3DView( i, show ); + } + } + break; + case IDC_MODELTAB_LOAD: + { + if ( ! CommandLine()->FindParm( "-NoSteamDialog" ) ) + { + g_MDLViewer->LoadModel_Steam(); + } + else + { + char modelfile[ 512 ]; + if ( FacePoser_ShowOpenFileNameDialog( modelfile, sizeof( modelfile ), "models", "*.mdl" ) ) + { + g_MDLViewer->LoadModelFile( modelfile ); + } + } + } + break; + case IDC_MODELTAB_CLOSE: + { + int idx = getSelectedIndex(); + if ( idx >= 0 ) + { + models->FreeModel( idx ); + } + } + break; + case IDC_MODELTAB_CLOSEALL: + { + models->CloseAllModels(); + } + break; + case IDC_MODELTAB_CENTERONFACE: + { + g_pControlPanel->CenterOnFace(); + } + break; + case IDC_MODELTAB_TOGGLE3DVIEW: + { + int idx = getSelectedIndex(); + if ( idx >= 0 ) + { + bool visible = models->IsModelShownIn3DView( idx ); + models->ShowModelIn3DView( idx, !visible ); + } + } + break; + case IDC_MODELTAB_ASSOCIATEACTOR: + { + int idx = getSelectedIndex(); + if ( idx >= 0 ) + { + char const *modelname = models->GetModelFileName( idx ); + + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( scene ) + { + CChoiceParams params; + strcpy( params.m_szDialogTitle, "Associate Actor" ); + + params.m_bPositionDialog = false; + params.m_nLeft = 0; + params.m_nTop = 0; + strcpy( params.m_szPrompt, "Choose actor:" ); + + params.m_Choices.RemoveAll(); + + params.m_nSelected = -1; + int oldsel = -1; + + int c = scene->GetNumActors(); + ChoiceText text; + for ( int i = 0; i < c; i++ ) + { + CChoreoActor *a = scene->GetActor( i ); + Assert( a ); + + + strcpy( text.choice, a->GetName() ); + + if ( !stricmp( a->GetFacePoserModelName(), modelname ) ) + { + params.m_nSelected = i; + oldsel = -1; + } + + params.m_Choices.AddToTail( text ); + } + + if ( ChoiceProperties( ¶ms ) && + params.m_nSelected != oldsel ) + { + + // Chose something new... + CChoreoActor *a = scene->GetActor( params.m_nSelected ); + + g_pChoreoView->AssociateModelToActor( a, idx ); + } + } + } + + } + } + } + break; + } + if ( iret ) + return iret; + return BaseClass::handleEvent( event ); + } + + + void HandleModelSelect( void ) + { + int idx = getSelectedIndex(); + if ( idx < 0 ) + return; + + // FIXME: Do any necessary window resetting here!!! + g_pControlPanel->ChangeModel( models->GetModelFileName( idx ) ); + } + + void Init( void ) + { + removeAll(); + + int c = models->Count(); + int i; + for ( i = 0; i < c ; i++ ) + { + char const *name = models->GetModelName( i ); + + // Strip it down to the base name + char cleanname[ 256 ]; + Q_FileBase( name, cleanname, sizeof( cleanname ) ); + + add( cleanname ); + } + } +}; + +#define IDC_TOOL_TOGGLEVISIBILITY 1000 +#define IDC_TOOL_TOGGLELOCK 1001 +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CMDLViewerWindowTab : public CTabWindow +{ +public: + typedef CTabWindow BaseClass; + + CMDLViewerWindowTab( mxWindow *parent, int x, int y, int w, int h, int id = 0, int style = 0 ) : + CTabWindow( parent, x, y, w, h, id, style ) + { + SetInverted( true ); + + m_nLastSelected = -1; + m_flLastSelectedTime = -1; + } + + virtual void ShowRightClickMenu( int mx, int my ) + { + IFacePoserToolWindow *tool = GetSelectedTool(); + if ( !tool ) + return; + + mxWindow *toolw = tool->GetMxWindow(); + if ( !toolw ) + return; + + mxPopupMenu *pop = new mxPopupMenu(); + Assert( pop ); + + bool isVisible = toolw->isVisible(); + bool isLocked = tool->IsLocked(); + + pop->add( isVisible ? "Hide" : "Show", IDC_TOOL_TOGGLEVISIBILITY ); + pop->add( isLocked ? "Unlock" : "Lock", IDC_TOOL_TOGGLELOCK ); + + // Convert click position + POINT pt; + pt.x = mx; + pt.y = my; + + /* + ClientToScreen( (HWND)getHandle(), &pt ); + ScreenToClient( (HWND)g_MDLViewer->getHandle(), &pt ); + */ + + // Convert coordinate space + pop->popup( this, pt.x, pt.y ); + } + + virtual int handleEvent( mxEvent *event ) + { + int iret = 0; + switch ( event->event ) + { + case mxEvent::Action: + { + iret = 1; + switch ( event->action ) + { + default: + iret = 0; + break; + case IDC_TOOL_TOGGLEVISIBILITY: + { + IFacePoserToolWindow *tool = GetSelectedTool(); + if ( tool ) + { + mxWindow *toolw = tool->GetMxWindow(); + if ( toolw ) + { + toolw->setVisible( !toolw->isVisible() ); + g_MDLViewer->UpdateWindowMenu(); + } + } + } + break; + case IDC_TOOL_TOGGLELOCK: + { + IFacePoserToolWindow *tool = GetSelectedTool(); + if ( tool ) + { + tool->ToggleLockedState(); + } + } + break; + } + } + break; + default: + break; + } + if ( iret ) + return iret; + return BaseClass::handleEvent( event ); + } + + void Init( void ) + { + int c = IFacePoserToolWindow::GetToolCount(); + int i; + for ( i = 0; i < c ; i++ ) + { + IFacePoserToolWindow *tool = IFacePoserToolWindow::GetTool( i ); + add( tool->GetDisplayNameRoot() ); + } + } + +#define WINDOW_DOUBLECLICK_TIME 0.4 + + void HandleWindowSelect( void ) + { + extern double realtime; + IFacePoserToolWindow *tool = GetSelectedTool(); + if ( !tool ) + return; + + bool doubleclicked = false; + + double curtime = realtime; + int clickedItem = getSelectedIndex(); + + if ( clickedItem == m_nLastSelected ) + { + if ( curtime < m_flLastSelectedTime + WINDOW_DOUBLECLICK_TIME ) + { + doubleclicked = true; + } + } + + m_flLastSelectedTime = curtime; + m_nLastSelected = clickedItem; + + mxWindow *toolw = tool->GetMxWindow(); + if ( !toolw ) + return; + + if ( doubleclicked ) + { + toolw->setVisible( !toolw->isVisible() ); + m_flLastSelectedTime = -1; + } + + if ( !toolw->isVisible() ) + { + return; + } + + // Move window to front + HWND wnd = (HWND)tool->GetMxWindow()->getHandle(); + SetFocus( wnd ); + SetWindowPos( wnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + } + +private: + + IFacePoserToolWindow *GetSelectedTool() + { + int idx = getSelectedIndex(); + int c = IFacePoserToolWindow::GetToolCount(); + + if ( idx < 0 || idx >= c ) + return NULL; + + IFacePoserToolWindow *tool = IFacePoserToolWindow::GetTool( idx ); + return tool; + } + + // HACKY double click handler + int m_nLastSelected; + double m_flLastSelectedTime; +}; + +//----------------------------------------------------------------------------- +// Purpose: The workspace is the parent of all of the tool windows +//----------------------------------------------------------------------------- +class CMDLViewerWorkspace : public mxWindow +{ +public: + CMDLViewerWorkspace( mxWindow *parent, int x, int y, int w, int h, const char *label = 0, int style = 0) + : mxWindow( parent, x, y, w, h, label, style ) + { + FacePoser_AddWindowStyle( this, WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS ); + } + + //----------------------------------------------------------------------------- + // Purpose: + // Output : Returns true on success, false on failure. + //----------------------------------------------------------------------------- + bool PaintBackground( void ) + { + CChoreoWidgetDrawHelper drawHelper( this ); + RECT rc; + drawHelper.GetClientRect( rc ); + drawHelper.DrawFilledRect( GetSysColor( COLOR_APPWORKSPACE ), rc ); + return false; + } +}; + +void MDLViewer::LoadPosition( void ) +{ + bool visible; + bool locked; + bool zoomed; + int x, y, w, h; + + FacePoser_LoadWindowPositions( "MDLViewer", visible, x, y, w, h, locked, zoomed ); + + if ( w == 0 || h == 0 ) + { + zoomed = true; + visible = true; + } + + setBounds( x, y, w, h ); + if ( zoomed ) + { + ShowWindow( (HWND)getHandle(), SW_SHOWMAXIMIZED ); + } + else + { + setVisible( visible ); + } +} + +void MDLViewer::SavePosition( void ) +{ + bool visible; + int xpos, ypos, width, height; + + visible = isVisible(); + xpos = x(); + ypos = y(); + width = w(); + height = h(); + + // xpos and ypos are screen space + POINT pt; + pt.x = xpos; + pt.y = ypos; + + // Convert from screen space to relative to client area of parent window so + // the setBounds == MoveWindow call will offset to the same location + if ( getParent() ) + { + ScreenToClient( (HWND)getParent()->getHandle(), &pt ); + xpos = (short)pt.x; + ypos = (short)pt.y; + } + + bool zoomed = IsZoomed( (HWND)getHandle() ) ? true : false; + + bool iconic = IsIconic( (HWND)getHandle() ) ? true : false; + + // Don't reset values if it's minimized during shutdown + if ( iconic ) + return; + + FacePoser_SaveWindowPositions( "MDLViewer", visible, xpos, ypos, width, height, false, zoomed ); +} + +MDLViewer::MDLViewer () : + mxWindow (0, 0, 0, 0, 0, g_appTitle, mxWindow::Normal), + menuCloseCaptionLanguages(0), + m_bOldSoundScriptsDirty( -1 ), + m_bVCDSaved( false ) +{ + int i; + + g_MDLViewer = this; + + FacePoser_MakeToolWindow( this, false ); + + workspace = new CMDLViewerWorkspace( this, 0, 0, 500, 500, "" ); + windowtab = new CMDLViewerWindowTab( this, 0, 500, 500, 20, IDC_WINDOW_TAB ); + modeltab = new CMDLViewerModelTab( this, 500, 500, 200, 20, IDC_MODEL_TAB ); + gridsettings = new CMDLViewerGridSettings( this, 0, 500, 500, 20 ); + modeltab->SetRightJustify( true ); + + g_pStatusWindow = new mxStatusWindow( workspace, 0, 0, 1024, 150, "" ); + g_pStatusWindow->setVisible( true ); + + InitViewerSettings( "faceposer" ); + g_viewerSettings.speechapiindex = SPEECH_API_LIPSINC; + g_viewerSettings.m_iEditAttachment = -1; + + LoadViewerRootSettings( ); + + LoadPosition(); + // ShowWindow( (HWND)getHandle(), SW_SHOWMAXIMIZED ); + + g_pStatusWindow->setBounds( 0, h2() - 150, w2(), 150 ); + + Con_Printf( "MDLViewer started\n" ); + + Con_Printf( "Creating menu bar\n" ); + + // create menu stuff + mb = new mxMenuBar (this); + menuFile = new mxMenu (); + menuOptions = new mxMenu (); + menuWindow = new mxMenu (); + menuHelp = new mxMenu (); + menuEdit = new mxMenu (); + menuExpressions = new mxMenu(); + menuChoreography = new mxMenu(); + + mb->addMenu ("File", menuFile); + //mb->addMenu( "Edit", menuEdit ); + mb->addMenu ("Options", menuOptions); + mb->addMenu ( "Expression", menuExpressions ); + mb->addMenu ( "Choreography", menuChoreography ); + mb->addMenu ("Window", menuWindow); + mb->addMenu ("Help", menuHelp); + + mxMenu *menuRecentFiles = new mxMenu (); + menuRecentFiles->add ("(empty)", IDC_FILE_RECENTFILES1); + menuRecentFiles->add ("(empty)", IDC_FILE_RECENTFILES2); + menuRecentFiles->add ("(empty)", IDC_FILE_RECENTFILES3); + menuRecentFiles->add ("(empty)", IDC_FILE_RECENTFILES4); + + menuFile->add ("Load Model...", IDC_FILE_LOADMODEL); + menuFile->add( "Refresh\tF5", IDC_FILE_REFRESH ); + + menuFile->addSeparator(); + menuFile->add ("Save Sound Changes...", IDC_FILE_SAVESOUNDSCRIPTCHANGES ); + menuFile->add( "Rebuild scenes.image...", IDC_FILE_REBUILDSCENESIMAGE ); + + menuFile->addSeparator(); + + menuFile->add ("Load Background Texture...", IDC_FILE_LOADBACKGROUNDTEX); + menuFile->add ("Load Ground Texture...", IDC_FILE_LOADGROUNDTEX); + menuFile->addSeparator (); + menuFile->add ("Unload Ground Texture", IDC_FILE_UNLOADGROUNDTEX); + menuFile->addSeparator (); + menuFile->addMenu ("Recent Files", menuRecentFiles); + menuFile->addSeparator (); + menuFile->add ("Exit", IDC_FILE_EXIT); + + menuFile->setEnabled(IDC_FILE_LOADBACKGROUNDTEX, false); + menuFile->setEnabled(IDC_FILE_LOADGROUNDTEX, false); + menuFile->setEnabled(IDC_FILE_UNLOADGROUNDTEX, false); + menuFile->setEnabled(IDC_FILE_SAVESOUNDSCRIPTCHANGES, false); + + menuOptions->add ("Background Color...", IDC_OPTIONS_COLORBACKGROUND); + menuOptions->add ("Ground Color...", IDC_OPTIONS_COLORGROUND); + menuOptions->add ("Light Color...", IDC_OPTIONS_COLORLIGHT); + + { + menuCloseCaptionLanguages = new mxMenu(); + + for ( int i = 0; i < CC_NUM_LANGUAGES; i++ ) + { + int id = IDC_OPTIONS_LANGUAGESTART + i; + menuCloseCaptionLanguages->add( CSentence::NameForLanguage( i ), id ); + } + + menuOptions->addSeparator(); + menuOptions->addMenu( "CC Language", menuCloseCaptionLanguages ); + } + + menuOptions->addSeparator (); + menuOptions->add ("Center View", IDC_OPTIONS_CENTERVIEW); + menuOptions->add ("Center on Face", IDC_OPTIONS_CENTERONFACE ); +#ifdef WIN32 + menuOptions->addSeparator (); + menuOptions->add ("Make Screenshot...", IDC_OPTIONS_MAKESCREENSHOT); + //menuOptions->add ("Dump Model Info", IDC_OPTIONS_DUMP); + menuOptions->addSeparator (); + menuOptions->add ("Clear model sounds.", IDC_OPTIONS_CLEARMODELSOUNDS ); + +#endif + + menuExpressions->add( "New...", IDC_EXPRESSIONS_NEW ); + menuExpressions->addSeparator (); + menuExpressions->add( "Load...", IDC_EXPRESSIONS_LOAD ); + menuExpressions->add( "Save", IDC_EXPRESSIONS_SAVE ); + menuExpressions->addSeparator (); + menuExpressions->add( "Export to VFE", IDC_EXPRESSIONS_EXPORT ); + menuExpressions->addSeparator (); + menuExpressions->add( "Close class", IDC_EXPRESSIONS_CLOSE ); + menuExpressions->add( "Close all classes", IDC_EXPRESSIONS_CLOSEALL ); + menuExpressions->addSeparator(); + menuExpressions->add( "Recreate all bitmaps", IDC_EXPRESSIONS_REDOBITMAPS ); + + menuChoreography->add( "New...", IDC_CHOREOSCENE_NEW ); + menuChoreography->addSeparator(); + menuChoreography->add( "Load...", IDC_CHOREOSCENE_LOAD ); + menuChoreography->add( "Save", IDC_CHOREOSCENE_SAVE ); + menuChoreography->add( "Save As...", IDC_CHOREOSCENE_SAVEAS ); + menuChoreography->addSeparator(); + menuChoreography->add( "Close", IDC_CHOREOSCENE_CLOSE ); + menuChoreography->addSeparator(); + menuChoreography->add( "Add Actor...", IDC_CHOREOSCENE_ADDACTOR ); + menuChoreography->addSeparator(); + menuChoreography->add( "Load Next", IDC_CHOREOSCENE_LOADNEXT ); + +#ifdef WIN32 + menuHelp->add ("Goto Homepage...", IDC_HELP_GOTOHOMEPAGE); + menuHelp->addSeparator (); +#endif + menuHelp->add ("About...", IDC_HELP_ABOUT); + + // create the Material System window + Con_Printf( "Creating 3D View\n" ); + g_pMatSysWindow = new MatSysWindow (workspace, 0, 0, 100, 100, "", mxWindow::Normal); + + Con_Printf( "Creating Close Caption tool" ); + g_pCloseCaptionTool = new CloseCaptionTool( workspace ); + + Con_Printf( "Creating control panel\n" ); + g_pControlPanel = new ControlPanel (workspace); + + Con_Printf( "Creating phoneme editor\n" ); + g_pPhonemeEditor = new PhonemeEditor( workspace ); + + Con_Printf( "Creating expression tool\n" ); + g_pExpressionTool = new ExpressionTool( workspace ); + + Con_Printf( "Creating gesture tool\n" ); + g_pGestureTool = new GestureTool( workspace ); + + Con_Printf( "Creating ramp tool\n" ); + g_pRampTool = new RampTool( workspace ); + + Con_Printf( "Creating scene ramp tool\n" ); + g_pSceneRampTool = new SceneRampTool( workspace ); + + Con_Printf( "Creating expression tray\n" ); + g_pExpressionTrayTool = new mxExpressionTray( workspace, IDC_EXPRESSIONTRAY ); + + Con_Printf( "Creating animation browser\n" ); + g_pAnimationBrowserTool = new AnimationBrowser( workspace, IDC_ANIMATIONBROWSER ); + + Con_Printf( "Creating flex slider window\n" ); + g_pFlexPanel = new FlexPanel( workspace ); + + Con_Printf( "Creating wave browser\n" ); + g_pWaveBrowser = new CWaveBrowser( workspace ); + + Con_Printf( "Creating VCD browser\n" ); + g_pVCDBrowser = new CVCDBrowser( workspace ); + + Con_Printf( "Creating choreography view\n" ); + g_pChoreoView = new CChoreoView( workspace, 200, 200, 400, 300, 0 ); + // Choreo scene file drives main window title name + g_pChoreoView->SetUseForMainWindowTitle( true ); +#if 0 + new TestWindow( workspace, 100, 100, 256, 256 ); +#endif + + Con_Printf( "IFacePoserToolWindow::Init\n" ); + + IFacePoserToolWindow::InitTools(); + + Con_Printf( "windowtab->Init\n" ); + + windowtab->Init(); + + Con_Printf( "loadRecentFiles\n" ); + + loadRecentFiles (); + initRecentFiles (); + + Con_Printf( "RestoreThumbnailSize\n" ); + + g_pExpressionTrayTool->RestoreThumbnailSize(); + g_pAnimationBrowserTool->RestoreThumbnailSize(); + + Con_Printf( "Add Tool Windows\n" ); + + int c = IFacePoserToolWindow::GetToolCount(); + for ( i = 0; i < c ; i++ ) + { + IFacePoserToolWindow *tool = IFacePoserToolWindow::GetTool( i ); + menuWindow->add( tool->GetToolName(), IDC_WINDOW_FIRSTTOOL + i ); + } + + menuWindow->addSeparator(); + menuWindow->add( "Cascade", IDC_WINDOW_CASCADE ); + menuWindow->addSeparator(); + menuWindow->add( "Tile", IDC_WINDOW_TILE ); + menuWindow->add( "Tile Horizontally", IDC_WINDOW_TILE_HORIZ ); + menuWindow->add( "Tile Vertically", IDC_WINDOW_TILE_VERT ); + menuWindow->addSeparator(); + menuWindow->add( "Hide All", IDC_WINDOW_HIDEALL ); + menuWindow->add( "Show All", IDC_WINDOW_SHOWALL ); + + Con_Printf( "UpdateWindowMenu\n" ); + + UpdateWindowMenu(); + // Check the default item + UpdateLanguageMenu( g_viewerSettings.cclanguageid ); + + m_nCurrentFrame = 0; + + Con_Printf( "gridsettings->Init()\n" ); + + gridsettings->Init(); + + Con_Printf( "LoadWindowPositions\n" ); + + LoadWindowPositions(); + + Con_Printf( "Model viewer created\n" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void MDLViewer::UpdateWindowMenu( void ) +{ + int c = IFacePoserToolWindow::GetToolCount(); + for ( int i = 0; i < c ; i++ ) + { + IFacePoserToolWindow *tool = IFacePoserToolWindow::GetTool( i ); + menuWindow->setChecked( IDC_WINDOW_FIRSTTOOL + i, tool->GetMxWindow()->isVisible() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : currentLanguageId - +//----------------------------------------------------------------------------- +void MDLViewer::UpdateLanguageMenu( int currentLanguageId ) +{ + if ( !menuCloseCaptionLanguages ) + return; + + for ( int i = 0; i < CC_NUM_LANGUAGES; i++ ) + { + int id = IDC_OPTIONS_LANGUAGESTART + i; + menuCloseCaptionLanguages->setChecked( id, i == currentLanguageId ? true : false ); + } +} + +void MDLViewer::OnDelete() +{ + saveRecentFiles (); + SaveViewerRootSettings( ); + +#ifdef WIN32 + DeleteFile ("hlmv.cfg"); + DeleteFile ("midump.txt"); +#endif + + IFacePoserToolWindow::ShutdownTools(); + + g_MDLViewer = NULL; +} + +MDLViewer::~MDLViewer () +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void MDLViewer::InitModelTab( void ) +{ + modeltab->Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void MDLViewer::InitGridSettings( void ) +{ + gridsettings->Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int MDLViewer::GetActiveModelTab( void ) +{ + return modeltab->getSelectedIndex(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : modelindex - +//----------------------------------------------------------------------------- +void MDLViewer::SetActiveModelTab( int modelindex ) +{ + modeltab->select( modelindex ); + modeltab->HandleModelSelect(); +} + +//----------------------------------------------------------------------------- +// Purpose: Reloads the currently loaded model file. +//----------------------------------------------------------------------------- +void MDLViewer::Refresh( void ) +{ + Con_ColorPrintf( RGB( 0, 125, 255 ), "Refreshing...\n" ); + + bool reinit_soundemitter = true; + + // Save any changed sound script files + int c = soundemitter->GetNumSoundScripts(); + for ( int i = 0; i < c; i++ ) + { + if ( !soundemitter->IsSoundScriptDirty( i ) ) + continue; + + char const *scriptname = soundemitter->GetSoundScriptName( i ); + if ( !scriptname ) + continue; + + if ( !filesystem->FileExists( scriptname ) || + !filesystem->IsFileWritable( scriptname ) ) + { + continue; + } + + int retval = mxMessageBox( NULL, va( "Save changes to sound script '%s'?", scriptname ), g_appTitle, MX_MB_YESNOCANCEL ); + if ( retval != 0 ) + { + reinit_soundemitter = false; + continue; + } + + if ( retval == 0 ) + { + soundemitter->SaveChangesToSoundScript( i ); + Con_ColorPrintf( RGB( 50, 255, 100 ), " saving changes to script file '%s'\n", scriptname ); + } + } + + // kill the soundemitter system + if ( reinit_soundemitter ) + { + soundemitter->Shutdown(); + } + + + Con_ColorPrintf( RGB( 50, 255, 100 ), " reloading textures\n" ); + g_pMaterialSystem->ReloadTextures(); + + models->ReleaseModels(); + + Con_ColorPrintf( RGB( 50, 255, 100 ), " reloading models\n" ); + models->RestoreModels(); + + // restart the soundemitter system + if ( reinit_soundemitter ) + { + Con_ColorPrintf( RGB( 50, 255, 100 ), " reloading sound emitter system\n" ); + soundemitter->Init(); + } + else + { + Con_ColorPrintf( RGB( 250, 50, 50 ), " NOT reloading sound emitter system\n" ); + } + + Con_ColorPrintf( RGB( 0, 125, 255 ), "done.\n" ); +} + +void MDLViewer::OnFileLoaded( char const *pszFile ) +{ + int i; + for (i = 0; i < 8; i++) + { + if (!Q_stricmp( recentFiles[i], pszFile )) + break; + } + + // swap existing recent file + if (i < 8) + { + char tmp[256]; + strcpy (tmp, recentFiles[0]); + strcpy (recentFiles[0], recentFiles[i]); + strcpy (recentFiles[i], tmp); + } + + // insert recent file + else + { + for (i = 7; i > 0; i--) + strcpy (recentFiles[i], recentFiles[i - 1]); + + strcpy( recentFiles[0], pszFile ); + } + + initRecentFiles (); + + if ( g_pVCDBrowser ) + { + g_pVCDBrowser->SetCurrent( pszFile ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Loads the file and updates the MRU list. +// Input : pszFile - File to load. +//----------------------------------------------------------------------------- +void MDLViewer::LoadModelFile( const char *pszFile ) +{ + models->LoadModel( pszFile ); + + OnFileLoaded( pszFile ); + + g_pControlPanel->CenterOnFace(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *wnd - +// x - +// y - +// Output : static bool +//----------------------------------------------------------------------------- +static bool WindowContainsPoint( mxWindow *wnd, int x, int y ) +{ + POINT pt; + pt.x = (short)x; + pt.y = (short)y; + + HWND window = (HWND)wnd->getHandle(); + if ( !window ) + return false; + + ScreenToClient( window, &pt ); + + if ( pt.x < 0 ) + return false; + if ( pt.y < 0 ) + return false; + if ( pt.x > wnd->w() ) + return false; + if ( pt.y > wnd->h() ) + return false; + + return true; +} + + +void MDLViewer::LoadModel_Steam() +{ + if ( !g_FSDialogFactory ) + return; + + IFileSystemOpenDialog *pDlg; + pDlg = (IFileSystemOpenDialog*)g_FSDialogFactory( FILESYSTEMOPENDIALOG_VERSION, NULL ); + if ( !pDlg ) + { + char str[512]; + Q_snprintf( str, sizeof( str ), "Can't create %s interface.", FILESYSTEMOPENDIALOG_VERSION ); + ::MessageBox( NULL, str, "Error", MB_OK ); + return; + } + pDlg->Init( g_Factory, NULL ); + pDlg->AddFileMask( "*.jpg" ); + pDlg->AddFileMask( "*.mdl" ); + pDlg->SetInitialDir( "models", "game" ); + pDlg->SetFilterMdlAndJpgFiles( true ); + + if (pDlg->DoModal() == IDOK) + { + char filename[MAX_PATH]; + pDlg->GetFilename( filename, sizeof( filename ) ); + LoadModelFile( filename ); + } + + pDlg->Release(); +} + + + +int MDLViewer::handleEvent (mxEvent *event) +{ + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + int iret = 0; + + switch (event->event) + { + case mxEvent::Size: + { + int width = w2(); + int height = h2(); + + windowtab->SetRowHeight( WINDOW_TAB_OFFSET - 2 ); + modeltab->SetRowHeight( WINDOW_TAB_OFFSET - 2 ); + + int gridsettingswide = 100; + int gridstart = width - gridsettingswide - 5; + + int modelwide = gridstart / 3; + int windowwide = gridstart - modelwide; + + int rowheight = max( windowtab->GetBestHeight( windowwide ), modeltab->GetBestHeight( modelwide ) ); + + workspace->setBounds( 0, 0, width, height - rowheight ); + + gridsettings->setBounds( gridstart, height - rowheight + 1, gridsettingswide, WINDOW_TAB_OFFSET - 2 ); + + windowtab->setBounds( 0, height - rowheight, windowwide, rowheight ); + modeltab->setBounds( windowwide, height - rowheight, modelwide, rowheight ); + + iret = 1; + } + break; + case mxEvent::Action: + { + iret = 1; + switch (event->action) + { + case IDC_WINDOW_TAB: + { + windowtab->HandleWindowSelect(); + } + break; + case IDC_MODEL_TAB: + { + modeltab->HandleModelSelect(); + } + break; + + case IDC_FILE_LOADMODEL: + { + if ( ! CommandLine()->FindParm( "-NoSteamDialog" ) ) + { + g_MDLViewer->LoadModel_Steam(); + } + else + { + char modelfile[ 512 ]; + if ( FacePoser_ShowOpenFileNameDialog( modelfile, sizeof( modelfile ), "models", "*.mdl" ) ) + { + LoadModelFile( modelfile ); + } + } + } + break; + + case IDC_FILE_REFRESH: + { + Refresh(); + break; + } + + case IDC_FILE_SAVESOUNDSCRIPTCHANGES: + { + OnSaveSoundScriptChanges(); + } + break; + case IDC_FILE_REBUILDSCENESIMAGE: + { + OnRebuildScenesImage(); + } + break; + + case IDC_FILE_LOADBACKGROUNDTEX: + case IDC_FILE_LOADGROUNDTEX: + { + const char *ptr = mxGetOpenFileName (this, 0, "*.*"); + if (ptr) + { + if (0 /* g_pMatSysWindow->loadTexture (ptr, event->action - IDC_FILE_LOADBACKGROUNDTEX) */) + { + if (event->action == IDC_FILE_LOADBACKGROUNDTEX) + g_pControlPanel->setShowBackground (true); + else + g_pControlPanel->setShowGround (true); + + } + else + mxMessageBox (this, "Error loading texture.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + } + } + break; + + case IDC_FILE_UNLOADGROUNDTEX: + { + // g_pMatSysWindow->loadTexture (0, 1); + g_pControlPanel->setShowGround (false); + } + break; + + case IDC_FILE_RECENTFILES1: + case IDC_FILE_RECENTFILES2: + case IDC_FILE_RECENTFILES3: + case IDC_FILE_RECENTFILES4: + case IDC_FILE_RECENTFILES5: + case IDC_FILE_RECENTFILES6: + case IDC_FILE_RECENTFILES7: + case IDC_FILE_RECENTFILES8: + { + int i = event->action - IDC_FILE_RECENTFILES1; + + if ( recentFiles[ i ] && recentFiles[ i ][ 0 ] ) + { + char ext[ 4 ]; + Q_ExtractFileExtension( recentFiles[ i ], ext, sizeof( ext ) ); + bool valid = false; + if ( !Q_stricmp( ext, "mdl" ) ) + { + // Check extension + LoadModelFile( recentFiles[ i ] ); + valid = true; + } + else if ( !Q_stricmp( ext, "vcd" ) ) + { + g_pChoreoView->LoadSceneFromFile( recentFiles[ i ] ); + valid = true; + } + + if ( valid ) + { + char tmp[256]; + strcpy (tmp, recentFiles[0]); + strcpy (recentFiles[0], recentFiles[i]); + strcpy (recentFiles[i], tmp); + + initRecentFiles (); + } + } + + redraw (); + } + break; + + case IDC_FILE_EXIT: + { + redraw (); + mx::quit (); + } + break; + + case IDC_OPTIONS_COLORBACKGROUND: + case IDC_OPTIONS_COLORGROUND: + case IDC_OPTIONS_COLORLIGHT: + { + float *cols[3] = { g_viewerSettings.bgColor, g_viewerSettings.gColor, g_viewerSettings.lColor }; + float *col = cols[event->action - IDC_OPTIONS_COLORBACKGROUND]; + int r = (int) (col[0] * 255.0f); + int g = (int) (col[1] * 255.0f); + int b = (int) (col[2] * 255.0f); + if (mxChooseColor (this, &r, &g, &b)) + { + col[0] = (float) r / 255.0f; + col[1] = (float) g / 255.0f; + col[2] = (float) b / 255.0f; + } + } + break; + + case IDC_OPTIONS_CENTERVIEW: + g_pControlPanel->centerView (); + break; + + case IDC_OPTIONS_CENTERONFACE: + g_pControlPanel->CenterOnFace(); + break; + + case IDC_OPTIONS_CLEARMODELSOUNDS: + { + sound->StopAll(); + Con_ColorPrintf( RGB( 0, 100, 255 ), "Resetting model sound channels\n" ); + } + break; + + case IDC_OPTIONS_MAKESCREENSHOT: + { + char *ptr = (char *) mxGetSaveFileName (this, "", "*.tga"); + if (ptr) + { + char fn[ 512 ]; + Q_strncpy( fn, ptr, sizeof( fn ) ); + Q_SetExtension( fn, ".tga", sizeof( fn ) ); + g_pMatSysWindow->TakeScreenShot( fn ); + } + } + break; + + case IDC_OPTIONS_DUMP: + g_pControlPanel->dumpModelInfo (); + break; + +#ifdef WIN32 + case IDC_HELP_GOTOHOMEPAGE: + ShellExecute (0, "open", "http://developer.valvesoftware.com/wiki/Category:Choreography", 0, 0, SW_SHOW); + break; +#endif + + case IDC_HELP_ABOUT: + mxMessageBox (this, + "v1.0 Copyright � 1996-2007, Valve Corporation. All rights reserved.\r\nBuild Date: " __DATE__ "", + "Valve Face Poser", + MX_MB_OK | MX_MB_INFORMATION); + break; + + case IDC_EXPRESSIONS_REDOBITMAPS: + { + CExpClass *active = expressions->GetActiveClass(); + if ( active ) + { + g_pProgressDialog->Start( "Rebuild Bitmaps", "", true ); + + g_pMatSysWindow->EnableStickySnapshotMode( ); + for ( int i = 0; i < active->GetNumExpressions() ; i++ ) + { + CExpression *exp = active->GetExpression( i ); + if ( !exp ) + continue; + + g_pProgressDialog->UpdateText( exp->name ); + g_pProgressDialog->Update( (float)i / (float)active->GetNumExpressions() ); + if ( g_pProgressDialog->IsCancelled() ) + { + Msg( "Cancelled\n" ); + break; + } + + exp->CreateNewBitmap( models->GetActiveModelIndex() ); + + if ( ! ( i % 5 ) ) + { + g_pExpressionTrayTool->redraw(); + } + } + g_pMatSysWindow->DisableStickySnapshotMode( ); + + g_pProgressDialog->Finish(); + + active->SelectExpression( 0 ); + } + } + break; + case IDC_EXPRESSIONS_NEW: + { + char classfile[ 512 ]; + if ( FacePoser_ShowSaveFileNameDialog( classfile, sizeof( classfile ), "expressions", "*.txt" ) ) + { + Q_DefaultExtension( classfile, ".txt", sizeof( classfile ) ); + expressions->CreateNewClass( classfile ); + } + } + break; + case IDC_EXPRESSIONS_LOAD: + { + char classfile[ 512 ]; + if ( FacePoser_ShowOpenFileNameDialog( classfile, sizeof( classfile ), "expressions", "*.txt" ) ) + { + expressions->LoadClass( classfile ); + } + } + break; + + case IDC_EXPRESSIONS_SAVE: + { + CExpClass *active = expressions->GetActiveClass(); + if ( active ) + { + active->Save(); + active->Export(); + } + } + break; + case IDC_EXPRESSIONS_EXPORT: + { + CExpClass *active = expressions->GetActiveClass(); + if ( active ) + { + active->Export(); + } + } + break; + case IDC_EXPRESSIONS_CLOSE: + g_pControlPanel->Close(); + break; + case IDC_EXPRESSIONS_CLOSEALL: + g_pControlPanel->Closeall(); + break; + case IDC_CHOREOSCENE_NEW: + g_pChoreoView->New(); + break; + case IDC_CHOREOSCENE_LOAD: + g_pChoreoView->Load(); + break; + case IDC_CHOREOSCENE_LOADNEXT: + g_pChoreoView->LoadNext(); + break; + case IDC_CHOREOSCENE_SAVE: + g_pChoreoView->Save(); + break; + case IDC_CHOREOSCENE_SAVEAS: + g_pChoreoView->SaveAs(); + break; + case IDC_CHOREOSCENE_CLOSE: + g_pChoreoView->Close(); + break; + case IDC_CHOREOSCENE_ADDACTOR: + g_pChoreoView->NewActor(); + break; + case IDC_WINDOW_TILE: + { + OnTile(); + } + break; + case IDC_WINDOW_TILE_HORIZ: + { + OnTileHorizontally(); + } + break; + case IDC_WINDOW_TILE_VERT: + { + OnTileVertically(); + } + break; + case IDC_WINDOW_CASCADE: + { + OnCascade(); + } + break; + case IDC_WINDOW_HIDEALL: + { + OnHideAll(); + } + break; + case IDC_WINDOW_SHOWALL: + { + OnShowAll(); + } + break; + default: + { + iret = 0; + int tool_number = event->action - IDC_WINDOW_FIRSTTOOL; + int max_tools = IDC_WINDOW_LASTTOOL - IDC_WINDOW_FIRSTTOOL; + + if ( tool_number >= 0 && + tool_number <= max_tools && + tool_number < IFacePoserToolWindow::GetToolCount() ) + { + iret = 1; + IFacePoserToolWindow *tool = IFacePoserToolWindow::GetTool( tool_number ); + if ( tool ) + { + mxWindow *toolw = tool->GetMxWindow(); + + bool wasvisible = toolw->isVisible(); + toolw->setVisible( !wasvisible ); + + g_MDLViewer->UpdateWindowMenu(); + + } + } + + int lang_number = event->action - IDC_OPTIONS_LANGUAGESTART; + if ( lang_number >= 0 && + lang_number < CC_NUM_LANGUAGES ) + { + iret = 1; + SetCloseCaptionLanguageId( lang_number ); + } + } + break; + } //switch (event->action) + } // mxEvent::Action + break; + case KeyDown: + { + //g_pMatSysWindow->handleEvent(event); + // Send it to the active tool + IFacePoserToolWindow *active = IFacePoserToolWindow::GetActiveTool(); + if ( active ) + { + mxWindow *w = active->GetMxWindow(); + if ( w ) + { + w->handleEvent( event ); + } + } + else + { + g_pMatSysWindow->handleEvent(event); + } + iret = 1; + } + break; + case mxEvent::Activate: + { + if (event->action) + { + mx::setIdleWindow( g_pMatSysWindow ); + // Force reload of localization data + SetCloseCaptionLanguageId( GetCloseCaptionLanguageId(), true ); + } + else + { + mx::setIdleWindow( 0 ); + } + iret = 1; + } + break; + } // event->event + + return iret; +} + +void MDLViewer::SaveWindowPositions( void ) +{ + // Save the model viewer position + SavePosition(); + + int c = IFacePoserToolWindow::GetToolCount(); + for ( int i = 0; i < c; i++ ) + { + IFacePoserToolWindow *w = IFacePoserToolWindow::GetTool( i ); + w->SavePosition(); + } +} + +void MDLViewer::LoadWindowPositions( void ) +{ + // NOTE: Don't do this here, we do the mdlviewer position earlier in startup + // LoadPosition(); + + int w = this->w(); + int h = this->h(); + + g_viewerSettings.width = w; + g_viewerSettings.height = h; + + int c = IFacePoserToolWindow::GetToolCount(); + for ( int i = 0; i < c; i++ ) + { + IFacePoserToolWindow *w = IFacePoserToolWindow::GetTool( i ); + w->LoadPosition(); + } +} + +void +MDLViewer::redraw () +{ +} + +int MDLViewer::GetCurrentFrame( void ) +{ + return m_nCurrentFrame; +} + +void MDLViewer::Think( float dt ) +{ + ++m_nCurrentFrame; + + // Iterate across tools + IFacePoserToolWindow::ToolThink( dt ); + + sound->Update( dt ); + + bool soundscriptsdirty = AreSoundScriptsDirty(); + if ( soundscriptsdirty != m_bOldSoundScriptsDirty ) + { + // Update the menu item when this changes + menuFile->setEnabled(IDC_FILE_SAVESOUNDSCRIPTCHANGES, soundscriptsdirty ); + } + + m_bOldSoundScriptsDirty = soundscriptsdirty; +} + +static int CountVisibleTools( void ) +{ + int i; + int c = IFacePoserToolWindow::GetToolCount(); + int viscount = 0; + + for ( i = 0; i < c; i++ ) + { + IFacePoserToolWindow *tool = IFacePoserToolWindow::GetTool( i ); + mxWindow *w = tool->GetMxWindow(); + if ( !w->isVisible() ) + continue; + + viscount++; + } + + return viscount; +} + +void MDLViewer::OnCascade() +{ + int i; + int c = IFacePoserToolWindow::GetToolCount(); + int viscount = CountVisibleTools(); + + int x = 0, y = 0; + + int offset = 20; + + int wide = workspace->w2() - viscount * offset; + int tall = ( workspace->h2() - viscount * offset ) / 2; + + for ( i = 0; i < c; i++ ) + { + IFacePoserToolWindow *tool = IFacePoserToolWindow::GetTool( i ); + mxWindow *w = tool->GetMxWindow(); + if ( !w->isVisible() ) + continue; + + w->setBounds( x, y, wide, tall ); + x += offset; + y += offset; + } +} + +void MDLViewer::OnTile() +{ + int c = CountVisibleTools(); + + int rows = (int)sqrt( ( float )c ); + rows = clamp( rows, 1, rows ); + + int cols = 1; + while ( rows * cols < c ) + { + cols++; + } + + DoTile( rows, cols ); +} + +void MDLViewer::OnTileHorizontally() +{ + int c = CountVisibleTools(); + + DoTile( c, 1 ); +} + +void MDLViewer::OnTileVertically() +{ + int c = CountVisibleTools(); + + DoTile( 1, c ); +} + +void MDLViewer::OnHideAll() +{ + int c = IFacePoserToolWindow::GetToolCount(); + for ( int i = 0; i < c; i++ ) + { + IFacePoserToolWindow *tool = IFacePoserToolWindow::GetTool( i ); + mxWindow *w = tool->GetMxWindow(); + + w->setVisible( false ); + } + + UpdateWindowMenu(); +} + +void MDLViewer::OnShowAll() +{ + int c = IFacePoserToolWindow::GetToolCount(); + for ( int i = 0; i < c; i++ ) + { + IFacePoserToolWindow *tool = IFacePoserToolWindow::GetTool( i ); + mxWindow *w = tool->GetMxWindow(); + + w->setVisible( true ); + } + + UpdateWindowMenu(); +} + +void MDLViewer::DoTile( int x, int y ) +{ + int c = IFacePoserToolWindow::GetToolCount(); + + if ( x < 1 ) + x = 1; + if ( y < 1 ) + y = 1; + + int wide = workspace->w2() / y; + int tall = workspace->h2() / x; + + int obj = 0; + + for ( int row = 0 ; row < x ; row++ ) + { + for ( int col = 0; col < y; col++ ) + { + bool found = false; + while ( 1 ) + { + if ( obj >= c ) + break; + + IFacePoserToolWindow *tool = IFacePoserToolWindow::GetTool( obj++ ); + mxWindow *w = tool->GetMxWindow(); + if ( w->isVisible() ) + { + w->setBounds( col * wide, row * tall, wide, tall ); + + found = true; + break; + } + } + + if ( !found ) + break; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Not used by faceposer +// Output : int +//----------------------------------------------------------------------------- +int MDLViewer::GetCurrentHitboxSet(void) +{ + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool MDLViewer::PaintBackground( void ) +{ + CChoreoWidgetDrawHelper drawHelper( this ); + + RECT rc; + drawHelper.GetClientRect( rc ); + + drawHelper.DrawFilledRect( COLOR_CHOREO_BACKGROUND, rc ); + return false; +} + +void MDLViewer::OnRebuildScenesImage() +{ + g_pProgressDialog->Start( "Rebuilding scenes.image", "", false ); + + CUtlBuffer targetBuffer; + + bool bLittleEndian = true; + + const char *pFilename = bLittleEndian ? "scenes/scenes.image" : "scenes/scenes.360.image"; + + CP4AutoEditAddFile checkout( CFmtStr( "%s%s", gamedir, pFilename ) ); + + bool bSuccess = g_pSceneImage->CreateSceneImageFile( targetBuffer, gamedir, bLittleEndian, false, this ); + if ( bSuccess ) + { + scriptlib->WriteBufferToFile( pFilename, targetBuffer, WRITE_TO_DISK_ALWAYS ); + } + + g_pProgressDialog->Finish(); + m_bVCDSaved = false; +} + +void MDLViewer::UpdateStatus( char const *pchSceneName, bool bQuiet, int nIndex, int nCount ) +{ + g_pProgressDialog->UpdateText( pchSceneName ); + g_pProgressDialog->Update( (float)nIndex / (float)nCount ); +} + +void MDLViewer::OnVCDSaved() +{ + m_bVCDSaved = true; +} + +SpewRetval_t HLFacePoserSpewFunc( SpewType_t spewType, char const *pMsg ) +{ + g_bInError = true; + + switch (spewType) + { + case SPEW_ERROR: + ::MessageBox(NULL, pMsg, "FATAL ERROR", MB_OK); + g_bInError = false; + return SPEW_ABORT; + + case SPEW_LOG: + g_bInError = false; + return SPEW_CONTINUE; + + case SPEW_WARNING: + Con_ErrorPrintf( pMsg ); + g_bInError = false; + return SPEW_CONTINUE; + + default: + Con_Printf(pMsg); + g_bInError = false; +#ifdef _DEBUG + return spewType == SPEW_ASSERT ? SPEW_DEBUGGER : SPEW_CONTINUE; +#else + return SPEW_CONTINUE; +#endif + } +} + +void MDLViewer::OnSaveSoundScriptChanges() +{ + if ( !AreSoundScriptsDirty() ) + { + return; + } + +// Save any changed sound script files + int c = soundemitter->GetNumSoundScripts(); + for ( int i = 0; i < c; i++ ) + { + if ( !soundemitter->IsSoundScriptDirty( i ) ) + continue; + + char const *scriptname = soundemitter->GetSoundScriptName( i ); + if ( !scriptname ) + continue; + + if ( !filesystem->FileExists( scriptname ) ) + { + continue; + } + + if ( !filesystem->IsFileWritable( scriptname ) ) + { + mxMessageBox( NULL, va( "Can't save changes to sound script '%s', file is READ-ONLY?", scriptname ), g_appTitle, MX_MB_OK ); + continue; + } + + int retval = mxMessageBox( NULL, va( "Save changes to sound script '%s'?", scriptname ), g_appTitle, MX_MB_YESNOCANCEL ); + if ( retval == 2 ) + { + return; + } + + if ( retval == 0 ) + { + soundemitter->SaveChangesToSoundScript( i ); + } + } +} + + +//----------------------------------------------------------------------------- +// The application object +//----------------------------------------------------------------------------- +class CHLFacePoserApp : public CTier3SteamApp +{ + typedef CTier3SteamApp BaseClass; + +public: + // Methods of IApplication + virtual bool Create(); + virtual bool PreInit(); + virtual int Main(); + virtual void PostShutdown(); + virtual void Destroy(); + +private: + // Sets up the search paths + bool SetupSearchPaths(); +}; + + +//----------------------------------------------------------------------------- +// Create all singleton systems +//----------------------------------------------------------------------------- +bool CHLFacePoserApp::Create() +{ + // Save some memory so engine/hammer isn't so painful + CommandLine()->AppendParm( "-disallowhwmorph", NULL ); + + SpewOutputFunc( HLFacePoserSpewFunc ); + + AppSystemInfo_t appSystems[] = + { + { "inputsystem.dll", INPUTSYSTEM_INTERFACE_VERSION }, + { "materialsystem.dll", MATERIAL_SYSTEM_INTERFACE_VERSION }, + { "studiorender.dll", STUDIO_RENDER_INTERFACE_VERSION }, + { "vphysics.dll", VPHYSICS_INTERFACE_VERSION }, + { "datacache.dll", DATACACHE_INTERFACE_VERSION }, + { "datacache.dll", MDLCACHE_INTERFACE_VERSION }, + { "datacache.dll", STUDIO_DATA_CACHE_INTERFACE_VERSION }, + { "vguimatsurface.dll", VGUI_SURFACE_INTERFACE_VERSION }, + { "vgui2.dll", VGUI_IVGUI_INTERFACE_VERSION }, + { "soundemittersystem.dll", SOUNDEMITTERSYSTEM_INTERFACE_VERSION }, + { "", "" } // Required to terminate the list + }; + + if ( !AddSystems( appSystems ) ) + return false; + + // Add the P4 module separately so that if it is absent (say in the SDK) then the other system will initialize properly + AppModule_t p4Module = LoadModule( "p4lib.dll" ); + if ( p4Module != APP_MODULE_INVALID ) + { + AddSystem( p4Module, P4_INTERFACE_VERSION ); + } + + g_Factory = GetFactory(); + + IMaterialSystem* pMaterialSystem = (IMaterialSystem*)FindSystem( MATERIAL_SYSTEM_INTERFACE_VERSION ); + if ( !pMaterialSystem ) + { + Warning( "Material System interface could not be found!\n" ); + return false; + } + + const char *pShaderDLL = CommandLine()->ParmValue("-shaderdll"); + if(!pShaderDLL) + { + pShaderDLL = "shaderapidx9.dll"; + } + pMaterialSystem->SetShaderAPI( pShaderDLL ); + + return true; +} + + +void CHLFacePoserApp::Destroy() +{ +} + + +const char *GetGameDirectory() +{ + // TODO: get rid of this and ONLY use the filesystem, so hlfaceposer works nicely for + // mods that get the base game resources from the Steam filesystem. + return gamedir; +} + +char const *GetGameDirectorySimple() +{ + return gamedirsimple; +} + + +//----------------------------------------------------------------------------- +// Sets up the game path +//----------------------------------------------------------------------------- +bool CHLFacePoserApp::SetupSearchPaths() +{ + // Add paths... + if ( !BaseClass::SetupSearchPaths( NULL, false, true ) ) + return false; + + // Set gamedir. + Q_MakeAbsolutePath( gamedir, sizeof( gamedir ), GetGameInfoPath() ); + + Q_FileBase( gamedir, gamedirsimple, sizeof( gamedirsimple ) ); + + Q_AppendSlash( gamedir, sizeof( gamedir ) ); + + workspacefiles->Init( GetGameDirectorySimple() ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Init, shutdown +//----------------------------------------------------------------------------- +bool CHLFacePoserApp::PreInit( ) +{ + if ( !BaseClass::PreInit() ) + return false; + + g_pFileSystem = filesystem = g_pFullFileSystem; + g_pStudioDataCache = (IStudioDataCache*)FindSystem( STUDIO_DATA_CACHE_INTERFACE_VERSION ); + physcollision = (IPhysicsCollision *)FindSystem( VPHYSICS_COLLISION_INTERFACE_VERSION ); + physprop = (IPhysicsSurfaceProps *)FindSystem( VPHYSICS_SURFACEPROPS_INTERFACE_VERSION ); + g_pLocalize = (vgui::ILocalize *)FindSystem(VGUI_LOCALIZE_INTERFACE_VERSION ); + soundemitter = (ISoundEmitterSystemBase*)FindSystem(SOUNDEMITTERSYSTEM_INTERFACE_VERSION); + + if ( !soundemitter || !g_pLocalize || !filesystem || !physprop || !physcollision || + !g_pMaterialSystem || !g_pStudioRender || !g_pMDLCache || !g_pDataCache ) + { + Error("Unable to load required library interface!\n"); + } + + MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false ); + filesystem->SetWarningFunc( Warning ); + + // Add paths... + if ( !SetupSearchPaths() ) + return false; + + // Get the adapter from the command line.... + const char *pAdapterString; + int nAdapter = 0; + if (CommandLine()->CheckParm( "-adapter", &pAdapterString )) + { + nAdapter = atoi( pAdapterString ); + } + + int adapterFlags = MATERIAL_INIT_ALLOCATE_FULLSCREEN_TEXTURE; + if ( CommandLine()->CheckParm( "-ref" ) ) + { + adapterFlags |= MATERIAL_INIT_REFERENCE_RASTERIZER; + } + + g_pMaterialSystem->SetAdapter( nAdapter, adapterFlags ); + + LoadFileSystemDialogModule(); + + return true; +} + +void CHLFacePoserApp::PostShutdown() +{ + UnloadFileSystemDialogModule(); + + g_pFileSystem = filesystem = NULL; + g_pStudioDataCache = NULL; + physcollision = NULL; + physprop = NULL; + + BaseClass::PostShutdown(); + + g_Factory = NULL; +} + +//----------------------------------------------------------------------------- +// main application +//----------------------------------------------------------------------------- +int CHLFacePoserApp::Main() +{ + // Do Perforce Stuff + g_p4factory->SetDummyMode( false ); + if ( CommandLine()->FindParm( "-nop4" ) || !p4 ) + { + g_p4factory->SetDummyMode( true ); + } + + g_p4factory->SetOpenFileChangeList( "FacePoser Auto Checkout" ); + + soundemitter->ModInit(); + g_pMaterialSystem->ModInit(); + + g_pDataCache->SetSize( 64 * 1024 * 1024 ); + + // Always start with english + g_pLocalize->AddFile( "resource/closecaption_english.txt", "GAME", true ); + + sound->Init(); + + IFacePoserToolWindow::EnableToolRedraw( false ); + + g_MDLViewer = new MDLViewer (); + g_MDLViewer->setMenuBar (g_MDLViewer->getMenuBar ()); + + FaceposerVGui()->Init( (HWND)g_MDLViewer->getHandle() ); + + // Force reload of close captioning data file!!! + SetCloseCaptionLanguageId( g_viewerSettings.cclanguageid, true ); + + g_pStudioModel->Init(); + + int i; + bool modelloaded = false; + for ( i = 1; i < CommandLine()->ParmCount(); i++ ) + { + if ( Q_stristr (CommandLine()->GetParm( i ), ".mdl") ) + { + modelloaded = true; + g_MDLViewer->LoadModelFile( CommandLine()->GetParm( i ) ); + break; + } + } + + models->LoadModelList(); + g_pPhonemeEditor->ValidateSpeechAPIIndex(); + + if ( models->Count() == 0 ) + { + g_pFlexPanel->initFlexes( ); + } + + // Load expressions from last time + int files = workspacefiles->GetNumStoredFiles( IWorkspaceFiles::EXPRESSION ); + for ( i = 0; i < files; i++ ) + { + expressions->LoadClass( workspacefiles->GetStoredFile( IWorkspaceFiles::EXPRESSION, i ) ); + } + + IFacePoserToolWindow::EnableToolRedraw( true ); + + int nRetVal = mx::run (); + + if (g_pStudioModel) + { + g_pStudioModel->Shutdown(); + g_pStudioModel = NULL; + } + + g_pMaterialSystem->ModShutdown(); + soundemitter->ModShutdown(); + g_pMaterialSystem->ModShutdown(); + + FaceposerVGui()->Shutdown(); + + return nRetVal; +} + +static bool CHLFacePoserApp_SuggestGameInfoDirFn( CFSSteamSetupInfo const *pFsSteamSetupInfo, char *pchPathBuffer, int nBufferLength, bool *pbBubbleDirectories ) +{ + if ( pbBubbleDirectories ) + *pbBubbleDirectories = true; + + for ( int i = 1; i < CommandLine()->ParmCount(); i++ ) + { + if ( Q_stristr( CommandLine()->GetParm( i ), ".mdl" ) ) + { + Q_MakeAbsolutePath( pchPathBuffer, nBufferLength, CommandLine()->GetParm( i ) ); + return true; + } + } + + return false; +} + +int main (int argc, char *argv[]) +{ + CommandLine()->CreateCmdLine( argc, argv ); + CoInitialize(NULL); + + // make sure, we start in the right directory + char szName[256]; + strcpy (szName, mx::getApplicationPath() ); + mx::init (argc, argv); + + char workingdir[ 256 ]; + workingdir[0] = 0; + Q_getwd( workingdir, sizeof( workingdir ) ); + + // Set game info directory suggestion callback + SetSuggestGameInfoDirFn( CHLFacePoserApp_SuggestGameInfoDirFn ); + + CHLFacePoserApp hlFacePoserApp; + CSteamApplication steamApplication( &hlFacePoserApp ); + int nRetVal = steamApplication.Run(); + + CoUninitialize(); + + return nRetVal; +} + diff --git a/utils/hlfaceposer/mdlviewer.h b/utils/hlfaceposer/mdlviewer.h new file mode 100644 index 0000000..69ba681 --- /dev/null +++ b/utils/hlfaceposer/mdlviewer.h @@ -0,0 +1,196 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef MDLVIEWER_H +#define MDLVIEWER_H +#ifdef _WIN32 +#pragma once +#endif + +#include "sceneimage.h" + +#define IDC_FILE_LOADMODEL 1001 +#define IDC_FILE_LOADBACKGROUNDTEX 1002 +#define IDC_FILE_LOADGROUNDTEX 1003 +#define IDC_FILE_UNLOADGROUNDTEX 1004 +#define IDC_FILE_RECENTFILES1 1008 +#define IDC_FILE_RECENTFILES2 1009 +#define IDC_FILE_RECENTFILES3 1010 +#define IDC_FILE_RECENTFILES4 1011 +#define IDC_FILE_RECENTFILES5 1012 +#define IDC_FILE_RECENTFILES6 1013 +#define IDC_FILE_RECENTFILES7 1014 +#define IDC_FILE_RECENTFILES8 1015 +#define IDC_FILE_EXIT 1016 +#define IDC_FILE_REFRESH 1017 +#define IDC_FILE_SAVESOUNDSCRIPTCHANGES 1018 +#define IDC_FILE_REBUILDSCENESIMAGE 1019 + +#define IDC_EXPRESSIONS_SAVE 1020 +#define IDC_EXPRESSIONS_LOAD 1021 +#define IDC_EXPRESSIONS_SAVEAS 1022 + +#define IDC_EXPRESSIONS_EXPORT 1023 + +#define IDC_EXPRESSIONS_CLOSE 1024 +#define IDC_EXPRESSIONS_CLOSEALL 1025 + +#define IDC_EXPRESSIONS_NEW 1026 +#define IDC_EXPRESSIONS_REDOBITMAPS 1027 + + +#define IDC_CHOREOSCENE_NEW 1030 +#define IDC_CHOREOSCENE_LOAD 1031 +#define IDC_CHOREOSCENE_SAVE 1032 +#define IDC_CHOREOSCENE_SAVEAS 1033 +#define IDC_CHOREOSCENE_CLOSE 1034 +#define IDC_CHOREOSCENE_ADDACTOR 1035 +#define IDC_FILE_LOADMODEL_STEAM 1036 +#define IDC_CHOREOSCENE_LOADNEXT 1038 + +#define IDC_OPTIONS_COLORBACKGROUND 1101 +#define IDC_OPTIONS_COLORGROUND 1102 +#define IDC_OPTIONS_COLORLIGHT 1103 +#define IDC_OPTIONS_CENTERVIEW 1104 +#define IDC_OPTIONS_MAKESCREENSHOT 1105 +#define IDC_OPTIONS_DUMP 1106 +#define IDC_OPTIONS_CENTERONFACE 1107 +#define IDC_OPTIONS_CLEARMODELSOUNDS 1108 + +#define IDC_OPTIONS_LANGUAGESTART 1150 + +#define IDC_WINDOW_FIRSTTOOL 1200 +#define IDC_WINDOW_LASTTOOL 1231 +#define IDC_WINDOW_TILE_HORIZ 1232 +#define IDC_WINDOW_TILE_VERT 1233 +#define IDC_WINDOW_CASCADE 1234 +#define IDC_WINDOW_HIDEALL 1235 +#define IDC_WINDOW_SHOWALL 1236 +#define IDC_WINDOW_TILE 1237 + +#define IDC_WINDOW_TAB 1238 +#define IDC_MODEL_TAB 1239 +#define IDC_GRIDSETTINGS 1240 + +#define IDC_HELP_GOTOHOMEPAGE 1301 +#define IDC_HELP_ABOUT 1302 + +class mxMenuBar; +class mxMenu; +class MatSysWindow; +class ControlPanel; +class FlexPanel; +class mxStatusWindow; +class CChoreoView; +class CMDLViewerWorkspace; +class CMDLViewerWindowTab; +class CMDLViewerModelTab; +class CMDLViewerGridSettings; + +enum { Action, Size, Timer, Idle, Show, Hide, + MouseUp, MouseDown, MouseMove, MouseDrag, + KeyUp, KeyDown + }; + +class MDLViewer : public mxWindow, public ISceneCompileStatus +{ + mxMenuBar *mb; + mxMenu *menuFile; + mxMenu *menuOptions; + mxMenu *menuCloseCaptionLanguages; + mxMenu *menuWindow; + mxMenu *menuHelp; + mxMenu *menuEdit; + mxMenu *menuExpressions; + mxMenu *menuChoreography; + + CMDLViewerWorkspace *workspace; + CMDLViewerWindowTab *windowtab; + CMDLViewerModelTab *modeltab; + CMDLViewerGridSettings *gridsettings; + + void loadRecentFiles (); + void saveRecentFiles (); + void initRecentFiles (); + + int m_nCurrentFrame; + +public: + // CREATORS + MDLViewer (); + ~MDLViewer (); + + virtual void OnDelete(); + virtual bool CanClose(); + + virtual void UpdateStatus( char const *pchSceneName, bool bQuiet, int nIndex, int nCount ); + + void OnFileLoaded( char const *pszFile ); + + // MANIPULATORS + virtual int handleEvent (mxEvent *event); + void redraw (); + virtual bool PaintBackground( void ); + + void UpdateWindowMenu( void ); + void UpdateLanguageMenu( int currentLanguageId ); + + void InitModelTab( void ); + void InitGridSettings( void ); + + int GetActiveModelTab( void ); + void SetActiveModelTab( int modelindex ); + + void Refresh( void ); + void LoadModelFile( const char *pszFile ); + int GetCurrentHitboxSet(void); + + virtual bool Closing( void ); + + void LoadWindowPositions( void ); + void SaveWindowPositions( void ); + + void OnSaveSoundScriptChanges(); + void OnRebuildScenesImage(); + + void OnCascade(); + void OnTile(); + void OnTileHorizontally(); + void OnTileVertically(); + + void OnHideAll(); + void OnShowAll(); + + void Think( float dt ); + + int GetCurrentFrame( void ); + + // ACCESSORS + mxMenuBar *getMenuBar () const { return mb; } + + void LoadModel_Steam(); + + void OnVCDSaved(); + +private: + void DoTile( int x, int y ); + + void LoadPosition( void ); + void SavePosition( void ); + + bool AreSoundScriptsDirty(); + + bool m_bOldSoundScriptsDirty; + bool m_bVCDSaved; +}; + + +const char *GetGameDirectory( ); +void CreatePath( const char *pPath ); +extern MDLViewer *g_MDLViewer; +extern char g_appTitle[]; + +#endif // MDLVIEWER_H diff --git a/utils/hlfaceposer/mxbitmapbutton.cpp b/utils/hlfaceposer/mxbitmapbutton.cpp new file mode 100644 index 0000000..4f088b7 --- /dev/null +++ b/utils/hlfaceposer/mxbitmapbutton.cpp @@ -0,0 +1,96 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "mxBitmapButton.h" +#include "hlfaceposer.h" + + +mxBitmapButton::mxBitmapButton( mxWindow *parent, int x, int y, int w, int h, int id /*= 0*/, const char *bitmap /* = 0 */ ) +: mxWindow( parent, x, y, w, h, "" ) +{ + setId( id ); + + m_bmImage.valid = false; + + SetImage( bitmap ); + + HWND wnd = (HWND)getHandle(); + + DWORD style = GetWindowLong( wnd, GWL_STYLE ); + style |= WS_CLIPSIBLINGS; + SetWindowLong( wnd, GWL_STYLE, style ); +} + +mxBitmapButton::~mxBitmapButton( void ) +{ + DeleteImage(); +} + +void mxBitmapButton::redraw() +{ + HWND wnd = (HWND)getHandle(); + if ( !wnd ) + return; + + if ( !m_bmImage.valid ) + return; + + RECT rc; + GetClientRect( wnd, &rc ); + + HDC dc = GetDC( wnd ); + + DrawBitmapToDC( dc, 0, 0, w(), h(), m_bmImage ); + + ReleaseDC( wnd, dc ); + + ValidateRect( wnd, &rc ); +} + +int mxBitmapButton::handleEvent( mxEvent * event ) +{ + int iret = 0; + + switch (event->event) + { + case mxEvent::MouseUp: + // Send message to parent + HWND parent = (HWND)( getParent() ? getParent()->getHandle() : NULL ); + if ( parent ) + { + LPARAM lp; + WPARAM wp; + + wp = MAKEWPARAM( getId(), BN_CLICKED ); + lp = (long)getHandle(); + + SendMessage( parent, WM_COMMAND, wp, lp ); + iret = 1; + } + break; + } + + return iret; +} + +void mxBitmapButton::SetImage( const char *bitmapname ) +{ + if ( m_bmImage.valid ) + { + DeleteImage(); + } + + LoadBitmapFromFile( bitmapname, m_bmImage ); +} + +void mxBitmapButton::DeleteImage( void ) +{ + if ( m_bmImage.valid ) + { + DeleteObject( m_bmImage.image ); + m_bmImage.valid = false; + } +} diff --git a/utils/hlfaceposer/mxbitmapbutton.h b/utils/hlfaceposer/mxbitmapbutton.h new file mode 100644 index 0000000..60e1d9a --- /dev/null +++ b/utils/hlfaceposer/mxbitmapbutton.h @@ -0,0 +1,34 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef MXBITMAPBUTTON_H +#define MXBITMAPBUTTON_H +#ifdef _WIN32 +#pragma once +#endif + + +#include <mxtk/mx.h> +#include "mxBitmapTools.h" + +class mxBitmapButton : public mxWindow +{ +public: + mxBitmapButton( mxWindow *parent, int x, int y, int w, int h, int id = 0, const char *bitmap = 0 ); + ~mxBitmapButton( void ); + + virtual void redraw(); + virtual int handleEvent( mxEvent * event ); + + void SetImage( const char *bitmapname ); + +private: + void DeleteImage( void ); + + mxbitmapdata_t m_bmImage; +}; +#endif // MXBITMAPBUTTON_H diff --git a/utils/hlfaceposer/mxbitmaptools.cpp b/utils/hlfaceposer/mxbitmaptools.cpp new file mode 100644 index 0000000..87b23c9 --- /dev/null +++ b/utils/hlfaceposer/mxbitmaptools.cpp @@ -0,0 +1,154 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "mxbitmaptools.h" +#include <mxtk/mx.h> +#include "hlfaceposer.h" +#include "filesystem.h" + +bool LoadBitmapFromFile( const char *relative, mxbitmapdata_t& bitmap ) +{ + + bitmap.valid = false; + bitmap.image = NULL; + bitmap.width = -1; + bitmap.height = -1; + + // Draw + HDC dc = GetDC( NULL ); + if ( !dc ) + { + return false; + } + + int width, height; + + width = 100; + height = 100; + + HBITMAP bmNewImage = (HBITMAP)0; + + HBITMAP bm, oldbm; + bm = CreateCompatibleBitmap( dc, width, height ); + if ( bm ) + { + oldbm = (HBITMAP)SelectObject( dc, bm ); + + HDC memdc = CreateCompatibleDC( dc ); + if ( memdc ) + { + char filename[ 512 ]; + filesystem->RelativePathToFullPath( relative, "MOD", filename, sizeof( filename ) ); + + bmNewImage = (HBITMAP)LoadImage( + (HINSTANCE) GetModuleHandle(0), filename, + IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE ); + + if ( !bmNewImage ) + { + filesystem->RelativePathToFullPath( relative, "GAME", filename, sizeof( filename ) ); + + bmNewImage = (HBITMAP)LoadImage( + (HINSTANCE) GetModuleHandle(0), filename, + IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE ); + } + + if ( bmNewImage ) + { + HBITMAP oldmembm = (HBITMAP)SelectObject( memdc, bmNewImage ); + + BITMAPINFO bmi; + memset( &bmi, 0, sizeof( bmi ) ); + bmi.bmiHeader.biSize = sizeof( BITMAPINFOHEADER ); + + if ( GetDIBits( memdc, bmNewImage, 0, 0, NULL, &bmi, DIB_RGB_COLORS ) ) + { + bitmap.width = bmi.bmiHeader.biWidth; + bitmap.height = bmi.bmiHeader.biHeight; + } + + SelectObject( memdc, oldmembm ); + } + + DeleteDC( memdc ); + } + + SelectObject( dc, oldbm ); + DeleteObject( bm ); + } + + ReleaseDC( NULL, dc ); + + if ( bmNewImage && + bitmap.width != -1 && + bitmap.height != -1 ) + { + bitmap.image = bmNewImage; + bitmap.valid = true; + } + + return bitmap.valid; +} + +void DrawBitmapToWindow( mxWindow *wnd, int x, int y, int w, int h, mxbitmapdata_t& bitmap ) +{ + if ( !bitmap.valid ) + return; + + // Draw + HDC dc = GetDC( (HWND) wnd->getHandle() ); + if ( !dc ) + return; + + HBITMAP bm, oldbm; + bm = CreateCompatibleBitmap( dc, w, h ); + oldbm = (HBITMAP)SelectObject( dc, bm ); + + HDC memdc = CreateCompatibleDC( dc ); + HBITMAP oldmembm = (HBITMAP)SelectObject( memdc, bitmap.image ); + + int oldmode = SetStretchBltMode( dc, COLORONCOLOR ); + + StretchBlt( dc, x, y, w, h, memdc, 0, 0, bitmap.width, bitmap.height, SRCCOPY ); + + SetStretchBltMode( dc, oldmode ); + + SelectObject( memdc, oldmembm ); + DeleteDC( memdc ); + + SelectObject( dc, oldbm ); + DeleteObject( bm ); + ReleaseDC( (HWND) wnd->getHandle(), dc ); + + RECT rc; + rc.left = x; + rc.right = x + w; + rc.top = y; + rc.bottom = y + h; + + ValidateRect( (HWND)wnd->getHandle(), &rc ); +} + +void DrawBitmapToDC( void *hdc, int x, int y, int w, int h, mxbitmapdata_t& bitmap ) +{ + if ( !bitmap.valid ) + return; + + HDC dc = (HDC)hdc; + + HDC memdc = CreateCompatibleDC( dc ); + HBITMAP oldmembm = (HBITMAP)SelectObject( memdc, bitmap.image ); + + int oldmode = SetStretchBltMode( dc, COLORONCOLOR ); + + StretchBlt( dc, x, y, w, h, memdc, 0, 0, bitmap.width, bitmap.height, SRCCOPY ); + + SetStretchBltMode( dc, oldmode ); + + SelectObject( memdc, oldmembm ); + DeleteDC( memdc ); +}
\ No newline at end of file diff --git a/utils/hlfaceposer/mxbitmaptools.h b/utils/hlfaceposer/mxbitmaptools.h new file mode 100644 index 0000000..dad3268 --- /dev/null +++ b/utils/hlfaceposer/mxbitmaptools.h @@ -0,0 +1,35 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#if !defined( MXBITMAPTOOLS_H ) +#define MXBITMAPTOOLS_H +#ifdef _WIN32 +#pragma once +#endif + +struct mxbitmapdata_t +{ + mxbitmapdata_t() + { + valid = false; + image = 0; + width = 0; + height = 0; + } + + bool valid; + void *image; + int width; + int height; +}; + +class mxWindow; + +bool LoadBitmapFromFile( const char *relative, mxbitmapdata_t& bitmap ); +void DrawBitmapToWindow( mxWindow *wnd, int x, int y, int w, int h, mxbitmapdata_t& bitmap ); +void DrawBitmapToDC( void *hdc, int x, int y, int w, int h, mxbitmapdata_t& bitmap ); + +#endif // MXBITMAPTOOLS_H
\ No newline at end of file diff --git a/utils/hlfaceposer/mxbitmapwindow.cpp b/utils/hlfaceposer/mxbitmapwindow.cpp new file mode 100644 index 0000000..d79d9c0 --- /dev/null +++ b/utils/hlfaceposer/mxbitmapwindow.cpp @@ -0,0 +1,84 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include <mxtk/mxWindow.h> +#include "mxBitmapWindow.h" +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include "tier0/dbg.h" + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *parent - +// x - +// y - +// w - +// h - +// 0 - +// 0 - +//----------------------------------------------------------------------------- +mxBitmapWindow::mxBitmapWindow(mxWindow *parent, int x, int y, int w, int h, int style /*= 0*/, const char *bitmap /*=0*/ ) +: mxWindow( parent, x, y, w, h, "", style ) +{ + m_Bitmap.valid = false; + + if ( bitmap && bitmap[ 0 ] ) + { + Load( bitmap ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +mxBitmapWindow::~mxBitmapWindow( void ) +{ + if ( m_Bitmap.valid ) + { + DeleteObject( m_Bitmap.image ); + m_Bitmap.valid = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *bitmap - +// Output : virtual void +//----------------------------------------------------------------------------- +void mxBitmapWindow::setImage( const char *bitmap ) +{ + if ( m_Bitmap.valid ) + { + DeleteObject( m_Bitmap.image ); + m_Bitmap.valid = NULL; + } + if ( bitmap && bitmap[ 0 ] ) + { + Load( bitmap ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *bitmap - +// Output : mxImage +//----------------------------------------------------------------------------- +bool mxBitmapWindow::Load( const char *bitmap ) +{ + Assert( !m_Bitmap.valid ); + return LoadBitmapFromFile( bitmap, m_Bitmap ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : void mxBitmapWindow::redraw +//----------------------------------------------------------------------------- +void mxBitmapWindow::redraw () +{ + DrawBitmapToWindow( this, 0, 0, w(), h(), m_Bitmap ); +}
\ No newline at end of file diff --git a/utils/hlfaceposer/mxbitmapwindow.h b/utils/hlfaceposer/mxbitmapwindow.h new file mode 100644 index 0000000..8bb6d5c --- /dev/null +++ b/utils/hlfaceposer/mxbitmapwindow.h @@ -0,0 +1,35 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#if !defined( MXBITMAPWINDOW_H ) +#define MXBITMAPWINDOW_H +#ifdef _WIN32 +#pragma once +#endif + +#include "mxBitmapTools.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class mxBitmapWindow : public mxWindow +{ +public: + mxBitmapWindow( mxWindow *parent, int x, int y, int w, int h, int style = 0, const char *bitmap = 0 ); + virtual ~mxBitmapWindow ( void ); + + virtual void setImage( const char *bitmap ); + + virtual void redraw (); + + virtual bool Load( const char *bitmap ); +private: + + mxbitmapdata_t m_Bitmap; + +}; + +#endif // MXBITMAPWINDOW_H
\ No newline at end of file diff --git a/utils/hlfaceposer/mxexpressionslider.cpp b/utils/hlfaceposer/mxexpressionslider.cpp new file mode 100644 index 0000000..0e82fc9 --- /dev/null +++ b/utils/hlfaceposer/mxexpressionslider.cpp @@ -0,0 +1,723 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include <stdio.h> +#include <windows.h> +#include "mxExpressionSlider.h" +#include "expressiontool.h" +#include "mathlib/mathlib.h" +#include "hlfaceposer.h" +#include "choreowidgetdrawhelper.h" + +mxExpressionSlider::mxExpressionSlider (mxWindow *parent, int x, int y, int w, int h, int id ) + : mxWindow( parent, x, y, w, h ) +{ + setId( id ); + setType( MX_SLIDER ); + + FacePoser_AddWindowStyle( this, WS_CLIPCHILDREN | WS_CLIPSIBLINGS ); + + m_flMin[ 0 ] = 0.0; + m_flMax[ 0 ] = 1.0f; + m_nTicks[ 0 ] = 20; + m_flCurrent[ 0 ] = 0.0f; + + m_flMin[ 1 ] = 0.0; + m_flMax[ 1 ] = 1.0f; + m_nTicks[ 1 ] = 20; + m_flCurrent[ 1 ] = 0.5f; + + m_flSetting[ 0 ] = 0.0; + m_flSetting[ 1 ] = 0.0; + + m_bIsEdited[ 0 ] = false; + m_bIsEdited[ 1 ] = false; + + m_bDraggingThumb = false; + m_nCurrentBar = 0; + + m_bPaired = false; + + m_nTitleWidth = 120; + m_bDrawTitle = true; + + m_pInfluence = new mxCheckBox( this, 2, 4, 12, 12, "", IDC_INFLUENCE ); +} + +mxExpressionSlider::~mxExpressionSlider( void ) +{ +} + +void mxExpressionSlider::SetTitleWidth( int width ) +{ + m_nTitleWidth = width; + redraw(); +} + +void mxExpressionSlider::SetDrawTitle( bool drawTitle ) +{ + m_bDrawTitle = drawTitle; + redraw(); +} + + +void mxExpressionSlider::SetMode( bool paired ) +{ + if ( m_bPaired != paired ) + { + m_bPaired = paired; + redraw(); + } +} + +void mxExpressionSlider::BoundValue( void ) +{ + for ( int i = 0; i < NUMBARS; i++ ) + { + if ( m_flCurrent[ i ] > m_flMax[ i ] ) + { + m_flCurrent[ i ] = m_flMax[ i ]; + } + if ( m_flCurrent[ i ] < m_flMin[ i ] ) + { + m_flCurrent[ i ] = m_flMin[ i ]; + } + } +} + + +void mxExpressionSlider::setValue( int barnum, float value ) +{ + if (m_flSetting[ barnum ] == value && m_bIsEdited[ barnum ] == false) + return; + + m_flSetting[ barnum ] = value; + m_bIsEdited[ barnum ] = false; + + if (m_bPaired) + { + if (m_flSetting[ 0 ] < m_flSetting[ 1 ]) + { + m_flCurrent[ 0 ] = m_flSetting[ 1 ]; + m_flCurrent[ 1 ] = 1.0 - (m_flSetting[ 0 ] / m_flSetting[ 1 ]) * 0.5; + } + else if (m_flSetting[ 0 ] > m_flSetting[ 1 ]) + { + m_flCurrent[ 0 ] = m_flSetting[ 0 ]; + m_flCurrent[ 1 ] = (m_flSetting[ 1 ] / m_flSetting[ 0 ]) * 0.5; + } + else + { + m_flCurrent[ 0 ] = m_flSetting[ 0 ]; + m_flCurrent[ 1 ] = 0.5; + } + } + else + { + m_flCurrent[ barnum ] = value; + } + + BoundValue(); + // FIXME: delay this until all sliders are set + if (!m_bPaired || barnum == 1) + redraw(); +} + +void mxExpressionSlider::setRange( int barnum, float min, float max, int ticks /*= 100*/ ) +{ + m_flMin[ barnum ] = min; + m_flMax[ barnum ] = max; + + Assert( m_flMax[ barnum ] > m_flMin[ barnum ] ); + + m_nTicks[ barnum ] = ticks; + + BoundValue(); + + redraw(); +} + +void mxExpressionSlider::setInfluence( float value ) +{ + bool bWasChecked = m_pInfluence->isChecked( ); + bool bNowChecked = value > 0.0f ? true : false; + if (bNowChecked != bWasChecked) + { + m_pInfluence->setChecked( bNowChecked ); + redraw(); + } +} + +float mxExpressionSlider::getRawValue( int barnum ) const +{ + return m_flCurrent[ barnum ]; +} + +float mxExpressionSlider::getValue( int barnum ) const +{ + float scale = 1.0; + if (m_bPaired) + { + // if it's paired, this is assuming that m_flCurrent[0] holds the max value, + // and m_flCurrent[1] is a weighting from 0 to 1, with 0.5 being even + if (barnum == 0 && m_flCurrent[ 1 ] > 0.5) + { + scale = (1.0 - m_flCurrent[ 1 ]) / 0.5; + } + else if (barnum == 1 && m_flCurrent[ 1 ] < 0.5) + { + scale = (m_flCurrent[ 1 ] / 0.5); + } + } + + return m_flCurrent[ 0 ] * scale; +} + +float mxExpressionSlider::getMinValue( int barnum ) const +{ + return m_flMin[ barnum ]; +} + +float mxExpressionSlider::getMaxValue( int barnum ) const +{ + return m_flMax[ barnum ]; +} + +float mxExpressionSlider::getInfluence( ) const +{ + return m_pInfluence->isChecked() ? 1.0f : 0.0f; +} + +void mxExpressionSlider::setEdited( int barnum, bool isEdited ) +{ + if (m_bIsEdited[ barnum ] == isEdited) + return; + + m_bIsEdited[ barnum ] = isEdited; + redraw(); +} + +bool mxExpressionSlider::isEdited( int barnum ) const +{ + return (m_bIsEdited[ barnum ]); +} + +void mxExpressionSlider::GetSliderRect( RECT& rc ) +{ + HWND wnd = (HWND)getHandle(); + if ( !wnd ) + return; + + GetClientRect( wnd, &rc ); + + if ( m_bDrawTitle ) + { + rc.left += m_nTitleWidth; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &rc - +//----------------------------------------------------------------------------- +void mxExpressionSlider::GetBarRect( RECT &rcBar ) +{ + RECT rc; + GetSliderRect( rc ); + + rcBar = rc; + + InflateRect( &rcBar, -10, 0 ); + rcBar.top += 5; + rcBar.bottom = rcBar.top + 2; + + int midy = ( rcBar.top + rcBar.bottom ) / 2; + rcBar.top = midy - 1; + rcBar.bottom = midy + 1; +} + + +void mxExpressionSlider::GetThumbRect( int barnum, RECT &rcThumb ) +{ + RECT rc; + GetSliderRect( rc ); + + RECT rcBar = rc; + GetBarRect( rcBar ); + + float frac = 0.0f; + + if ( m_flMax[ barnum ] - m_flMin[ barnum ] > 0 ) + { + frac = (m_flCurrent[ barnum ] - m_flMin[ barnum ]) / ( m_flMax[ barnum ] - m_flMin[ barnum ] ); + } + + int tickmark = (int)( frac * m_nTicks[ barnum ] + 0.5 ); + tickmark = min( m_nTicks[ barnum ], tickmark ); + tickmark = max( 0, tickmark ); + + int thumbwidth = 20; + int thumbheight = 14; + int xoffset = -thumbwidth / 2; + int yoffset = -thumbheight / 2 + 2; + + int leftedge = rcBar.left + (int)( (float)( rcBar.right - rcBar.left ) * (float)(tickmark) / (float)m_nTicks[ barnum ] ); + + rcThumb.left = leftedge + xoffset; + rcThumb.right = rcThumb.left + thumbwidth; + rcThumb.top = rcBar.top + yoffset; + rcThumb.bottom = rcThumb.top + thumbheight; +} + +void mxExpressionSlider::DrawBar( HDC& dc ) +{ + RECT rcBar; + + GetBarRect( rcBar ); + + HPEN oldPen; + + HPEN shadow; + HBRUSH face; + HPEN hilight; + + shadow = CreatePen( PS_SOLID, 1, GetSysColor( COLOR_3DSHADOW ) ); + hilight = CreatePen( PS_SOLID, 1, GetSysColor( COLOR_3DHIGHLIGHT ) ); + face = CreateSolidBrush( GetSysColor( COLOR_3DFACE ) ); + + oldPen = (HPEN)SelectObject( dc, hilight ); + + MoveToEx( dc, rcBar.left, rcBar.bottom, NULL ); + LineTo( dc, rcBar.left, rcBar.top ); + LineTo( dc, rcBar.right, rcBar.top ); + + SelectObject( dc, shadow ); + + LineTo( dc, rcBar.right, rcBar.bottom ); + LineTo( dc, rcBar.left, rcBar.bottom ); + + rcBar.left += 1; + //rcBar.right -= 1; + rcBar.top += 1; + rcBar.bottom -= 1; + + FillRect( dc, &rcBar, face ); + + SelectObject( dc, oldPen ); + + DeleteObject( face ); + DeleteObject( shadow ); + DeleteObject( hilight ); +} + +void mxExpressionSlider::DrawThumb( int barnum, HDC& dc ) +{ + RECT rcThumb; + + GetThumbRect( barnum, rcThumb ); + + // Draw it + + + HPEN oldPen; + + HPEN shadow; + HBRUSH face; + HPEN hilight; + + shadow = CreatePen( PS_SOLID, 1, GetSysColor( COLOR_3DDKSHADOW ) ); + hilight = CreatePen( PS_SOLID, 1, GetSysColor( COLOR_3DHIGHLIGHT ) ); + + switch ( barnum ) + { + default: + case MAGNITUDE_BAR: + { + float frac; + if (m_flCurrent[ barnum ] < 0) + { + frac = m_flCurrent[ barnum ] / m_flMin[ barnum ]; + } + else + { + frac = m_flCurrent[ barnum ] / m_flMax[ barnum ]; + } + frac = min( 1.0f, frac ); + frac = max( 0.0f, frac ); + + COLORREF clr = GetSysColor( COLOR_3DFACE ); + int r, g, b; + r = GetRValue( clr ); + g = GetRValue( clr ); + b = GetRValue( clr ); + + // boost colors + r = (int)( (1-frac) * b ); + b = min( 255, (int)(r + ( 255 - r ) * frac ) ); + g = (int)( (1-frac) * g ); + + face = CreateSolidBrush( RGB( r, g, b ) ); + } + break; + case BALANCE_BAR: + { + float frac = m_flCurrent[ barnum ]; + frac = min( 1.0f, frac ); + frac = max( 0.0f, frac ); + + COLORREF clr = GetSysColor( COLOR_3DFACE ); + int r, g, b; + r = GetRValue( clr ); + g = GetRValue( clr ); + b = GetRValue( clr ); + + // boost colors toward red if we are not at 0.5 + float boost = 2.0 * ( fabs( frac - 0.5f ) ); + + r = r + ( 255 - r ) * boost; + g = ( 1 - boost ) * g; + b = ( 1 - boost ) * b; + + face = CreateSolidBrush( RGB( r, g, b ) ); + } + break; + } + + //rcThumb.left += 1; + //rcThumb.right -= 1; + //rcThumb.top += 1; + //rcThumb.bottom -= 1; + + //FillRect( dc, &rcThumb, face ); + POINT region[3]; + int cPoints = 3; + + InflateRect( &rcThumb, -2, 0 ); + +// int height = rcThumb.bottom - rcThumb.top; +// int offset = height / 2 + 1; + int offset = 2; + + switch ( barnum ) + { + case MAGNITUDE_BAR: + default: + { + region[ 0 ].x = rcThumb.left; + region[ 0 ].y = rcThumb.top; + + region[ 1 ].x = rcThumb.right; + region[ 1 ].y = rcThumb.top; + + region[ 2 ].x = ( rcThumb.left + rcThumb.right ) / 2; + region[ 2 ].y = rcThumb.bottom - offset; + } + break; + case BALANCE_BAR: + { + region[ 0 ].x = ( rcThumb.left + rcThumb.right ) / 2; + region[ 0 ].y = rcThumb.top + offset; + + region[ 1 ].x = rcThumb.left; + region[ 1 ].y = rcThumb.bottom; + + region[ 2 ].x = rcThumb.right; + region[ 2 ].y = rcThumb.bottom; + + } + break; + } + + HRGN rgn = CreatePolygonRgn( region, cPoints, ALTERNATE ); + + int oldPF = SetPolyFillMode( dc, ALTERNATE ); + FillRgn( dc, rgn, face ); + SetPolyFillMode( dc, oldPF ); + + DeleteObject( rgn ); + + oldPen = (HPEN)SelectObject( dc, hilight ); + + MoveToEx( dc, region[0].x, region[0].y, NULL ); + LineTo( dc, region[1].x, region[1].y ); + SelectObject( dc, shadow ); + LineTo( dc, region[2].x, region[2].y ); + SelectObject( dc, hilight ); + LineTo( dc, region[0].x, region[0].y ); + + SelectObject( dc, oldPen ); + + DeleteObject( face ); + DeleteObject( shadow ); + DeleteObject( hilight ); +} + +void mxExpressionSlider::DrawTitle( HDC &dc ) +{ + if ( !m_bDrawTitle ) + return; + + HWND wnd = (HWND)getHandle(); + if ( !wnd ) + return; + + RECT rc; + GetClientRect( wnd, &rc ); + rc.right = m_nTitleWidth; + rc.left += 16; + + InflateRect( &rc, -5, -2 ); + + char sz[ 128 ]; + sprintf( sz, "%s", getLabel() ); + + HFONT fnt, oldfont; + + fnt = CreateFont( + -12 // H + , 0 // W + , 0 // Escapement + , 0 // Orient + , FW_NORMAL // Wt. (BOLD) + , 0 // Ital. + , 0 // Underl. + , 0 // SO + , ANSI_CHARSET // Charset + , OUT_TT_PRECIS // Out prec. + , CLIP_DEFAULT_PRECIS // Clip prec. + , PROOF_QUALITY // Qual. + , VARIABLE_PITCH | FF_DONTCARE // Pitch and Fam. + , "Arial" ); + + COLORREF oldColor; + + if (!isEdited( 0 )) + { + oldColor = SetTextColor( dc, GetSysColor( COLOR_BTNTEXT ) ); + } + else + { + oldColor = SetTextColor( dc, RGB( 255, 0, 0 ) ); + } + int oldMode = SetBkMode( dc, TRANSPARENT ); + oldfont = (HFONT)SelectObject( dc, fnt ); + + DrawText( dc, sz, -1, &rc, DT_NOPREFIX | DT_VCENTER | DT_SINGLELINE | DT_LEFT | DT_WORD_ELLIPSIS ); + + SelectObject( dc, oldfont ); + DeleteObject( fnt ); + SetBkMode( dc, oldMode ); + SetTextColor( dc, oldColor ); +} + +void mxExpressionSlider::redraw() +{ + HWND wnd = (HWND)getHandle(); + if ( !wnd ) + return; + + HDC finalDC = GetDC( wnd ); + if ( !finalDC ) + return; + + RECT rc; + GetClientRect( wnd, &rc ); + + int w = rc.right - rc.left; + int h = rc.bottom - rc.top; + + HDC dc = CreateCompatibleDC( finalDC ); + HBITMAP oldbm, bm; + + bm = CreateCompatibleBitmap( finalDC, w, h ); + + oldbm = (HBITMAP)SelectObject( dc, bm ); + + HBRUSH br = CreateSolidBrush( GetSysColor( COLOR_3DFACE ) ); + + FillRect( dc, &rc, br ); + + DeleteObject( br ); + + DrawTitle( dc ); + + DrawBar( dc ); + + // Draw slider + for ( int i = ( m_bPaired ? 1 : 0 ); i >= 0 ; i-- ) + { + DrawThumb( i, dc ); + } + + BitBlt( finalDC, 0, 0, w, h, dc, 0, 0, SRCCOPY ); + + SelectObject( dc, oldbm ); + + DeleteObject( bm ); + + DeleteDC( dc ); + + ReleaseDC( wnd, finalDC ); + ValidateRect( wnd, &rc ); +} + +void mxExpressionSlider::MoveThumb( int barnum, int xpos, bool finish ) +{ + RECT rcBar; + + GetBarRect( rcBar ); + + if ( xpos < rcBar.left ) + { + m_flCurrent[ barnum ] = m_flMin[ barnum ]; + } + else if ( xpos > rcBar.right ) + { + m_flCurrent[ barnum ] = m_flMax[ barnum ]; + } + else + { + float frac = (float)( xpos - rcBar.left ) / (float)( rcBar.right - rcBar.left ); + // snap slider to nearest "tick" so that it get drawn in the correct place + int curtick = (int)( frac * m_nTicks[ 0 ] + 0.5); + m_flCurrent[ barnum ] = m_flMin[ barnum ] + ((float)curtick / (float)m_nTicks[ 0 ]) * (m_flMax[ barnum ] - m_flMin[ barnum ]); + } + + // update equivalent setting + m_flSetting[ 0 ] = getValue( 0 ); + m_flSetting[ 1 ] = getValue( 1 ); + + m_bIsEdited[ 0 ] = true; + m_bIsEdited[ 1 ] = true; + + // Send message to parent + HWND parent = (HWND)( getParent() ? getParent()->getHandle() : NULL ); + if ( parent ) + { + LPARAM lp; + WPARAM wp; + + wp = MAKEWPARAM( finish ? SB_ENDSCROLL : SB_THUMBPOSITION, barnum ); + lp = (long)getHandle(); + + SendMessage( parent, WM_HSCROLL, wp, lp ); + } + + BoundValue(); + redraw(); +} + +bool mxExpressionSlider::PaintBackground( void ) +{ + return false; +} + +int mxExpressionSlider::handleEvent( mxEvent *event ) +{ + int iret = 0; + switch ( event->event ) + { + case mxEvent::Action: + { + iret = 1; + switch ( event->action ) + { + default: + iret = 0; + break; + case IDC_INFLUENCE: + { + SetFocus( (HWND)getHandle() ); + + setEdited( 0, false ); + setEdited( 1, false ); + + // Send message to parent + HWND parent = (HWND)( getParent() ? getParent()->getHandle() : NULL ); + if ( parent ) + { + LPARAM lp; + WPARAM wp; + + wp = MAKEWPARAM( SB_ENDSCROLL, m_nCurrentBar ); + lp = (long)getHandle(); + + SendMessage( parent, WM_HSCROLL, wp, lp ); + } + break; + } + } + } + break; + case mxEvent::MouseDown: + { + SetFocus( (HWND)getHandle() ); + + if ( !m_bDraggingThumb ) + { + RECT rcThumb; + POINT pt; + + pt.x = (short)event->x; + pt.y = (short)event->y; + + m_nCurrentBar = ( event->buttons & mxEvent::MouseRightButton ) ? BALANCE_BAR : MAGNITUDE_BAR; + GetThumbRect( m_nCurrentBar, rcThumb ); + + if ( PtInRect( &rcThumb, pt ) ) + { + m_bDraggingThumb = true; + } + + // Snap position if they didn't click on the thumb itself +#if 0 + else + { + m_bDraggingThumb = true; + MoveThumb( m_nCurrentBar, (short)event->x, false ); + } +#endif + } + iret = 1; + } + break; + case mxEvent::MouseDrag: + case mxEvent::MouseMove: + { + if ( m_bDraggingThumb ) + { + m_pInfluence->setChecked( true ); + MoveThumb( m_nCurrentBar, (short)event->x, false ); + iret = 1; + } + } + break; + case mxEvent::MouseUp: + { + if ( m_bDraggingThumb ) + { + m_pInfluence->setChecked( true ); + m_bDraggingThumb = false; + MoveThumb( m_nCurrentBar, (short)event->x, true ); + m_nCurrentBar = 0; + } + iret = 1; + } + break; + case mxEvent::KeyDown: + { + if ( event->key == VK_RETURN || + event->key == 'S' ) + { + // See if there is an event in the flex animation window + g_pExpressionTool->OnSetSingleKeyFromFlex( getLabel() ); + } + } + break; + } + + return iret; +} diff --git a/utils/hlfaceposer/mxexpressionslider.h b/utils/hlfaceposer/mxexpressionslider.h new file mode 100644 index 0000000..6ea7ef1 --- /dev/null +++ b/utils/hlfaceposer/mxexpressionslider.h @@ -0,0 +1,91 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef MXEXPRESSIONSLIDER_H +#define MXEXPRESSIONSLIDER_H +#ifdef _WIN32 +#pragma once +#endif + +#include <mxtk/mxWindow.h> +#include <mxtk/mxCheckBox.h> + +#define IDC_INFLUENCE 1000 + +class mxExpressionSlider : public mxWindow +{ +public: + enum + { + MAGNITUDE_BAR = 0, + BALANCE_BAR = 1, + + NUMBARS = 2 + }; + + mxExpressionSlider (mxWindow *parent, int x, int y, int w, int h, int id ); + ~mxExpressionSlider( void ); + + void SetTitleWidth( int width ); + void SetDrawTitle( bool drawTitle ); + + void SetMode( bool paired ); + + void setValue ( int barnum, float value); + void setRange ( int barnum, float min, float max, int ticks = 100); + void setInfluence ( float value ); + void setEdited( bool isEdited ); + + // ACCESSORS + float getRawValue( int barnum ) const; + + float getValue( int barnum ) const; + float getMinValue( int barnum ) const; + float getMaxValue( int barnum ) const; + float getInfluence( ) const; + + void setEdited( int barnum, bool isEdited ); + bool isEdited( int barnum ) const; + + virtual void redraw(); + virtual bool PaintBackground( void ); + + virtual int handleEvent (mxEvent *event); + +private: + void BoundValue( void ); + + void GetSliderRect( RECT& rc ); + void GetBarRect( RECT &rc ); + void GetThumbRect( int barnum, RECT &rc ); + + void DrawBar( HDC& dc ); + void DrawThumb( int barnum, HDC& dc ); + void DrawTitle( HDC &dc ); + + void MoveThumb( int barnum, int xpos, bool finish ); + + int m_nTitleWidth; + bool m_bDrawTitle; + + float m_flMin[ NUMBARS ], m_flMax[ NUMBARS ]; + int m_nTicks[ NUMBARS ]; + float m_flCurrent[ NUMBARS ]; // slider location (amount/balance) + + float m_flSetting[ NUMBARS ]; // paired flex input values (right/left) + bool m_bIsEdited[ NUMBARS ]; // paired flex input values (right/left) + + bool m_bDraggingThumb; + int m_nCurrentBar; + + bool m_bPaired; + + mxCheckBox *m_pInfluence; +}; + + +#endif // MXEXPRESSIONSLIDER_H diff --git a/utils/hlfaceposer/mxexpressiontab.cpp b/utils/hlfaceposer/mxexpressiontab.cpp new file mode 100644 index 0000000..21efec0 --- /dev/null +++ b/utils/hlfaceposer/mxexpressiontab.cpp @@ -0,0 +1,62 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "hlfaceposer.h" +#include "mxExpressionTab.h" +#include "mdlviewer.h" +#include "expressions.h" + +mxExpressionTab *g_pExpressionClass = 0; + +//----------------------------------------------------------------------------- +// Purpose: Right click context menu +// Input : mx - +// my - +//----------------------------------------------------------------------------- +void mxExpressionTab::ShowRightClickMenu( int mx, int my ) +{ + if ( !g_MDLViewer ) + return; + + mxPopupMenu *pop = new mxPopupMenu(); + Assert( pop ); + + pop->add( "New...", IDC_EXPRESSIONS_NEW ); + pop->addSeparator (); + pop->add( "Load...", IDC_EXPRESSIONS_LOAD ); + pop->add( "Save", IDC_EXPRESSIONS_SAVE ); + pop->addSeparator (); + pop->add( "Export to VFE", IDC_EXPRESSIONS_EXPORT ); + pop->addSeparator (); + if ( m_nSelected != -1 ) + { + pop->add( "Close class", IDC_EXPRESSIONS_CLOSE ); + } + pop->add( "Close all classes", IDC_EXPRESSIONS_CLOSEALL ); + pop->addSeparator(); + pop->add( "Recreate all bitmaps", IDC_EXPRESSIONS_REDOBITMAPS ); + + // Convert click position + POINT pt; + pt.x = mx; + pt.y = my; + ClientToScreen( (HWND)getHandle(), &pt ); + ScreenToClient( (HWND)g_MDLViewer->getHandle(), &pt ); + + // Convert coordinate space + pop->popup( g_MDLViewer, pt.x, pt.y ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int mxExpressionTab::getSelectedIndex () const +{ + // Convert based on override index + return m_nSelected; +} diff --git a/utils/hlfaceposer/mxexpressiontab.h b/utils/hlfaceposer/mxexpressiontab.h new file mode 100644 index 0000000..2eea9a3 --- /dev/null +++ b/utils/hlfaceposer/mxexpressiontab.h @@ -0,0 +1,29 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#ifndef MXEXPRESSIONTAB_H +#define MXEXPRESSIONTAB_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tabwindow.h" + +class mxExpressionTab : public CTabWindow +{ +public: + mxExpressionTab( mxWindow *parent, int x, int y, int w, int h, int id = 0, int style = 0 ) + : CTabWindow( parent, x, y, w, h, id, style ) + { + } + + virtual void ShowRightClickMenu( int mx, int my ); + virtual int getSelectedIndex () const; +}; + +extern mxExpressionTab *g_pExpressionClass; + +#endif // MXEXPRESSIONTAB_H diff --git a/utils/hlfaceposer/mxexpressiontray.cpp b/utils/hlfaceposer/mxexpressiontray.cpp new file mode 100644 index 0000000..57bdd6d --- /dev/null +++ b/utils/hlfaceposer/mxexpressiontray.cpp @@ -0,0 +1,1212 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "hlfaceposer.h" +#include <windows.h> +#include <stdio.h> +#include <mxtk/mxWindow.h> +#include <mxtk/mxScrollBar.h> +#include "mxexpressiontray.h" +#include "expressions.h" +#include "expclass.h" +#include "ControlPanel.h" +#include "FlexPanel.h" +#include <mxtk/mxPopupMenu.h> +#include "ChoreoView.h" +#include "StudioModel.h" +#include "ExpressionProperties.h" +#include "InputProperties.h" +#include "ViewerSettings.h" +#include "mxExpressionTab.h" +#include "choreowidgetdrawhelper.h" +#include "ExpressionTool.h" +#include "faceposer_models.h" +#include "tier0/icommandline.h" +#include "filesystem.h" + +#define MAX_THUMBNAILSIZE 256 +#define MIN_THUMBNAILSIZE 64 +#define THUMBNAIL_SIZE_STEP 4 +#define DEFAULT_THUMBNAIL_SIZE 128 +#define TOP_GAP 45 + +mxExpressionTray *g_pExpressionTrayTool = 0; + +mxExpressionTray::mxExpressionTray( mxWindow *parent, int id /*=0*/ ) +: IFacePoserToolWindow( "ExpressionTrayTool", "Expressions" ), mxWindow( parent, 0, 0, 0, 0, "ExpressionTrayTool", id ) +{ + setId( id ); + + m_nTopOffset = 0; + slScrollbar = new mxScrollbar( this, 0, 0, 18, 100, IDC_TRAYSCROLL, mxScrollbar::Vertical ); + + m_nLastNumExpressions = -1; + + m_nGranularity = 10; + + m_nPrevCell = -1; + m_nCurCell = -1; + + m_nClickedCell = -1; + + m_nButtonSquare = 16; + + m_nGap = 4; + m_nDescriptionHeight = 34; + m_nSnapshotWidth = g_viewerSettings.thumbnailsize; + m_nSnapshotWidth = max( MIN_THUMBNAILSIZE, m_nSnapshotWidth ); + m_nSnapshotWidth = min( MAX_THUMBNAILSIZE, m_nSnapshotWidth ); + + g_viewerSettings.thumbnailsize = m_nSnapshotWidth; + + m_nSnapshotHeight = m_nSnapshotWidth + m_nDescriptionHeight; + + m_pButtons = NULL; + + m_nPreviousExpressionCount = -1; + + m_bDragging = false; + m_nDragCell = -1; + + CreateButtons(); + + g_pExpressionClass = new mxExpressionTab( this, 5, 5, 500, 20, IDC_EXPRESSIONCLASS ); + + m_pABButton = new mxButton( this, 520, 8, 50, 18, "A/B", IDC_AB ); + m_pThumbnailIncreaseButton = new mxButton( this, 0, 0, 18, 18, "+", IDC_THUMBNAIL_INCREASE ); + m_pThumbnailDecreaseButton = new mxButton( this, 0, 0, 18, 18, "-", IDC_THUMBNAIL_DECREASE ); + +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : mxExpressionTray::~mxExpressionTray +//----------------------------------------------------------------------------- +mxExpressionTray::~mxExpressionTray ( void ) +{ + DeleteAllButtons(); + g_pExpressionTrayTool = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : cellsize - +//----------------------------------------------------------------------------- +void mxExpressionTray::SetCellSize( int cellsize ) +{ + m_nSnapshotWidth = cellsize; + m_nSnapshotHeight = cellsize + m_nDescriptionHeight; + + DeleteAllButtons(); + CreateButtons(); + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void mxExpressionTray::Deselect( void ) +{ + CExpClass *active = expressions->GetActiveClass(); + if ( active ) + { + for ( int i = 0 ; i < active->GetNumExpressions(); i++ ) + { + CExpression *exp = active->GetExpression( i ); + if ( exp ) + { + exp->SetSelected( false ); + } + } + } + + m_nCurCell = m_nPrevCell = -1; + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : exp - +//----------------------------------------------------------------------------- +void mxExpressionTray::Select( int exp, bool deselect /*=true*/ ) +{ + int oldcell = m_nCurCell; + + if ( deselect ) + { + Deselect(); + } + + m_nPrevCell = oldcell; + m_nCurCell = exp; + + if ( m_nCurCell >= 0 ) + { + CExpClass *active = expressions->GetActiveClass(); + if ( active ) + { + CExpression *exp = active->GetExpression( m_nCurCell ); + if ( exp ) + { + exp->SetSelected( true ); + } + } + } + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *btn - +//----------------------------------------------------------------------------- +void mxExpressionTray::AddButton( const char *name, const char *tooltip, const char *bitmap, ETMEMBERFUNC pfnCallback, + bool active, int x, int y, int w, int h ) +{ + mxETButton *btn = new mxETButton; + strcpy( btn->m_szName, name ); + strcpy( btn->m_szToolTip, tooltip ); + btn->m_bActive = active; + btn->m_rc.left = x; + btn->m_rc.top = y; + btn->m_rc.right = x + w; + btn->m_rc.bottom = y + h; + + btn->m_pImage = new mxbitmapdata_t; + Assert( btn->m_pImage ); + btn->m_pImage->valid = false; + LoadBitmapFromFile( bitmap, *btn->m_pImage ); + + btn->m_fnCallback = pfnCallback; + + btn->next = m_pButtons; + m_pButtons = btn; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void mxExpressionTray::CreateButtons( void ) +{ + int x = m_nSnapshotWidth - 2 * ( m_nButtonSquare + 4 ); + int y = 4; + + AddButton( "undo", "Undo", "gfx/hlfaceposer/undo.bmp", &mxExpressionTray::ET_Undo, true, x, y, m_nButtonSquare, m_nButtonSquare ); + + x += ( m_nButtonSquare + 4 ); + + AddButton( "redo", "Redo", "gfx/hlfaceposer/redo.bmp", &mxExpressionTray::ET_Redo, true, x, y, m_nButtonSquare, m_nButtonSquare ); +} + +void mxExpressionTray::ActivateButton( const char *name, bool active ) +{ + mxETButton *btn = FindButton( name ); + if ( !name ) + return; + + btn->m_bActive = active; +} + +mxExpressionTray::mxETButton *mxExpressionTray::FindButton( const char *name ) +{ + mxETButton *p = m_pButtons; + while ( p ) + { + if ( !stricmp( p->m_szName, name ) ) + return p; + p = p->next; + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void mxExpressionTray::DeleteAllButtons( void ) +{ + mxETButton *p = m_pButtons, *n; + while ( p ) + { + n = p->next; + delete p->m_pImage; + delete p; + p = n; + } + m_pButtons = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : x - +// y - +// Output : mxExpressionTray::mxETButton +//----------------------------------------------------------------------------- +mxExpressionTray::mxETButton *mxExpressionTray::GetItemUnderCursor( int x, int y ) +{ + // Convert to cell space + int cell = GetCellUnderPosition( x, y ); + if ( cell == -1 ) + { + return NULL; + } + + // Cell is off screen? + int cx, cy, cw, ch; + if ( !ComputeRect( cell, cx, cy, cw, ch ) ) + { + return NULL; + } + + + mxETButton *p = m_pButtons; + while ( p ) + { + if ( p->m_bActive && + x >= cx && + x <= cx + cw && + y >= cy && + y <= cy + ch ) + { + // In-side cell + int cellx = x - cx; + int celly = y - cy; + + if ( cellx >= p->m_rc.left && + cellx <= p->m_rc.right && + celly >= p->m_rc.top && + celly <= p->m_rc.bottom ) + { + return p; + } + } + p = p->next; + } + + return NULL; +} + +void mxExpressionTray::DrawButton( CChoreoWidgetDrawHelper& helper, int cell, mxETButton *btn ) +{ + if ( !btn || !btn->m_pImage || !btn->m_pImage->valid ) + return; + + if ( !btn->m_bActive ) + return; + + int x, y, w, h; + if ( !ComputeRect( cell, x, y, w, h ) ) + return; + + x += btn->m_rc.left; + y += btn->m_rc.top; + w = btn->m_rc.right - btn->m_rc.left; + h = btn->m_rc.bottom - btn->m_rc.top; + + HDC dc = helper.GrabDC(); + + DrawBitmapToDC( dc, x, y, w, h, *btn->m_pImage ); + helper.DrawOutlinedRect( RGB( 170, 170, 170 ), PS_SOLID, 1, x, y, x + w, y + h ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int mxExpressionTray::ComputePixelsNeeded( void ) +{ + CExpClass *active = expressions->GetActiveClass(); + if ( !active ) + 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 = ( ( active->GetNumExpressions() + colsperrow - 1 ) / colsperrow ); + return rowsneeded * ( m_nSnapshotHeight + m_nGap ) + m_nGap + TOP_GAP + GetCaptionHeight(); +} + +bool mxExpressionTray::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 mxExpressionTray::DrawExpressionFocusRect( CChoreoWidgetDrawHelper& helper, int x, int y, int w, int h, COLORREF clr ) +{ + helper.DrawOutlinedRect( clr, PS_SOLID, 4, x, y, x + w, y + h ); +} + +void mxExpressionTray::DrawExpressionDescription( CChoreoWidgetDrawHelper& helper, int x, int y, int w, int h, const char *expressionname, const char *description ) +{ + 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, RGB( 0, 0, 0 ), textRect, "%s", expressionname ); + +// DrawText( hdc, expressionname, strlen( expressionname ), &textRect, DT_NOPREFIX | DT_SINGLELINE | DT_LEFT | DT_VCENTER | DT_WORD_ELLIPSIS ); + + OffsetRect( &textRect, 0, textheight ); + + helper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 63, 63, 63 ), textRect, "%s", description ); + +// DrawText( hdc, description, strlen( description ), &textRect, DT_NOPREFIX | DT_SINGLELINE | DT_LEFT | DT_VCENTER | DT_WORD_ELLIPSIS ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dc - +// current - +// rcx - +// rcy - +// rcw - +// rch - +//----------------------------------------------------------------------------- +void mxExpressionTray::DrawDirtyFlag( CChoreoWidgetDrawHelper& helper, CExpression *current, int rcx, int rcy, int rcw, int rch ) +{ + // Not dirty + if ( !current || ( !current->CanUndo() && !current->GetDirty() ) ) + return; + + int fontsize = 14; + + RECT textRect; + textRect.left = rcx + 5; + textRect.right = rcx + rcw; + textRect.top = rcy + 5; + textRect.bottom = textRect.top + fontsize + 2; + + helper.DrawColoredText( "Arial", fontsize, FW_NORMAL, RGB( 100, 240, 255 ), textRect, "*" ); +} + +bool mxExpressionTray::PaintBackground( void ) +{ + redraw(); + return false; +} + +void mxExpressionTray::DrawThumbNail( CExpClass *active, CExpression *current, CChoreoWidgetDrawHelper& helper, int rcx, int rcy, int rcw, int rch, int c, int selected, bool updateselection ) +{ + if ( !current ) + return; + + HDC dc = helper.GrabDC(); + + helper.DrawFilledRect( GetSysColor( COLOR_BTNFACE ), rcx, rcy, rcw + rcx, rch + rcy ); + + if ( current->m_Bitmap[ models->GetActiveModelIndex() ].valid ) + { + DrawBitmapToDC( dc, rcx, rcy, rcw, rch - m_nDescriptionHeight, current->m_Bitmap[ models->GetActiveModelIndex() ] ); + helper.DrawOutlinedRect( RGB( 127, 127, 127 ), PS_SOLID, 1, rcx, rcy, rcx + rcw, rcy + rch - m_nDescriptionHeight ); + } + + DrawDirtyFlag( helper, current, rcx, rcy, rcw, rch ); + + DrawExpressionDescription( helper, rcx, rcy, rcw, rch, current->name, current->description ); + + if ( c == selected ) + { + DrawExpressionFocusRect( helper, rcx, rcy, rcw, rch - m_nDescriptionHeight, RGB( 255, 100, 63 ) ); + + if ( updateselection ) + { + m_nPrevCell = -1; + m_nCurCell = c; + } + + if ( current->CanUndo() || current->CanRedo() ) + { + if ( current->CanUndo() ) + { + DrawButton( helper, c, FindButton( "undo" ) ); + } + + if ( current->CanRedo() ) + { + DrawButton( helper, c, FindButton( "redo" ) ); + } + + RECT rc; + rc.left = rcx + rcw - 2 * ( m_nButtonSquare + 4 ); + rc.top = rcy + m_nButtonSquare + 6; + rc.right = rc.left + 2 * ( m_nButtonSquare + 4 ); + rc.bottom = rc.top + 15; + + helper.DrawColoredText( "Arial", 9, FW_NORMAL, RGB( 200, 200, 200 ), rc, + "%i/%i", current->UndoCurrent(), current->UndoLevels() ); + } + + } + else + { + if ( current->GetSelected() ) + { + DrawExpressionFocusRect( helper, rcx, rcy, rcw, rch - m_nDescriptionHeight, RGB( 127, 127, 220 ) ); + } + } +} + +void mxExpressionTray::redraw() +{ + if ( !ToolCanDraw() ) + return; + + bool updateSelection = false; + + CExpClass *active = expressions->GetActiveClass(); + if ( active && active->GetNumExpressions() != m_nPreviousExpressionCount ) + { + m_nTopOffset = 0; + + RepositionSlider(); + m_nPreviousExpressionCount = active->GetNumExpressions(); + } + + CChoreoWidgetDrawHelper helper( this, GetSysColor( COLOR_BTNFACE ) ); + HandleToolRedraw( helper ); + + int w, h; + w = w2(); + h = h2(); + + if ( active ) + { + RECT clipRect; + helper.GetClientRect( clipRect ); + + clipRect.top += TOP_GAP + GetCaptionHeight(); + + helper.StartClipping( clipRect ); + + if ( m_nLastNumExpressions != active->GetNumExpressions() ) + { + m_nTopOffset = 0; + m_nLastNumExpressions = active->GetNumExpressions(); + RepositionSlider(); + updateSelection = true; + } + + int selected = active->GetSelectedExpression(); + + int rcx, rcy, rcw, rch; + + int c = 0; + while ( c < active->GetNumExpressions() ) + { + if ( !ComputeRect( c, rcx, rcy, rcw, rch ) ) + { + c++; + continue; + } + + CExpression *current = active->GetExpression( c ); + if ( !current ) + break; + + DrawThumbNail( active, current, helper, rcx, rcy, rcw, rch, c, selected, updateSelection ); + + c++; + } + + helper.StopClipping(); + + } + else + { + + RECT rc; + helper.GetClientRect( rc ); + + // Arial 36 normal + char sz[ 256 ]; + sprintf( sz, "No expression file loaded" ); + + int pointsize = 18; + + int textlen = helper.CalcTextWidth( "Arial", pointsize, FW_NORMAL, sz ); + + RECT rcText; + rcText.top = ( rc.bottom - rc.top ) / 2 - pointsize / 2; + rcText.bottom = rcText.top + pointsize + 10; + int fullw = rc.right - rc.left; + + rcText.left = rc.left + ( fullw - textlen ) / 2; + rcText.right = rcText.left + textlen; + + helper.DrawColoredText( "Arial", pointsize, FW_NORMAL, RGB( 80, 80, 80 ), rcText, sz ); + } + + +// ValidateRect( (HWND)getHandle(), &rc ); +} + +int mxExpressionTray::GetCellUnderPosition( int x, int y ) +{ + CExpClass *active = expressions->GetActiveClass(); + if ( !active ) + return -1; + + int rcx, rcy, rcw, rch; + int c = 0; + while ( c < active->GetNumExpressions() ) + { + 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 mxExpressionTray::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 mxExpressionTray::AB( void ) +{ + if ( m_nPrevCell == -1 && m_nCurCell == -1 ) + return; + + CExpClass *active = expressions->GetActiveClass(); + if ( !active ) + return; + + if ( m_nPrevCell >= 0 && m_nPrevCell < active->GetNumExpressions() ) + { + active->SelectExpression( m_nPrevCell ); + } +} + +int mxExpressionTray::CountSelected( void ) +{ + CExpClass *active = expressions->GetActiveClass(); + if ( !active ) + return 0; + + int c = 0; + for ( int i = 0; i < active->GetNumExpressions(); i++ ) + { + CExpression *exp = active->GetExpression( i ); + if ( !exp ) + continue; + + if ( exp->GetSelected() ) + { + c++; + } + } + + return c; +} + +void mxExpressionTray::SetClickedCell( int cell ) +{ + m_nClickedCell = cell; +} + +void mxExpressionTray::ShowRightClickMenu( int mx, int my ) +{ + CExpClass *active = expressions->GetActiveClass(); + if ( !active ) + return; + + mxPopupMenu *pop = new mxPopupMenu(); + Assert( pop ); + + CExpression *exp = NULL; + if ( m_nClickedCell != -1 ) + { + exp = active->GetExpression( m_nClickedCell ); + } + + pop->add( "New Expression...", IDC_CONTEXT_NEWEXP ); + if ( exp ) + { + pop->addSeparator(); + pop->add( va( "Edit '%s'...", exp->name ), IDC_CONTEXT_EDITEXP ); + pop->add( va( "Save '%s'", exp->name ), IDC_CONTEXT_SAVEEXP ); + + if ( exp->CanUndo() || exp->CanRedo() ) + { + pop->add( va( "Revert '%s'", exp->name ), IDC_CONTEXT_REVERT ); + } + pop->addSeparator(); + pop->add( va( "Delete '%s'", exp->name ), IDC_CONTEXT_DELETEXP ); + pop->addSeparator(); + pop->add( va( "Re-create thumbnail for '%s'", exp->name ), IDC_CONTEXT_CREATEBITMAP ); + } + + pop->popup( this, mx, my ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void mxExpressionTray::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 mxExpressionTray::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: + iret = 0; + break; + case IDC_EXPRESSIONCLASS: + { + int index = g_pExpressionClass->getSelectedIndex(); + if ( index >= 0 ) + { + CExpClass *current = expressions->GetClass( index ); + if ( current ) + { + // Switch classname + expressions->ActivateExpressionClass( current ); + current->SelectExpression( 0 ); + } + } + } + break; + case IDC_CONTEXT_NEWEXP: + g_pFlexPanel->NewExpression(); + break; + case IDC_CONTEXT_EDITEXP: + if ( m_nClickedCell != -1 ) + { + g_pFlexPanel->EditExpression(); + } + break; + case IDC_CONTEXT_REVERT: + if ( m_nClickedCell != -1 ) + { + g_pFlexPanel->RevertExpression( m_nClickedCell ); + } + break; + case IDC_CONTEXT_SAVEEXP: + if ( m_nClickedCell != -1 ) + { + g_pFlexPanel->SaveExpression( m_nClickedCell ); + } + break; + case IDC_CONTEXT_DELETEXP: + if ( m_nClickedCell != -1 ) + { + g_pControlPanel->DeleteExpression( m_nClickedCell ); + } + break; + case IDC_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: + { + AB(); + } + break; + case IDC_THUMBNAIL_INCREASE: + { + ThumbnailIncrease(); + } + break; + case IDC_THUMBNAIL_DECREASE: + { + ThumbnailDecrease(); + } + break; + case IDC_CONTEXT_CREATEBITMAP: + { + if ( m_nClickedCell >= 0 ) + { + CExpClass *active = expressions->GetActiveClass(); + if ( active ) + { + CExpression *exp = active->GetExpression( m_nClickedCell ); + if ( exp ) + { + active->SelectExpression( m_nClickedCell ); + exp->CreateNewBitmap( models->GetActiveModelIndex() ); + redraw(); + } + } + } + } + break; + } + break; + } + case mxEvent::MouseDown: + { + if ( !( event->buttons & mxEvent::MouseRightButton ) ) + { + // Figure out cell # + int cell = GetCellUnderPosition( event->x, event->y ); + CExpClass *active = expressions->GetActiveClass(); + if ( active ) + { + + if ( cell == m_nCurCell && cell >= 0 && cell < active->GetNumExpressions() ) + { + mxETButton *btn = GetItemUnderCursor( event->x, event->y ); + if ( btn && btn->m_fnCallback ) + { + (this->*(btn->m_fnCallback))( cell ); + return iret; + } + } + + if ( cell >= 0 && cell < active->GetNumExpressions() ) + { + active->SelectExpression( cell, event->modifiers & mxEvent::KeyShift ? false : true ); + + int cx, cy, cw, ch; + if ( ComputeRect( cell, cx, cy, cw, ch ) ) + { + 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(); + } + } + else + { + Deselect(); + active->DeselectExpression(); + 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; + } + + int cell = GetCellUnderPosition( event->x, event->y ); + CExpClass *active = expressions->GetActiveClass(); + + if ( m_bDragging ) + { + DrawFocusRect(); + m_bDragging = false; + // See if we let go on top of the choreo view + + if ( active ) + { + // 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 + CExpression *exp = active->GetExpression( m_nDragCell ); + if ( exp && maybeTool ) + { + if ( IsWindowOrChild( g_pChoreoView, maybeTool ) ) + { + if ( g_pChoreoView->CreateExpressionEvent( pt.x, pt.y, active, exp ) ) + { + return iret; + } + } + + if ( IsWindowOrChild( g_pExpressionTool, maybeTool ) ) + { + if ( g_pExpressionTool->SetFlexAnimationTrackFromExpression( pt.x, pt.y, active, exp ) ) + { + return iret; + } + } + } + } + } + + if ( active ) + { + // Over a new cell + if ( cell >= 0 && + cell < active->GetNumExpressions() && + cell != m_nCurCell && + m_nCurCell != -1 ) + { + // Swap cells + CExpression *exp = active->GetExpression( m_nCurCell ); + if ( exp ) + { + active->SwapExpressionOrder( m_nCurCell, cell ); + active->SetDirty( true ); + active->SelectExpression( cell ); + } + } + } + } + break; + case mxEvent::Size: + { + int width = w2(); + + int ch = GetCaptionHeight(); + + g_pExpressionClass->setBounds( 5, 5 + ch, width - 120, 20 ); + + m_pABButton->setBounds( width - 60, 4 + ch, 60, 16 ); + m_pThumbnailIncreaseButton->setBounds( width - 60 - 40, 4 + ch, 16, 16 ); + m_pThumbnailDecreaseButton->setBounds( width - 60 - 20, 4 + ch, 16, 16 ); + + 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; + }; + + if ( iret ) + { + SetActiveTool( this ); + } + return iret; +} + +void mxExpressionTray::ET_Undo( int cell ) +{ + g_pControlPanel->UndoExpression( cell ); +} + +void mxExpressionTray::ET_Redo( int cell ) +{ + g_pControlPanel->RedoExpression( cell ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void mxExpressionTray::ThumbnailIncrease( void ) +{ + if ( m_nSnapshotWidth + THUMBNAIL_SIZE_STEP <= MAX_THUMBNAILSIZE ) + { + m_nSnapshotWidth += THUMBNAIL_SIZE_STEP; + g_viewerSettings.thumbnailsize = m_nSnapshotWidth; + m_nSnapshotHeight = m_nSnapshotWidth + m_nDescriptionHeight; + + Con_Printf( "Thumbnail size %i x %i\n", m_nSnapshotWidth, m_nSnapshotWidth ); + + redraw(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void mxExpressionTray::ThumbnailDecrease( void ) +{ + if ( m_nSnapshotWidth - THUMBNAIL_SIZE_STEP >= MIN_THUMBNAILSIZE ) + { + m_nSnapshotWidth -= THUMBNAIL_SIZE_STEP; + g_viewerSettings.thumbnailsize = m_nSnapshotWidth; + m_nSnapshotHeight = m_nSnapshotWidth + m_nDescriptionHeight; + + Con_Printf( "Thumbnail size %i x %i\n", m_nSnapshotWidth, m_nSnapshotWidth ); + + redraw(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void mxExpressionTray::RestoreThumbnailSize( void ) +{ + m_nSnapshotWidth = g_viewerSettings.thumbnailsize; + m_nSnapshotWidth = max( MIN_THUMBNAILSIZE, m_nSnapshotWidth ); + m_nSnapshotWidth = min( MAX_THUMBNAILSIZE, m_nSnapshotWidth ); + + g_viewerSettings.thumbnailsize = m_nSnapshotWidth; + + m_nSnapshotHeight = m_nSnapshotWidth + m_nDescriptionHeight; + + redraw(); +} + +void mxExpressionTray::ReloadBitmaps( void ) +{ + CExpClass *cl; + int c = expressions->GetNumClasses(); + EnableStickySnapshotMode(); + for ( int i = 0 ; i < c; i++ ) + { + cl = expressions->GetClass( i ); + if ( !cl ) + continue; + + cl->ReloadBitmaps(); + } + DisableStickySnapshotMode(); + redraw(); +} + +bool IsUsingPerPlayerExpressions() +{ + bool bPerPlayerExpressions = false; + if ( CommandLine()->CheckParm( "-perplayerexpressions" ) ) + { + bPerPlayerExpressions = true; + } + else + { + // Returns the search path, each path is separated by ;s. Returns the length of the string returned + char pSearchPath[2048]; + if ( g_pFullFileSystem->GetSearchPath( "GAME", false, pSearchPath, sizeof(pSearchPath) ) ) + { + Q_FixSlashes( pSearchPath ); + if ( Q_stristr( pSearchPath, "\\tf" ) ) + { + bPerPlayerExpressions = true; + } + } + } + return bPerPlayerExpressions; +} + +void mxExpressionTray::OnModelChanged() +{ + if ( IsUsingPerPlayerExpressions() ) + { + Msg( "Closing current phoneme set\n" ); + + if ( !g_pControlPanel->Closeall() ) + return; + + // See if per-model overrides exist for this model + char fn[ MAX_PATH ]; + Q_snprintf( fn, sizeof( fn ), "expressions/%s/phonemes/phonemes.txt", models->GetActiveModelName() ); + + // Load appropriate classes + char rootDir[ MAX_PATH ]; + Q_snprintf( rootDir, sizeof( rootDir ), "%s/phonemes/", models->GetActiveModelName() ); + FacePoser_SetPhonemeRootDir( rootDir ); + + FacePoser_EnsurePhonemesLoaded(); + } + + ReloadBitmaps(); + RestoreThumbnailSize(); +}
\ No newline at end of file diff --git a/utils/hlfaceposer/mxexpressiontray.h b/utils/hlfaceposer/mxexpressiontray.h new file mode 100644 index 0000000..e59520c --- /dev/null +++ b/utils/hlfaceposer/mxexpressiontray.h @@ -0,0 +1,161 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#if !defined( MXEXPRESSIONTRAY_H ) +#define MXEXPRESSIONTRAY_H +#ifdef _WIN32 +#pragma once +#endif + +#define IDC_TRAYSCROLL 1001 +#define IDC_CONTEXT_NEWEXP 1002 +#define IDC_CONTEXT_EDITEXP 1003 +#define IDC_CONTEXT_SAVEEXP 1004 +#define IDC_CONTEXT_DELETEXP 1005 +#define IDC_CONTEXT_REVERT 1012 +#define IDC_AB 1014 +#define IDC_THUMBNAIL_INCREASE 1015 +#define IDC_THUMBNAIL_DECREASE 1016 +#define IDC_CONTEXT_CREATEBITMAP 1017 + +#define COLOR_TRAYBACKGROUND RGB( 240, 240, 220 ) + +class ControlPanel; +class FlexPanel; +class mxScrollbar; +class mxCheckBox; +class CChoreoView; +class CExpression; +class CExpClass; +class mxButton; +class CChoreoWidgetDrawHelper; + +#include "faceposertoolwindow.h" +#include "mxbitmaptools.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class mxExpressionTray : public mxWindow, public IFacePoserToolWindow +{ +public: + mxExpressionTray( mxWindow *parent, int id = 0 ); + virtual ~mxExpressionTray ( void ); + + virtual void redraw (); + virtual bool PaintBackground( void ); + + virtual int handleEvent (mxEvent *event); + + void ThumbnailIncrease( void ); + void ThumbnailDecrease( void ); + void RestoreThumbnailSize( void ); + + void AB( void ); + + void Select( int exp, bool deselect = true ); + void Deselect( void ); + int CountSelected( void ); + + void SetCellSize( int cellsize ); + + void ReloadBitmaps( void ); + virtual void OnModelChanged(); + +private: // Data structures + + typedef void (mxExpressionTray::*ETMEMBERFUNC)( int cell ); + + class mxETButton + { + public: + mxETButton *next; + char m_szName[ 32 ]; + bool m_bActive; + RECT m_rc; + char m_szToolTip[ 128 ]; + mxbitmapdata_t *m_pImage; + + ETMEMBERFUNC m_fnCallback; + }; + +private: // Methods + void ChangeWeightOfExpressionInGroup( CExpClass *active, CExpression *exp, CExpression *group ); + int GetCellUnderPosition( int x, int y ); + + bool ComputeRect( int cell, int& rcx, int& rcy, int& rcw, int& rch ); + int ComputePixelsNeeded( void ); + + void RepositionSlider(); + void SetClickedCell( int cell ); + void ShowRightClickMenu( int mx, int my ); + + void DrawThumbNail( CExpClass *active, CExpression *current, CChoreoWidgetDrawHelper& helper, + int rcx, int rcy, int rcw, int rch, int c, int selected, bool updateselection ); + + void DrawDirtyFlag( CChoreoWidgetDrawHelper& helper, CExpression *current, int rcx, int rcy, int rcw, int rch ); + void DrawExpressionFocusRect( CChoreoWidgetDrawHelper& helper, int x, int y, int w, int h, COLORREF clr ); + void DrawExpressionDescription( CChoreoWidgetDrawHelper& helper, int x, int y, int w, int h, const char *expressionname, const char *description ); + + void CreateButtons( void ); + void DeleteAllButtons( void ); + void AddButton( const char *name, const char *tooltip, const char *bitmap, + ETMEMBERFUNC pfnCallback, bool active, int x, int y, int w, int h ); + mxETButton *GetItemUnderCursor( int x, int y ); + void DrawButton( CChoreoWidgetDrawHelper& helper, int cell, mxETButton *btn ); + void ActivateButton( const char *name, bool active ); + mxETButton *FindButton( const char *name ); + + void ET_Undo( int cell ); + void ET_Redo( int cell ); + + void DrawFocusRect( void ); + +private: // Data + + mxETButton *m_pButtons; + + mxScrollbar *slScrollbar; + + int m_nTopOffset; + + int m_nLastNumExpressions; + + int m_nGranularity; + + // For A/B + int m_nPrevCell; + int m_nCurCell; + + // For context menu + int m_nClickedCell; + + // Formatting + int m_nButtonSquare; + + int m_nGap; + int m_nDescriptionHeight; + int m_nSnapshotWidth; + int m_nSnapshotHeight; + + // For detecting that the slider thumbs need to be recomputed + int m_nPreviousExpressionCount; + + bool m_bDragging; + RECT m_rcFocus; + RECT m_rcOrig; + int m_nDragCell; + int m_nXStart; + int m_nYStart; + + mxButton *m_pABButton; + mxButton *m_pThumbnailIncreaseButton; + mxButton *m_pThumbnailDecreaseButton; +}; + +extern mxExpressionTray *g_pExpressionTrayTool; + +#endif // MXEXPRESSIONTRAY_H
\ No newline at end of file diff --git a/utils/hlfaceposer/mxstatuswindow.cpp b/utils/hlfaceposer/mxstatuswindow.cpp new file mode 100644 index 0000000..5ce6fbb --- /dev/null +++ b/utils/hlfaceposer/mxstatuswindow.cpp @@ -0,0 +1,311 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include <mxtk/mx.h> +#include "mxStatusWindow.h" +#include "hlfaceposer.h" +#include "choreowidgetdrawhelper.h" +#include "MDLViewer.h" +#include "faceposertoolwindow.h" + +extern double realtime; + +mxStatusWindow *g_pStatusWindow = NULL; + +#define STATUS_SCROLLBAR_SIZE 12 +#define STATUS_FONT_SIZE 9 + +mxStatusWindow::mxStatusWindow(mxWindow *parent, int x, int y, int w, int h, const char *label /*= 0*/ ) +: mxWindow( parent, x, y, w, h, label ), IFacePoserToolWindow( "Status Window", "Output" ), m_pScrollbar(NULL) +{ + for ( int i = 0; i < MAX_TEXT_LINES; i++ ) + { + m_rgTextLines[ i ].m_szText[ 0 ] = 0; + m_rgTextLines[ i ].rgb = CONSOLE_COLOR; + m_rgTextLines[ i ].curtime = 0; + } + m_nCurrentLine = 0; + + m_pScrollbar = new mxScrollbar( this, 0, 0, STATUS_SCROLLBAR_SIZE, 100, IDC_STATUS_SCROLL, mxScrollbar::Vertical ); + m_pScrollbar->setRange( 0, 1000 ); + m_pScrollbar->setPagesize( 100 ); +} + +mxStatusWindow::~mxStatusWindow() +{ + g_pStatusWindow = NULL; +} + +void mxStatusWindow::redraw() +{ +// if ( !ToolCanDraw() ) +// return; + + if ( !m_pScrollbar ) + return; + + CChoreoWidgetDrawHelper helper( this, RGB( 0, 0, 0 ) ); + HandleToolRedraw( helper ); + + RECT rc; + helper.GetClientRect( rc ); + + RECT rcText = rc; + + int lineheight = ( STATUS_FONT_SIZE + 2 ); + + InflateRect( &rcText, -4, 0 ); + rcText.bottom = h2() - 4; + rcText.top = rcText.bottom - lineheight; + + //int minval = m_pScrollbar->getMinValue(); + int maxval = m_pScrollbar->getMaxValue(); + int pagesize = m_pScrollbar->getPagesize(); + int curval = m_pScrollbar->getValue(); + + int offset = ( maxval - pagesize ) - curval; + offset = ( offset + lineheight - 1 ) / lineheight; + + offset = max( 0, offset ); + //offset = 0; + //offset += 10; + //offset = max( 0, offset ); + + for ( int i = 0; i < MAX_TEXT_LINES - offset; i++ ) + { + int rawline = m_nCurrentLine - i - 1; + if ( rawline <= 0 ) + continue; + + if ( rcText.bottom < 0 ) + break; + + int line = ( rawline - offset ) & TEXT_LINE_MASK; + + char *ptext = m_rgTextLines[ line ].m_szText; + + RECT rcTime = rcText; + rcTime.right = rcTime.left + 50; + + char sz[ 32 ]; + sprintf( sz, "%.3f", m_rgTextLines[ line ].curtime ); + + int len = helper.CalcTextWidth( "Arial", STATUS_FONT_SIZE, FW_NORMAL, sz ); + + rcTime.left = rcTime.right - len - 5; + + helper.DrawColoredText( "Arial", STATUS_FONT_SIZE, FW_NORMAL, RGB( 255, 255, 150 ), rcTime, sz ); + + rcTime = rcText; + rcTime.left += 50; + + helper.DrawColoredText( "Arial", STATUS_FONT_SIZE, FW_NORMAL, m_rgTextLines[ line ].rgb, rcTime, ptext ); + + OffsetRect( &rcText, 0, -lineheight ); + } + + DrawActiveTool(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool mxStatusWindow::PaintBackground( void ) +{ + redraw(); + return false; +} + +void mxStatusWindow::StatusPrint( COLORREF clr, bool overwrite, const char *text ) +{ + float curtime = (float)Plat_FloatTime(); + + char sz[32]; + sprintf( sz, "%.3f ", curtime ); + + OutputDebugString( sz ); + OutputDebugString( text ); + + char fixedtext[ 512 ]; + char *in, *out; + in = (char *)text; + out = fixedtext; + + int c = 0; + while ( *in && c < 511 ) + { + if ( *in == '\n' || *in == '\r' ) + { + in++; + } + else + { + *out++ = *in++; + c++; + } + } + *out = 0; + + if ( overwrite ) + { + m_nCurrentLine--; + } + + int i = m_nCurrentLine & TEXT_LINE_MASK; + + strncpy( m_rgTextLines[ i ].m_szText, fixedtext, 511 ); + m_rgTextLines[ i ].m_szText[ 511 ] = 0; + + m_rgTextLines[ i ].rgb = clr; + m_rgTextLines[ i ].curtime = curtime; + + m_nCurrentLine++; + + if ( m_nCurrentLine <= MAX_TEXT_LINES ) + { + PositionSliders( 0 ); + } + m_pScrollbar->setValue( m_pScrollbar->getMaxValue() ); + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : sboffset - +//----------------------------------------------------------------------------- +void mxStatusWindow::PositionSliders( int sboffset ) +{ + int lineheight = ( STATUS_FONT_SIZE + 2 ); + + int linesused = min( (int)MAX_TEXT_LINES, m_nCurrentLine ); + linesused = max( linesused, 1 ); + + int trueh = h2() - GetCaptionHeight(); + + int vpixelsneeded = max( linesused * lineheight, trueh ); + m_pScrollbar->setVisible( linesused * lineheight > trueh ); + + + m_pScrollbar->setPagesize( trueh ); + m_pScrollbar->setRange( 0, vpixelsneeded ); + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +// Output : int +//----------------------------------------------------------------------------- +int mxStatusWindow::handleEvent( mxEvent *event ) +{ + int iret = 0; + + if ( HandleToolEvent( event ) ) + { + return iret; + } + + switch ( event->event ) + { + default: + break; + case mxEvent::Size: + { + m_pScrollbar->setBounds( w2() - STATUS_SCROLLBAR_SIZE, GetCaptionHeight(), STATUS_SCROLLBAR_SIZE, h2()-GetCaptionHeight() ); + PositionSliders( 0 ); + m_pScrollbar->setValue( m_pScrollbar->getMaxValue() ); + iret = 1; + } + break; + case mxEvent::Action: + { + iret = 1; + switch ( event->action ) + { + default: + iret = 0; + break; + case IDC_STATUS_SCROLL: + { + if ( event->event == mxEvent::Action && + event->modifiers == SB_THUMBTRACK) + { + int offset = event->height; + m_pScrollbar->setValue( offset ); + PositionSliders( offset ); + DrawActiveTool(); + } + } + break; + } + } + break; + } + + return iret; +} +#include "StudioModel.h" + +#include "faceposer_models.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void mxStatusWindow::DrawActiveTool() +{ + RECT rcTool; + rcTool.left = 0; + rcTool.top = GetCaptionHeight() + 2; + rcTool.bottom = h2(); + rcTool.right = w2() - 16; + + rcTool.bottom = rcTool.top + 10; + rcTool.left = rcTool.right - 500; + + char sz[ 256 ]; + + IFacePoserToolWindow *activeTool = IFacePoserToolWindow::GetActiveTool(); + + static float lastrealtime = 0.0f; + + float dt = (float)realtime - lastrealtime; + dt = clamp( dt, 0.0f, 1.0f ); + + float fps = 0.0f; + if ( dt > 0.0001f ) + { + fps = 1.0f / dt; + } + + sprintf( sz, "%s (%i) at %.3f (%.2f fps) (soundcount %i)", + activeTool ? activeTool->GetToolName() : "None", + g_MDLViewer->GetCurrentFrame(), + (float)realtime, + fps, + models->CountActiveSources() ); + + lastrealtime = realtime; + + int len = CChoreoWidgetDrawHelper::CalcTextWidth( "Courier New", 10, FW_NORMAL, sz ); + + CChoreoWidgetDrawHelper helper( this, rcTool, RGB( 32, 0, 0 ) ); + + rcTool.left = rcTool.right - len - 15; + + helper.DrawColoredText( "Courier New", 10, FW_NORMAL, RGB( 255, 255, 200 ), rcTool, sz ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dt - +//----------------------------------------------------------------------------- +void mxStatusWindow::Think( float dt ) +{ + DrawActiveTool(); +}
\ No newline at end of file diff --git a/utils/hlfaceposer/mxstatuswindow.h b/utils/hlfaceposer/mxstatuswindow.h new file mode 100644 index 0000000..aca0686 --- /dev/null +++ b/utils/hlfaceposer/mxstatuswindow.h @@ -0,0 +1,61 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#ifndef MXSTATUSWINDOW_H +#define MXSTATUSWINDOW_H +#ifdef _WIN32 +#pragma once +#endif + +#include "faceposertoolwindow.h" + +class mxScrollbar; + +#define IDC_STATUS_SCROLL 1000 + +class mxStatusWindow : public mxWindow, public IFacePoserToolWindow +{ +public: + mxStatusWindow (mxWindow *parent, int x, int y, int w, int h, const char *label = 0 ); + ~mxStatusWindow(); + + void StatusPrint( COLORREF clr, bool overwrite, const char *text ); + + virtual void DrawActiveTool(); + + virtual void redraw(); + virtual bool PaintBackground( void ); + + virtual int handleEvent( mxEvent *event ); + + virtual void Think( float dt ); + +private: + + void PositionSliders( int sboffset ); + + enum + { + MAX_TEXT_LINES = 1024, + TEXT_LINE_MASK = MAX_TEXT_LINES - 1, + }; + + struct TextLine + { + char m_szText[ 512 ]; + COLORREF rgb; + float curtime; + }; + + TextLine m_rgTextLines[ MAX_TEXT_LINES ]; + int m_nCurrentLine; + + mxScrollbar *m_pScrollbar; +}; + +extern mxStatusWindow *g_pStatusWindow; + +#endif // MXSTATUSWINDOW_H diff --git a/utils/hlfaceposer/phonemeeditor.cpp b/utils/hlfaceposer/phonemeeditor.cpp new file mode 100644 index 0000000..e33f6c1 --- /dev/null +++ b/utils/hlfaceposer/phonemeeditor.cpp @@ -0,0 +1,8799 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// +#include <Assert.h> +#include <stdio.h> +#include <math.h> +#include "hlfaceposer.h" +#include "PhonemeEditor.h" +#include "PhonemeEditorColors.h" +#include "snd_audio_source.h" +#include "snd_wave_source.h" +#include "ifaceposersound.h" +#include "choreowidgetdrawhelper.h" +#include "mxBitmapButton.h" +#include "phonemeproperties.h" +#include "tier2/riff.h" +#include "StudioModel.h" +#include "expressions.h" +#include "expclass.h" +#include "InputProperties.h" +#include "phonemeextractor/PhonemeExtractor.h" +#include "PhonemeConverter.h" +#include "choreoevent.h" +#include "choreoscene.h" +#include "ChoreoView.h" +#include "filesystem.h" +#include "UtlBuffer.h" +#include "AudioWaveOutput.h" +#include "StudioModel.h" +#include "viewerSettings.h" +#include "ControlPanel.h" +#include "faceposer_models.h" +#include "tier1/strtools.h" +#include "tabwindow.h" +#include "MatSysWin.h" +#include "soundflags.h" +#include "mdlviewer.h" +#include "filesystem_init.h" +#include "WaveBrowser.h" +#include "tier2/p4helpers.h" +#include "vstdlib/random.h" + +extern IUniformRandomStream *random; + +float SnapTime( float input, float granularity ); + +#define MODE_TAB_OFFSET 20 + +// 10x magnification +#define MAX_TIME_ZOOM 1000 +// 10% per step +#define TIME_ZOOM_STEP 2 + +#define SCRUBBER_HEIGHT 15 + +#define TAG_TOP ( 25 + SCRUBBER_HEIGHT ) +#define TAG_BOTTOM ( TAG_TOP + 20 ) + +#define PLENTY_OF_TIME 99999.9 +#define MINIMUM_WORD_GAP 0.02f +#define MINIMUM_PHONEME_GAP 0.01f +#define DEFAULT_WORD_LENGTH 0.25f +#define DEFAULT_PHONEME_LENGTH 0.1f + +#define WORD_DATA_EXTENSION ".txt" + +// #define ITEM_GAP_EPSILON 0.0025f +struct PhonemeEditorColor +{ + int color_number; // For readability + int mode_number; // -1 for all + COLORREF root_color; + COLORREF gray_color; // if mode is wrong... +}; + +static PhonemeEditorColor g_PEColors[ NUM_COLORS ] = +{ + { COLOR_PHONEME_BACKGROUND, -1, RGB( 240, 240, 220 ) }, + { COLOR_PHONEME_TEXT, -1, RGB( 63, 63, 63 ) }, + { COLOR_PHONEME_LIGHTTEXT, 0, RGB( 180, 180, 120 ) }, + { COLOR_PHONEME_PLAYBACKTICK, 0, RGB( 255, 0, 0 ) }, + { COLOR_PHONEME_WAVDATA, 0, RGB( 128, 31, 63 ) }, + { COLOR_PHONEME_TIMELINE, 0, RGB( 31, 31, 127 ) }, + { COLOR_PHONEME_TIMELINE_MAJORTICK, 0, RGB( 200, 200, 255 ) }, + { COLOR_PHONEME_TIMELINE_MINORTICK, 0, RGB( 210, 210, 240 ) }, + { COLOR_PHONEME_EXTRACTION_RESULT_FAIL, 0, RGB( 180, 180, 0 ) }, + { COLOR_PHONEME_EXTRACTION_RESULT_SUCCESS, 0, RGB( 100, 180, 100 ) }, + { COLOR_PHONEME_EXTRACTION_RESULT_ERROR, 0, RGB( 255, 31, 31 ) }, + { COLOR_PHONEME_EXTRACTION_RESULT_OTHER, 0, RGB( 63, 63, 63 ) }, + { COLOR_PHONEME_TAG_BORDER, 0, RGB( 160, 100, 100 ) }, + { COLOR_PHONEME_TAG_BORDER_SELECTED, 0, RGB( 255, 40, 60 ) }, + { COLOR_PHONEME_TAG_FILLER_NORMAL, 0, RGB( 210, 210, 190 ) }, + { COLOR_PHONEME_TAG_SELECTED, 0, RGB( 200, 130, 130 ) }, + { COLOR_PHONEME_TAG_TEXT, 0, RGB( 63, 63, 63 ) }, + { COLOR_PHONEME_TAG_TEXT_SELECTED, 0, RGB( 250, 250, 250 ) }, + { COLOR_PHONEME_WAV_ENDPOINT, 0, RGB( 0, 0, 200 ) }, + { COLOR_PHONEME_AB, 0, RGB( 63, 190, 210 ) }, + { COLOR_PHONEME_AB_LINE, 0, RGB( 31, 150, 180 ) }, + { COLOR_PHONEME_AB_TEXT, 0, RGB( 100, 120, 120 ) }, + { COLOR_PHONEME_ACTIVE_BORDER, 0, RGB( 150, 240, 180 ) }, + { COLOR_PHONEME_SELECTED_BORDER, 0, RGB( 255, 0, 0 ) }, + { COLOR_PHONEME_TIMING_TAG, -1, RGB( 0, 100, 200 ) }, + + { COLOR_PHONEME_EMPHASIS_BG, 1, RGB( 230, 230, 200 ) }, + { COLOR_PHONEME_EMPHASIS_BG_STRONG, 1, RGB( 163, 201, 239 ) }, + { COLOR_PHONEME_EMPHASIS_BG_WEAK, 1, RGB( 237, 239, 163 ) }, + { COLOR_PHONEME_EMPHASIS_BORDER, 1, RGB( 200, 200, 200 ) }, + { COLOR_PHONEME_EMPHASIS_LINECOLOR, 1, RGB( 0, 0, 255 ) }, + { COLOR_PHONEME_EMPHASIS_DOTCOLOR, 1, RGB( 0, 0, 255 ) }, + { COLOR_PHONEME_EMPHASIS_DOTCOLOR_SELECTED, 1, RGB( 240, 80, 20 ) }, + { COLOR_PHONEME_EMPHASIS_TEXT, 1, RGB( 0, 0, 0 ) }, + { COLOR_PHONEME_EMPHASIS_MIDLINE, 1, RGB( 100, 150, 200 ) }, +}; + +struct Extractor +{ + PE_APITYPE apitype; + CSysModule *module; + IPhonemeExtractor *extractor; +}; + +CUtlVector< Extractor > g_Extractors; + + +bool DoesExtractorExistFor( PE_APITYPE type ) +{ + for ( int i=0; i < g_Extractors.Count(); i++ ) + { + if ( g_Extractors[i].apitype == type ) + return true; + } + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Implements the RIFF i/o interface on stdio +//----------------------------------------------------------------------------- +class StdIOReadBinary : public IFileReadBinary +{ +public: + int open( const char *pFileName ) + { + return (int)filesystem->Open( pFileName, "rb" ); + } + + int read( void *pOutput, int size, int file ) + { + if ( !file ) + return 0; + + return filesystem->Read( pOutput, size, (FileHandle_t)file ); + } + + void seek( int file, int pos ) + { + if ( !file ) + return; + + filesystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD ); + } + + unsigned int tell( int file ) + { + if ( !file ) + return 0; + + return filesystem->Tell( (FileHandle_t)file ); + } + + unsigned int size( int file ) + { + if ( !file ) + return 0; + + return filesystem->Size( (FileHandle_t)file ); + } + + void close( int file ) + { + if ( !file ) + return; + + filesystem->Close( (FileHandle_t)file ); + } +}; + +class StdIOWriteBinary : public IFileWriteBinary +{ +public: + int create( const char *pFileName ) + { + MakeFileWriteable( pFileName ); + return (int)filesystem->Open( pFileName, "wb" ); + } + + int write( void *pData, int size, int file ) + { + return filesystem->Write( pData, size, (FileHandle_t)file ); + } + + void close( int file ) + { + filesystem->Close( (FileHandle_t)file ); + } + + void seek( int file, int pos ) + { + filesystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD ); + } + + unsigned int tell( int file ) + { + return filesystem->Tell( (FileHandle_t)file ); + } +}; + +// Interface objects +static StdIOWriteBinary io_out; +static StdIOReadBinary io_in; + +class CPhonemeModeTab : public CTabWindow +{ +public: + typedef CTabWindow BaseClass; + + CPhonemeModeTab( mxWindow *parent, int x, int y, int w, int h, int id = 0, int style = 0 ) : + CTabWindow( parent, x, y, w, h, id, style ) + { + SetInverted( true ); + } + + virtual void ShowRightClickMenu( int mx, int my ) + { + // Nothing + } + + void Init( void ) + { + add( "Phonemes" ); + add( "Emphasis" ); + select( 0 ); + } +}; + +PhonemeEditor * g_pPhonemeEditor = 0; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *parent - +//----------------------------------------------------------------------------- +PhonemeEditor::PhonemeEditor( mxWindow *parent ) : + IFacePoserToolWindow( "PhonemeEditor", "Phoneme Editor" ), + mxWindow( parent, 0, 0, 0, 0 ) +{ + SetAutoProcess( false ); + + m_flPlaybackRate = 1.0f; + + m_flScrub = 0.0f; + m_flScrubTarget = 0.0f; + + m_CurrentMode = MODE_PHONEMES; + Emphasis_Init(); + SetupPhonemeEditorColors(); + + m_bRedoPending = false; + m_nUndoLevel = 0; + + m_bWordsActive = false; + + m_pWaveFile = NULL; + m_pMixer = NULL; + m_pEvent = NULL; + m_nClickX = 0; + + m_WorkFile.m_bDirty = false; + m_WorkFile.m_szWaveFile[ 0 ] = 0; + m_WorkFile.m_szWorkingFile[ 0 ] = 0; + m_WorkFile.m_szBasePath[ 0 ] = 0; + + m_nTickHeight = 20; + + m_flPixelsPerSecond = 500.0f; + m_nTimeZoom = 100; + m_nTimeZoomStep = TIME_ZOOM_STEP; + + m_pHorzScrollBar = new mxScrollbar( this, 0, 0, 18, 100, IDC_PHONEME_SCROLL, mxScrollbar::Horizontal ); + + + m_hPrevCursor = 0; + m_nStartX = 0; + m_nStartY = 0; + m_nLastX = 0; + m_nLastY = 0; + m_nDragType = DRAGTYPE_NONE; + + SetClickedPhoneme( -1, -1 ); + + m_nSelection[ 0 ] = m_nSelection[ 1 ] = 0; + m_bSelectionActive = false; + + m_nSelectedPhonemeCount = 0; + m_nSelectedWordCount = 0; + + m_btnSave = new mxButton( this, 0, 0, 16, 16, "Save (Ctrl+S)", IDC_SAVE_LINGUISTIC ); + m_btnRedoPhonemeExtraction = new mxButton( this, 38, 14, 80, 16, "Re-extract (Ctrl+R)", IDC_REDO_PHONEMEEXTRACTION ); + + m_btnLoad = new mxButton( this, 0, 0, 0, 0, "Load (Ctrl+O)", IDC_LOADWAVEFILE ); + m_btnPlay = new mxButton( this, 0, 0, 16, 16, "Play (Spacebar)", IDC_PLAYBUTTON ); + + m_pPlaybackRate = new mxSlider( this, 0, 0, 16, 16, IDC_PLAYBACKRATE ); + m_pPlaybackRate->setRange( 0.0, 2.0, 40 ); + m_pPlaybackRate->setValue( m_flPlaybackRate ); + + m_pModeTab = new CPhonemeModeTab( this, 0, 0, 500, 20, IDC_MODE_TAB ); + m_pModeTab->Init(); + + m_nLastExtractionResult = SR_RESULT_NORESULT; + + ClearDragLimit(); + + SetSuffix( " - Normal" ); + m_flScrubberTimeOffset = 0.0f; + + LoadPhonemeConverters(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::OnDelete() +{ + if ( m_pWaveFile ) + { + char fn[ 512 ]; + Q_snprintf( fn, sizeof( fn ), "%s%s", m_WorkFile.m_szBasePath, m_WorkFile.m_szWorkingFile ); + filesystem->RemoveFile( fn, "GAME" ); + } + + delete m_pWaveFile; + m_pWaveFile = NULL; + + m_Tags.Reset(); + m_TagsExt.Reset(); + + UnloadPhonemeConverters(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool PhonemeEditor::CanClose() +{ + if ( !GetDirty() ) + return true; + + int retval = mxMessageBox( this, va( "Save current changes to %s", m_WorkFile.m_szWaveFile ), + "Phoneme Editor", MX_MB_QUESTION | MX_MB_YESNOCANCEL ); + + // Cancel + if ( retval == 2 ) + { + return false; + } + + // Yes + if ( retval == 0 ) + { + CommitChanges(); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +PhonemeEditor::~PhonemeEditor( void ) +{ +} + +void PhonemeEditor::SetupPhonemeEditorColors( void ) +{ + int i; + for ( i = 0; i < NUM_COLORS; i++ ) + { + PhonemeEditorColor *p = &g_PEColors[ i ]; + Assert( p->color_number == i ); + + if ( p->mode_number == -1 ) + { + p->gray_color = p->root_color; + } + else + { + COLORREF bgColor = g_PEColors[ COLOR_PHONEME_BACKGROUND ].root_color; + + int bgr, bgg, bgb; + + bgr = GetRValue( bgColor ); + bgg = GetGValue( bgColor ); + bgb = GetBValue( bgColor ); + + int r, g, b; + + r = GetRValue( p->root_color ); + g = GetGValue( p->root_color ); + b = GetBValue( p->root_color ); + + int avg = ( r + g + b ) / 3; + int bgavg = ( bgr + bgg + bgb ) / 3; + + // Bias toward bg color + avg += ( bgavg - avg ) / 2.5; + + p->gray_color = RGB( avg, avg, avg ); + } + } +} + +COLORREF PhonemeEditor::PEColor( int colornum ) +{ + COLORREF clr = RGB( 0, 0, 0 ); + if ( colornum < 0 || colornum >= NUM_COLORS ) + { + Assert( 0 ); + return clr; + } + + PhonemeEditorColor *p = &g_PEColors[ colornum ]; + + if ( p->mode_number == -1 ) + { + return p->root_color; + } + + int modenum = (int)GetMode(); + + if ( p->mode_number == modenum ) + { + return p->root_color; + } + + return p->gray_color; +} + +void PhonemeEditor::EditWord( CWordTag *pWord, bool positionDialog /*= false*/ ) +{ + if ( !pWord ) + { + Con_Printf( "PhonemeEditor::EditWord: pWord == NULL\n" ); + return; + } + + CInputParams params; + memset( ¶ms, 0, sizeof( params ) ); + strcpy( params.m_szDialogTitle, "Edit Word" ); + strcpy( params.m_szPrompt, "Current Word:" ); + V_strcpy_safe( params.m_szInputText, pWord->GetWord() ); + + params.m_nLeft = -1; + params.m_nTop = -1; + + params.m_bPositionDialog = positionDialog; + if ( params.m_bPositionDialog ) + { + RECT rcWord; + GetWordRect( pWord, rcWord ); + + // Convert to screen coords + POINT pt; + pt.x = rcWord.left; + pt.y = rcWord.top; + + ClientToScreen( (HWND)getHandle(), &pt ); + + params.m_nLeft = pt.x; + params.m_nTop = pt.y; + } + + int iret = InputProperties( ¶ms ); + SetFocus( (HWND)getHandle() ); + if ( !iret ) + { + return; + } + + // Validate parameters + if ( CSentence::CountWords( params.m_szInputText ) != 1 ) + { + Con_ErrorPrintf( "Edit word: %s has multiple words in it!!!\n", params.m_szInputText ); + return; + } + + SetFocus( (HWND)getHandle() ); + + SetDirty( true ); + + PushUndo(); + + // Set the word and clear out the phonemes + // ->m_nPhonemeCode = TextToPhoneme( params.m_szName ); + pWord->SetWord( params.m_szInputText ); + + PushRedo(); + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pPhoneme - +// positionDialog - +//----------------------------------------------------------------------------- +void PhonemeEditor::EditPhoneme( CPhonemeTag *pPhoneme, bool positionDialog /*= false*/ ) +{ + if ( !pPhoneme ) + { + Con_Printf( "PhonemeEditor::EditPhoneme: pPhoneme == NULL\n" ); + return; + } + + CPhonemeParams params; + memset( ¶ms, 0, sizeof( params ) ); + strcpy( params.m_szDialogTitle, "Phoneme/Viseme Properties" ); + V_strcpy_safe( params.m_szName, ConvertPhoneme( pPhoneme->GetPhonemeCode() ) ); + + params.m_nLeft = -1; + params.m_nTop = -1; + + params.m_bPositionDialog = positionDialog; + if ( params.m_bPositionDialog ) + { + RECT rcPhoneme; + GetPhonemeRect( pPhoneme, rcPhoneme ); + + // Convert to screen coords + POINT pt; + pt.x = rcPhoneme.left; + pt.y = rcPhoneme.top; + + ClientToScreen( (HWND)getHandle(), &pt ); + + params.m_nLeft = pt.x; + params.m_nTop = pt.y; + } + + int iret = PhonemeProperties( ¶ms ); + SetFocus( (HWND)getHandle() ); + + if ( !iret ) + { + return; + } + + SetDirty( true ); + + PushUndo(); + + pPhoneme->SetPhonemeCode( TextToPhoneme( params.m_szName ) ); + + PushRedo(); + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::EditPhoneme( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + CPhonemeTag *pPhoneme = GetClickedPhoneme(); + if ( !pPhoneme ) + return; + + EditPhoneme( pPhoneme, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::EditWord( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + CWordTag *pWord = GetClickedWord(); + if ( !pWord ) + return; + + EditWord( pWord, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dragtype - +// startx - +// cursor - +//----------------------------------------------------------------------------- +void PhonemeEditor::StartDragging( int dragtype, int startx, int starty, HCURSOR cursor ) +{ + m_nDragType = dragtype; + m_nStartX = startx; + m_nLastX = startx; + m_nStartY = starty; + m_nLastY = starty; + + if ( m_hPrevCursor ) + { + SetCursor( m_hPrevCursor ); + m_hPrevCursor = NULL; + } + m_hPrevCursor = SetCursor( cursor ); + + m_FocusRects.Purge(); + + RECT rc; + GetWorkspaceRect( rc ); + + RECT rcStart; + rcStart.left = startx; + rcStart.right = startx; + + bool addrect = true; + switch ( dragtype ) + { + default: + case DRAGTYPE_SCRUBBER: + { + RECT rcScrub; + GetScrubHandleRect( rcScrub, true ); + + rcStart = rcScrub; + rcStart.left = ( rcScrub.left + rcScrub.right ) / 2; + rcStart.right = rcStart.left; + rcStart.bottom = h2() - 18 - MODE_TAB_OFFSET; + } + break; + case DRAGTYPE_EMPHASIS_SELECT: + { + RECT rcEmphasis; + Emphasis_GetRect( rc, rcEmphasis ); + + rcStart.top = starty; + rcStart.bottom = starty; + } + break; + case DRAGTYPE_EMPHASIS_MOVE: + { + SetDirty( true ); + + PushUndo(); + + Emphasis_MouseDrag( startx, starty ); + m_Tags.Resort(); + + addrect = false; + } + break; + case DRAGTYPE_SELECTSAMPLES: + case DRAGTYPE_MOVESELECTIONSTART: + case DRAGTYPE_MOVESELECTIONEND: + rcStart.top = rc.top; + rcStart.bottom = rc.bottom; + break; + case DRAGTYPE_MOVESELECTION: + { + rcStart.top = rc.top; + rcStart.bottom = rc.bottom; + + // Compute left/right pixels for selection + rcStart.left = GetPixelForSample( m_nSelection[ 0 ] ); + rcStart.right = GetPixelForSample( m_nSelection[ 1 ] ); + } + break; + case DRAGTYPE_PHONEME: + { + GetPhonemeTrayTopBottom( rcStart ); + m_bWordsActive = false; + } + break; + case DRAGTYPE_WORD: + { + GetWordTrayTopBottom( rcStart ); + m_bWordsActive = true; + } + break; + case DRAGTYPE_MOVEWORD: + { + TraverseWords( &PhonemeEditor::ITER_AddFocusRectSelectedWords, 0.0f ); + addrect = false; + m_bWordsActive = true; + } + break; + case DRAGTYPE_MOVEPHONEME: + { + TraversePhonemes( &PhonemeEditor::ITER_AddFocusRectSelectedPhonemes, 0.0f ); + addrect = false; + m_bWordsActive = false; + } + break; + case DRAGTYPE_EVENTTAG_MOVE: + { + rcStart.top = TAG_TOP; + rcStart.bottom = TAG_BOTTOM; + rcStart.left -= 10; + rcStart.right += 10; + } + break; + } + + if ( addrect ) + { + AddFocusRect( rcStart ); + } + + DrawFocusRect( "start" ); + + SetDragLimit( m_nDragType ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +// Output : int +//----------------------------------------------------------------------------- +int PhonemeEditor::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 ) + { + case IDC_EXPORT_SENTENCE: + { + OnExport(); + } + break; + case IDC_IMPORT_SENTENCE: + { + OnImport(); + } + break; + case IDC_PLAYBACKRATE: + { + m_flPlaybackRate = m_pPlaybackRate->getValue(); + redraw(); + } + break; + case IDC_MODE_TAB: + { + // The mode changed, so reset stuff here + EditorMode newMode = (EditorMode)m_pModeTab->getSelectedIndex(); + bool needpaint = ( m_CurrentMode != newMode ); + m_CurrentMode = newMode; + if ( needpaint ) + { + switch ( GetMode() ) + { + default: + case MODE_PHONEMES: + SetSuffix( " - Normal" ); + break; + case MODE_EMPHASIS: + SetSuffix( " - Emphasis Track" ); + break; + } + + OnModeChanged(); + redraw(); + } + } + break; + case IDC_EMPHASIS_DELETE: + Emphasis_Delete(); + break; + case IDC_EMPHASIS_DESELECT: + Emphasis_DeselectAll(); + break; + case IDC_EMPHASIS_SELECTALL: + Emphasis_SelectAll(); + break; + case IDC_API_SAPI: + OnSAPI(); + break; + case IDC_API_LIPSINC: + OnLipSinc(); + break; + case IDC_PLAYBUTTON: + Play(); + break; + case IDC_UNDO: + Undo(); + break; + case IDC_REDO: + Redo(); + break; + case IDC_CLEARUNDO: + ClearUndo(); + break; + case IDC_ADDTAG: + AddTag(); + break; + case IDC_DELETETAG: + DeleteTag(); + break; + case IDC_COMMITEXTRACTED: + CommitExtracted(); + SetFocus( (HWND)getHandle() ); + break; + case IDC_CLEAREXTRACTED: + ClearExtracted(); + break; + case IDC_SEPARATEPHONEMES: + SeparatePhonemes(); + break; + case IDC_SNAPPHONEMES: + SnapPhonemes(); + break; + case IDC_SEPARATEWORDS: + SeparateWords(); + break; + case IDC_SNAPWORDS: + SnapWords(); + break; + case IDC_EDITWORDLIST: + EditWordList(); + break; + case IDC_EDIT_PHONEME: + EditPhoneme(); + break; + case IDC_EDIT_WORD: + EditWord(); + break; + case IDC_EDIT_INSERTPHONEMEBEFORE: + EditInsertPhonemeBefore(); + break; + case IDC_EDIT_INSERTPHONEMEAFTER: + EditInsertPhonemeAfter(); + break; + case IDC_EDIT_INSERTWORDBEFORE: + EditInsertWordBefore(); + break; + case IDC_EDIT_INSERTWORDAFTER: + EditInsertWordAfter(); + break; + case IDC_EDIT_DELETEPHONEME: + EditDeletePhoneme(); + break; + case IDC_EDIT_DELETEWORD: + EditDeleteWord(); + break; + case IDC_EDIT_INSERTFIRSTPHONEMEOFWORD: + EditInsertFirstPhonemeOfWord(); + break; + case IDC_PHONEME_PLAY_ORIG: + { + StopPlayback(); + if ( m_pWaveFile ) + { + // Make sure phonemes are loaded + FacePoser_EnsurePhonemesLoaded(); + + sound->PlaySound( m_pWaveFile, VOL_NORM, &m_pMixer ); + } + } + break; + case IDC_PHONEME_SCROLL: + if (event->modifiers == SB_THUMBTRACK) + { + MoveTimeSliderToPos( event->height ); + } + else if ( event->modifiers == SB_PAGEUP ) + { + int offset = m_pHorzScrollBar->getValue(); + + offset -= 10; + offset = max( offset, m_pHorzScrollBar->getMinValue() ); + + MoveTimeSliderToPos( offset ); + } + else if ( event->modifiers == SB_PAGEDOWN ) + { + int offset = m_pHorzScrollBar->getValue(); + + offset += 10; + offset = min( offset, m_pHorzScrollBar->getMaxValue() ); + + MoveTimeSliderToPos( offset ); + } + break; + case IDC_REDO_PHONEMEEXTRACTION: + if ( m_Tags.m_Words.Size() <= 0 ) + { + // This calls redo LISET if some words are actually entered + EditWordList(); + } + else + { + RedoPhonemeExtraction(); + } + SetFocus( (HWND)getHandle() ); + break; + case IDC_REDO_PHONEMEEXTRACTION_SELECTION: + { + RedoPhonemeExtractionSelected(); + } + SetFocus( (HWND)getHandle() ); + break; + case IDC_DESELECT: + Deselect(); + redraw(); + break; + case IDC_PLAY_EDITED: + PlayEditedWave( false ); + SetFocus( (HWND)getHandle() ); + break; + case IDC_PLAY_EDITED_SELECTION: + PlayEditedWave( true ); + SetFocus( (HWND)getHandle() ); + break; + case IDC_SAVE_LINGUISTIC: + CommitChanges(); + SetFocus( (HWND)getHandle() ); + break; + case IDC_LOADWAVEFILE: + LoadWaveFile(); + SetFocus( (HWND)getHandle() ); + break; + case IDC_CANCELPLAYBACK: + StopPlayback(); + SetFocus( (HWND)getHandle() ); + break; + case IDC_SELECT_WORDSRIGHT: + SelectWords( true ); + break; + case IDC_SELECT_WORDSLEFT: + SelectWords( false ); + break; + case IDC_SELECT_PHONEMESRIGHT: + SelectPhonemes( true ); + break; + case IDC_SELECT_PHONEMESLEFT: + SelectPhonemes( false ); + break; + case IDC_DESELECT_PHONEMESANDWORDS: + DeselectPhonemes(); + DeselectWords(); + redraw(); + break; + case IDC_CLEANUP: + CleanupWordsAndPhonemes( true ); + redraw(); + break; + case IDC_REALIGNPHONEMES: + RealignPhonemesToWords( true ); + redraw(); + break; + case IDC_REALIGNWORDS: + RealignWordsToPhonemes( true ); + redraw(); + break; + case IDC_TOGGLE_VOICEDUCK: + OnToggleVoiceDuck(); + break; + } + + if ( iret == 1 ) + { + SetActiveTool( this ); + SetFocus( (HWND)getHandle() ); + } + } + break; + case mxEvent::MouseWheeled: + { + // Zoom time in / out + if ( event->height > 0 ) + { + m_nTimeZoom = min( m_nTimeZoom + m_nTimeZoomStep, MAX_TIME_ZOOM ); + } + else + { + m_nTimeZoom = max( m_nTimeZoom - m_nTimeZoomStep, m_nTimeZoomStep ); + } + RepositionHSlider(); + iret = 1; + } + break; + case mxEvent::Size: + { + int bw = 100; + int x = 5; + int by = h2() - 18 - MODE_TAB_OFFSET; + + m_pModeTab->setBounds( 0, h2() - MODE_TAB_OFFSET, w2(), MODE_TAB_OFFSET ); + + m_btnRedoPhonemeExtraction->setBounds( x, by, bw, 16 ); + x += bw; + m_btnSave->setBounds( x, by, bw, 16 ); + x += bw; + m_btnLoad->setBounds( x, by, bw, 16 ); + x += bw; + m_btnPlay->setBounds( x, by, bw, 16 ); + x += bw; + + m_pPlaybackRate->setBounds( x, by, 100, 16 ); + + RepositionHSlider(); + iret = 1; + } + break; + case mxEvent::MouseDown: + { + iret = 1; + + CPhonemeTag *pt; + CWordTag *wt; + + pt = GetPhonemeTagUnderMouse( (short)event->x, (short)event->y ); + wt = GetWordTagUnderMouse( (short)event->x, (short)event->y ); + + bool ctrldown = ( event->modifiers & mxEvent::KeyCtrl ) ? true : false; + bool shiftdown = ( event->modifiers & mxEvent::KeyShift ) ? true : false; + + if ( event->buttons & mxEvent::MouseRightButton ) + { + RECT rc; + GetWorkspaceRect( rc ); + + if ( IsMouseOverWordRow( (short)event->y ) ) + { + ShowWordMenu( wt, (short)event->x, (short)event->y ); + } + else if ( IsMouseOverPhonemeRow( (short)event->y ) ) + { + ShowPhonemeMenu( pt, (short)event->x, (short)event->y ); + } + else if ( IsMouseOverTagRow( (short)event->y ) ) + { + ShowTagMenu( (short)event->x, (short)event->y ); + } + else if ( IsMouseOverScrubArea( event ) ) + { + float t = GetTimeForPixel( (short)event->x ); + + ClampTimeToSelectionInterval( t ); + + SetScrubTime( t ); + SetScrubTargetTime( t ); + + redraw(); + } + else + { + ShowContextMenu( (short)event->x, (short)event->y ); + } + return iret; + } + + if ( m_nDragType == DRAGTYPE_NONE ) + { + CountSelected(); + + int type = IsMouseOverBoundary( event ); + + if ( IsMouseOverScrubArea( event ) ) + { + if ( IsMouseOverScrubHandle( event ) ) + { + StartDragging( DRAGTYPE_SCRUBBER, + (short)event->x, + (short)event->y, + LoadCursor( NULL, IDC_SIZEWE ) ); + + float t = GetTimeForPixel( (short)event->x ); + m_flScrubberTimeOffset = m_flScrub - t; + float maxoffset = 0.5f * (float)SCRUBBER_HANDLE_WIDTH / GetPixelsPerSecond(); + m_flScrubberTimeOffset = clamp( m_flScrubberTimeOffset, -maxoffset, maxoffset ); + t += m_flScrubberTimeOffset; + ClampTimeToSelectionInterval( t ); + + SetScrubTime( t ); + SetScrubTargetTime( t ); + + DrawScrubHandle(); + iret = true; + } + else + { + float t = GetTimeForPixel( (short)event->x ); + + ClampTimeToSelectionInterval( t ); + + SetScrubTargetTime( t ); + + iret = true; + + } + return iret; + } + else if ( GetMode() == MODE_EMPHASIS ) + { + CEmphasisSample *sample = Emphasis_GetSampleUnderMouse( event ); + if ( sample ) + { + if ( shiftdown ) + { + sample->selected = !sample->selected; + redraw(); + } + else if ( sample->selected ) + { + StartDragging( DRAGTYPE_EMPHASIS_MOVE, (short)event->x, (short)event->y, LoadCursor( NULL, IDC_SIZEALL ) ); + } + else + { + if ( !shiftdown ) + { + Emphasis_DeselectAll(); + redraw(); + } + + StartDragging( DRAGTYPE_EMPHASIS_SELECT, (short)event->x, (short)event->y, NULL ); + } + return true; + } + else if ( ctrldown ) + { + // Add a sample point + float t = GetTimeForPixel( (short)event->x ); + + RECT rcWork; + GetWorkspaceRect( rcWork ); + RECT rcEmphasis; + Emphasis_GetRect( rcWork, rcEmphasis ); + + int eh = rcEmphasis.bottom - rcEmphasis.top; + int dy = (short)event->y - rcEmphasis.top; + + CEmphasisSample sample; + sample.time = t; + Assert( eh >= 0 ); + sample.value = (float)( dy ) / ( float ) eh; + sample.value = 1.0f - clamp( sample.value, 0.0f, 1.0f ); + sample.selected = false; + + Emphasis_AddSample( sample ); + + redraw(); + + return true; + } + else + { + if ( !shiftdown ) + { + Emphasis_DeselectAll(); + redraw(); + } + + StartDragging( DRAGTYPE_EMPHASIS_SELECT, (short)event->x, (short)event->y, NULL ); + return true; + } + } + else + { + if ( type == BOUNDARY_PHONEME && m_nSelectedPhonemeCount <= 1 ) + { + StartDragging( DRAGTYPE_PHONEME, (short)event->x, (short)event->y, LoadCursor( NULL, IDC_SIZEWE ) ); + return true; + } + else if ( type == BOUNDARY_WORD && m_nSelectedWordCount <= 1 ) + { + StartDragging( DRAGTYPE_WORD, (short)event->x, (short)event->y, LoadCursor( NULL, IDC_SIZEWE ) ); + return true; + } + else if ( IsMouseOverSamples( (short)event->x, (short)event->y ) ) + { + if ( !m_bSelectionActive ) + { + StartDragging( DRAGTYPE_SELECTSAMPLES, (short)event->x, (short)event->y, LoadCursor( NULL, IDC_SIZEWE ) ); + } + else + { + // Either move, move edge if ctrl key is held, or deselect + if ( IsMouseOverSelection( (short)event->x, (short)event->y ) ) + { + if ( IsMouseOverSelectionStartEdge( event ) ) + { + StartDragging( DRAGTYPE_MOVESELECTIONSTART, (short)event->x, (short)event->y, LoadCursor( NULL, IDC_SIZEWE ) ); + } + else if ( IsMouseOverSelectionEndEdge( event ) ) + { + StartDragging( DRAGTYPE_MOVESELECTIONEND, (short)event->x, (short)event->y, LoadCursor( NULL, IDC_SIZEWE ) ); + } + else + { + if ( shiftdown ) + { + StartDragging( DRAGTYPE_MOVESELECTION, (short)event->x, (short)event->y, LoadCursor( NULL, IDC_SIZEALL ) ); + } + } + } + else + { + Deselect(); + redraw(); + return iret; + } + } + return true; + } + } + + if ( IsMouseOverTag( (short)event->x, (short)event->y ) ) + { + StartDragging( DRAGTYPE_EVENTTAG_MOVE, (short)event->x, (short)event->y, LoadCursor( NULL, IDC_SIZEALL ) ); + return true; + } + else + { + if ( pt ) + { + // Can only move when holding down shift key + if ( shiftdown ) + { + pt->m_bSelected = true; + StartDragging( DRAGTYPE_MOVEPHONEME, + (short)event->x, (short)event->y, LoadCursor( NULL, IDC_SIZEALL ) ); + } + else + { + // toggle the selection + pt->m_bSelected = !pt->m_bSelected; + } + + + m_bWordsActive = false; + + redraw(); + return true; + } + else if ( wt ) + { + + // Can only move when holding down shift key + if ( shiftdown ) + { + wt->m_bSelected = true; + StartDragging( DRAGTYPE_MOVEWORD, + (short)event->x, (short)event->y, LoadCursor( NULL, IDC_SIZEALL ) ); + } + else + { + // toggle the selection + wt->m_bSelected = !wt->m_bSelected; + } + + m_bWordsActive = true; + + redraw(); + return true; + } + else if ( type == BOUNDARY_NONE ) + { + DeselectPhonemes(); + DeselectWords(); + redraw(); + return true; + } + } + } + } + break; + case mxEvent::MouseMove: + case mxEvent::MouseDrag: + { + OnMouseMove( event ); + iret = 1; + } + break; + case mxEvent::MouseUp: + { + if ( m_nDragType != DRAGTYPE_NONE ) + { + int mx = (short)event->x; + + LimitDrag( mx ); + + event->x = (short)mx; + + DrawFocusRect( "finish" ); + + if ( m_hPrevCursor ) + { + SetCursor( m_hPrevCursor ); + m_hPrevCursor = 0; + } + + switch ( m_nDragType ) + { + case DRAGTYPE_WORD: + FinishWordMove( m_nStartX, (short)event->x ); + break; + case DRAGTYPE_PHONEME: + FinishPhonemeMove( m_nStartX, (short)event->x ); + break; + case DRAGTYPE_SELECTSAMPLES: + FinishSelect( m_nStartX, (short)event->x ); + break; + case DRAGTYPE_MOVESELECTION: + FinishMoveSelection( m_nStartX, (short)event->x ); + break; + case DRAGTYPE_MOVESELECTIONSTART: + FinishMoveSelectionStart( m_nStartX, (short)event->x ); + break; + case DRAGTYPE_MOVESELECTIONEND: + FinishMoveSelectionEnd( m_nStartX, (short)event->x ); + break; + case DRAGTYPE_MOVEWORD: + FinishWordDrag( m_nStartX, (short)event->x ); + break; + case DRAGTYPE_MOVEPHONEME: + FinishPhonemeDrag( m_nStartX, (short)event->x ); + break; + case DRAGTYPE_EVENTTAG_MOVE: + FinishEventTagDrag( m_nStartX, (short)event->x ); + break; + case DRAGTYPE_EMPHASIS_MOVE: + { + Emphasis_MouseDrag( (short)event->x, (short)event->y ); + m_Tags.Resort(); + + PushRedo(); + + redraw(); + } + break; + case DRAGTYPE_EMPHASIS_SELECT: + { + Emphasis_SelectPoints(); + redraw(); + } + break; + case DRAGTYPE_SCRUBBER: + { + float t = GetTimeForPixel( (short)event->x ); + t += m_flScrubberTimeOffset; + m_flScrubberTimeOffset = 0.0f; + + ClampTimeToSelectionInterval( t ); + + SetScrubTime( t ); + SetScrubTargetTime( t ); + + sound->Flush(); + + DrawScrubHandle(); + } + break; + default: + break; + } + + m_nDragType = DRAGTYPE_NONE; + } + iret = 1; + } + break; + case mxEvent::KeyUp: + { + bool shiftDown = GetAsyncKeyState( VK_SHIFT ) ? true : false; + bool ctrlDown = GetAsyncKeyState( VK_CONTROL ) ? true : false; + + switch( event->key ) + { + case VK_TAB: + { + int direction = shiftDown ? -1 : 1; + SelectNextWord( direction ); + } + break; + case VK_NEXT: + case VK_PRIOR: + { + m_bWordsActive = event->key == VK_PRIOR ? true : false; + redraw(); + } + break; + case VK_UP: + case VK_RETURN: + if ( m_bWordsActive ) + { + if ( event->key == VK_UP || + ctrlDown ) + { + CountSelected(); + + if ( m_nSelectedWordCount == 1 ) + { + // Find the selected one + for ( int i = 0; i < m_Tags.m_Words.Size(); i++ ) + { + CWordTag *word = m_Tags.m_Words[ i ]; + if ( !word || !word->m_bSelected ) + continue; + + EditWord( word, true ); + } + } + } + } + else + { + if ( event->key == VK_UP || + ctrlDown ) + { + CountSelected(); + + if ( m_nSelectedPhonemeCount == 1 ) + { + // Find the selected one + for ( int i = 0; i < m_Tags.m_Words.Size(); i++ ) + { + CWordTag *word = m_Tags.m_Words[ i ]; + if ( !word ) + continue; + + for ( int j = 0; j < word->m_Phonemes.Size(); j++ ) + { + CPhonemeTag *phoneme = word->m_Phonemes[ j ]; + if ( !phoneme ) + continue; + + if ( !phoneme->m_bSelected ) + continue; + + EditPhoneme( phoneme, true ); + } + } + } + } + } + break; + case VK_DELETE: + if ( GetMode() == MODE_EMPHASIS ) + { + Emphasis_Delete(); + } + else + { + if ( m_bWordsActive ) + { + EditDeleteWord(); + } + else + { + EditDeletePhoneme(); + } + } + break; + case VK_INSERT: + if ( m_bWordsActive ) + { + if ( shiftDown ) + { + EditInsertWordBefore(); + } + else + { + EditInsertWordAfter(); + } + } + else + { + if ( shiftDown ) + { + EditInsertPhonemeBefore(); + } + else + { + EditInsertPhonemeAfter(); + } + } + break; + case VK_SPACE: + if ( m_pWaveFile && sound->IsSoundPlaying( m_pMixer ) ) + { + Con_Printf( "Stopping playback\n" ); + m_btnPlay->setLabel( "Play (Spacebar)" ); + StopPlayback(); + } + else + { + Con_Printf( "Playing .wav\n" ); + m_btnPlay->setLabel( "Stop[ (Spacebar)" ); + PlayEditedWave( m_bSelectionActive ); + } + break; + case VK_SHIFT: + case VK_CONTROL: + { + // Force mouse move + POINT pt; + GetCursorPos( &pt ); + SetCursorPos( pt.x, pt.y ); + return 0; + } + break; + case VK_ESCAPE: + { + // If playing sound, stop it, otherwise, deselect all + if ( !StopPlayback() ) + { + Deselect(); + DeselectPhonemes(); + DeselectWords(); + Emphasis_DeselectAll(); + redraw(); + } + } + break; + case 'O': + { + if ( ctrlDown ) + { + LoadWaveFile(); + } + } + break; + case 'S': + { + if ( ctrlDown ) + { + CommitChanges(); + } + } + break; + case 'T': + { + if ( ctrlDown ) + { + // Edit sentence text + EditWordList(); + } + } + break; + case 'G': + { + if ( ctrlDown ) + { + // Commit extraction + CommitExtracted(); + } + } + break; + case 'R': + { + if ( ctrlDown ) + { + RedoPhonemeExtraction(); + } + } + break; + default: + break; + } + + SetFocus( (HWND)getHandle() ); + iret = 1; + } + break; + case mxEvent::KeyDown: + { + switch ( event->key ) + { + case 'Z': + if ( GetAsyncKeyState( VK_CONTROL ) ) + { + Undo(); + } + break; + case 'Y': + if ( GetAsyncKeyState( VK_CONTROL ) ) + { + Redo(); + } + break; + + + case VK_RIGHT: + case VK_LEFT: + { + int direction = event->key == VK_LEFT ? -1 : 1; + + if ( !m_bWordsActive ) + { + if ( GetAsyncKeyState( VK_CONTROL ) ) + { + ExtendSelectedPhonemeEndTime( direction ); + } + else if ( GetAsyncKeyState( VK_SHIFT ) ) + { + ShiftSelectedPhoneme( direction ); + } + else + { + SelectNextPhoneme( direction ); + } + } + else + { + if ( GetAsyncKeyState( VK_CONTROL ) ) + { + ExtendSelectedWordEndTime( direction ); + } + else if ( GetAsyncKeyState( VK_SHIFT ) ) + { + ShiftSelectedWord( direction ); + } + else + { + SelectNextWord( direction ); + } + } + } + break; + case VK_RETURN: + { + } + break; + case VK_SHIFT: + case VK_CONTROL: + { + // Force mouse move + POINT pt; + GetCursorPos( &pt ); + //SetCursorPos( pt.x -1, pt.y ); + SetCursorPos( pt.x, pt.y ); + return 0; + } + break; + default: + break; + } + iret = 1; + } + break; + } + return iret; +} + +void PhonemeEditor::DrawWords( CChoreoWidgetDrawHelper& drawHelper, RECT& rcWorkSpace, CSentence& sentence, int type, bool showactive /* = true */ ) +{ + float starttime = m_nLeftOffset / GetPixelsPerSecond(); + float endtime = w2() / GetPixelsPerSecond() + starttime; + + int ypos = rcWorkSpace.top + m_nTickHeight + 2; + + if ( type == 1 ) + { + ypos += m_nTickHeight + 5; + } + + const char *fontName = "Arial"; + + bool drawselected; + for ( int pass = 0; pass < 2 ; pass++ ) + { + drawselected = pass == 0 ? false : true; + + for (int k = 0; k < sentence.m_Words.Size(); k++) + { + CWordTag *word = sentence.m_Words[ k ]; + if ( !word ) + continue; + + if ( word->m_bSelected != drawselected ) + continue; + + bool hasselectedphonemes = false; + for ( int p = 0; p < word->m_Phonemes.Size() && !hasselectedphonemes; p++ ) + { + CPhonemeTag *t = word->m_Phonemes[ p ]; + if ( t->m_bSelected ) + { + hasselectedphonemes = true; + } + } + + float t1 = word->m_flStartTime; + float t2 = word->m_flEndTime; + + // Tag it + float frac = ( t1 - starttime ) / ( endtime - starttime ); + + int xpos = ( int )( frac * rcWorkSpace.right ); + + if ( frac <= 0.0 ) + xpos = 0; + + // Draw duration + float frac2 = ( t2 - starttime ) / ( endtime - starttime ); + if ( frac2 < 0.0 ) + continue; + + int xpos2 = ( int )( frac2 * rcWorkSpace.right ); + + // Draw line and vertical ticks + RECT rcWord; + rcWord.left = xpos; + rcWord.right = xpos2; + rcWord.top = ypos - m_nTickHeight + 1; + rcWord.bottom = ypos; + + drawHelper.DrawFilledRect( + PEColor( word->m_bSelected ? COLOR_PHONEME_TAG_SELECTED : COLOR_PHONEME_TAG_FILLER_NORMAL ), + rcWord ); + + COLORREF border = PEColor( word->m_bSelected ? COLOR_PHONEME_TAG_BORDER_SELECTED : COLOR_PHONEME_TAG_BORDER ); + + if ( showactive && m_bWordsActive ) + { + drawHelper.DrawFilledRect( PEColor( COLOR_PHONEME_ACTIVE_BORDER ), xpos, ypos - m_nTickHeight, xpos2, ypos - m_nTickHeight + 4 ); + } + + drawHelper.DrawColoredLine( border, PS_SOLID, 1, xpos, ypos, xpos2, ypos ); + drawHelper.DrawColoredLine( border, PS_SOLID, 1, xpos, ypos, xpos, ypos - m_nTickHeight ); + drawHelper.DrawColoredLine( border, PS_SOLID, 1, xpos2, ypos, xpos2, ypos - m_nTickHeight ); + drawHelper.DrawColoredLine( border, PS_SOLID, 1, xpos, ypos - m_nTickHeight, xpos2, ypos - m_nTickHeight ); + + if ( hasselectedphonemes ) + { + drawHelper.DrawFilledRect( PEColor( COLOR_PHONEME_SELECTED_BORDER ), xpos, ypos - 3, xpos2, ypos ); + } + + //if ( frac >= 0.0 && frac <= 1.0 ) + { + int fontsize = 9; + + RECT rcText; + rcText.left = xpos; + rcText.right = xpos + 500; + rcText.top = ypos - m_nTickHeight + 4; + rcText.bottom = rcText.top + fontsize + 2; + + int length = drawHelper.CalcTextWidth( fontName, fontsize, FW_NORMAL, "%s", word->GetWord() ); + + rcText.right = max( (LONG)xpos2 - 2, rcText.left + length + 1 ); + + int w = rcText.right - rcText.left; + if ( w > length ) + { + rcText.left += ( w - length ) / 2; + } + + drawHelper.DrawColoredText( + fontName, + fontsize, + FW_NORMAL, + PEColor( word->m_bSelected ? COLOR_PHONEME_TAG_TEXT_SELECTED : COLOR_PHONEME_TAG_TEXT ), + rcText, + "%s", word->GetWord() ); + } + + } + } +} + +void PhonemeEditor::DrawPhonemes( CChoreoWidgetDrawHelper& drawHelper, RECT& rcWorkSpace, CSentence& sentence, int type, bool showactive /* = true */ ) +{ + float starttime = m_nLeftOffset / GetPixelsPerSecond(); + float endtime = w2() / GetPixelsPerSecond() + starttime; + + int ypos = rcWorkSpace.bottom - m_nTickHeight - 2; + + if ( type == 1 ) + { + ypos -= ( m_nTickHeight + 5 ); + } + + const char *fontName = "Arial"; + + bool drawselected; + for ( int pass = 0; pass < 2 ; pass++ ) + { + drawselected = pass == 0 ? false : true; + + for ( int i = 0; i < sentence.m_Words.Size(); i++ ) + { + CWordTag *w = sentence.m_Words[ i ]; + if ( !w ) + continue; + + if ( w->m_bSelected != drawselected ) + continue; + + for ( int k = 0; k < w->m_Phonemes.Size(); k++ ) + { + CPhonemeTag *pPhoneme = w->m_Phonemes[ k ]; + + float t1 = pPhoneme->GetStartTime(); + float t2 = pPhoneme->GetEndTime(); + + // Tag it + float frac = ( t1 - starttime ) / ( endtime - starttime ); + + int xpos = ( int )( frac * rcWorkSpace.right ); + if ( frac <= 0.0 ) + { + xpos = 0; + } + + // Draw duration + float frac2 = ( t2 - starttime ) / ( endtime - starttime ); + if ( frac2 < 0.0 ) + { + continue; + } + + int xpos2 = ( int )( frac2 * rcWorkSpace.right ); + + RECT rcFrame; + rcFrame.left = xpos; + rcFrame.right = xpos2; + rcFrame.top = ypos - m_nTickHeight + 1; + rcFrame.bottom = ypos; + + drawHelper.DrawFilledRect( + PEColor( pPhoneme->m_bSelected ? COLOR_PHONEME_TAG_SELECTED : COLOR_PHONEME_TAG_FILLER_NORMAL ), + rcFrame ); + + COLORREF border = PEColor( pPhoneme->m_bSelected ? COLOR_PHONEME_TAG_BORDER_SELECTED : COLOR_PHONEME_TAG_BORDER ); + + if ( showactive && !m_bWordsActive ) + { + drawHelper.DrawFilledRect( PEColor( COLOR_PHONEME_ACTIVE_BORDER ), xpos, ypos - 3, xpos2, ypos ); + } + + drawHelper.DrawColoredLine( border, PS_SOLID, 1, xpos, ypos - m_nTickHeight, xpos2, ypos - m_nTickHeight ); + drawHelper.DrawColoredLine( border, PS_SOLID, 1, xpos, ypos, xpos, ypos - m_nTickHeight ); + drawHelper.DrawColoredLine( border, PS_SOLID, 1, xpos2, ypos, xpos2, ypos - m_nTickHeight ); + drawHelper.DrawColoredLine( border, PS_SOLID, 1, xpos, ypos, xpos2, ypos ); + + if ( w->m_bSelected ) + { + drawHelper.DrawFilledRect( PEColor( COLOR_PHONEME_SELECTED_BORDER ), xpos, ypos - m_nTickHeight + 1, xpos2, ypos - m_nTickHeight + 4 ); + } + + //if ( frac >= 0.0 && frac <= 1.0 ) + { + + int fontsize = 9; + + RECT rcText; + rcText.left = xpos; + rcText.right = xpos + 500; + rcText.top = ypos - m_nTickHeight + 4; + rcText.bottom = rcText.top + fontsize + 2; + + int length = drawHelper.CalcTextWidth( fontName, fontsize, FW_NORMAL, "%s", ConvertPhoneme( pPhoneme->GetPhonemeCode() ) ); + + rcText.right = max( (LONG)xpos2 - 2, rcText.left + length + 1 ); + + int w = rcText.right - rcText.left; + if ( w > length ) + { + rcText.left += ( w - length ) / 2; + } + + drawHelper.DrawColoredText( + fontName, + fontsize, + FW_NORMAL, + PEColor( pPhoneme->m_bSelected ? COLOR_PHONEME_TAG_TEXT_SELECTED : COLOR_PHONEME_TAG_TEXT ), + rcText, + "%s", ConvertPhoneme( pPhoneme->GetPhonemeCode() ) ); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rc - +//----------------------------------------------------------------------------- +void PhonemeEditor::DrawRelativeTags( CChoreoWidgetDrawHelper& drawHelper, RECT& rc ) +{ + if ( !m_pEvent || !m_pWaveFile ) + return; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, PEColor( COLOR_PHONEME_TIMING_TAG ), rc, "Timing Tags:" ); + + float starttime = m_nLeftOffset / GetPixelsPerSecond(); + float endtime = w2() / GetPixelsPerSecond() + starttime; + + for ( int i = 0; i < m_pEvent->GetNumRelativeTags(); i++ ) + { + CEventRelativeTag *tag = m_pEvent->GetRelativeTag( i ); + if ( !tag ) + continue; + + // + float tagtime = tag->GetPercentage() * m_pWaveFile->GetRunningLength(); + if ( tagtime < starttime || tagtime > endtime ) + continue; + + float frac = ( tagtime - starttime ) / ( endtime - starttime ); + + int left = rc.left + (int)( frac * ( float )( rc.right - rc.left ) + 0.5f ); + + RECT rcMark; + rcMark = rc; + rcMark.top = rc.bottom - 8; + rcMark.bottom = rc.bottom; + rcMark.left = left - 4; + rcMark.right = left + 4; + + drawHelper.DrawTriangleMarker( rcMark, PEColor( COLOR_PHONEME_TIMING_TAG ) ); + + RECT rcText; + rcText = rc; + rcText.bottom = rc.bottom - 10; + rcText.top = rcText.bottom - 10; + + int len = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, tag->GetName() ); + rcText.left = left - len / 2; + rcText.right = rcText.left + len + 2; + + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, PEColor( COLOR_PHONEME_TIMING_TAG ), rcText, tag->GetName() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::redraw( void ) +{ + if ( !ToolCanDraw() ) + return; + + CChoreoWidgetDrawHelper drawHelper( this ); + HandleToolRedraw( drawHelper ); + + if ( !m_pWaveFile ) + return; + + HDC dc = drawHelper.GrabDC(); + + RECT rc; + GetWorkspaceRect( rc ); + + float starttime = m_nLeftOffset / GetPixelsPerSecond(); + float endtime = w2() / GetPixelsPerSecond() + starttime; + + // Now draw the time legend + RECT rcLabel; + float granularity = 0.5f; + + drawHelper.DrawColoredLine( PEColor( COLOR_PHONEME_TIMELINE ), PS_SOLID, 1, rc.left, rc.bottom - m_nTickHeight, rc.right, rc.bottom - m_nTickHeight ); + + if ( GetMode() != MODE_EMPHASIS ) + { + Emphasis_Redraw( drawHelper, rc ); + } + + sound->RenderWavToDC( + dc, + rc, + PEColor( COLOR_PHONEME_WAVDATA ), + starttime, + endtime, + m_pWaveFile, + m_bSelectionActive, + m_nSelection[ 0 ], + m_nSelection[ 1 ] ); + + float f = SnapTime( starttime, granularity ); + while ( f <= endtime ) + { + float frac = ( f - starttime ) / ( endtime - starttime ); + if ( frac >= 0.0f && frac <= 1.0f ) + { + drawHelper.DrawColoredLine( ( COLOR_PHONEME_TIMELINE_MAJORTICK ), PS_SOLID, 1, (int)( frac * rc.right ), rc.top, (int)( frac * rc.right ), rc.bottom - m_nTickHeight ); + + rcLabel.left = (int)( frac * rc.right ); + rcLabel.bottom = rc.bottom; + rcLabel.top = rcLabel.bottom - 10; + + char sz[ 32 ]; + sprintf( sz, "%.2f", f ); + int textWidth = drawHelper.CalcTextWidth( "Arial", 9, FW_NORMAL, sz ); + rcLabel.right = rcLabel.left + textWidth; + OffsetRect( &rcLabel, -textWidth / 2, 0 ); + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, PEColor( COLOR_PHONEME_TEXT ), rcLabel, sz ); + } + f += granularity; + } + + HBRUSH br = CreateSolidBrush( PEColor( COLOR_PHONEME_TEXT ) ); + + FrameRect( dc, &rc, br ); + + DeleteObject( br ); + + RECT rcTags = rc; + rcTags.top = TAG_TOP; + rcTags.bottom = TAG_BOTTOM; + + DrawRelativeTags( drawHelper, rcTags ); + + int fontsize = 9; + RECT rcText = rc; + rcText.top = rcText.bottom + 5; + rcText.left += 5; + rcText.bottom = rcText.top + fontsize + 1; + rcText.right -= 5; + + int fontweight = FW_NORMAL; + + const char *font = "Arial"; + + if ( m_nLastExtractionResult != SR_RESULT_NORESULT ) + { + COLORREF clr = PEColor( COLOR_PHONEME_EXTRACTION_RESULT_OTHER ); + switch ( m_nLastExtractionResult ) + { + case SR_RESULT_ERROR: + clr = PEColor( COLOR_PHONEME_EXTRACTION_RESULT_ERROR ); + break; + case SR_RESULT_SUCCESS: + clr = PEColor( COLOR_PHONEME_EXTRACTION_RESULT_SUCCESS ); + break; + case SR_RESULT_FAILED: + clr = PEColor( COLOR_PHONEME_EXTRACTION_RESULT_FAIL ); + break; + default: + break; + } + + drawHelper.DrawColoredText( font, fontsize, fontweight, clr, rcText, + "Last Extraction Result: %s", GetExtractionResultString( m_nLastExtractionResult ) ); + + OffsetRect( &rcText, 0, fontsize + 1 ); + } + + if ( m_pEvent && !Q_stristr( m_pEvent->GetParameters(), ".wav" ) ) + { + drawHelper.DrawColoredText( font, fontsize, fontweight, PEColor( COLOR_PHONEME_TEXT ), rcText, + "Sound: '%s', file: %s, length %.2f seconds", + m_pEvent->GetParameters(), + m_WorkFile.m_szWaveFile, + m_pWaveFile->GetRunningLength() ); + } + else + { + drawHelper.DrawColoredText( font, fontsize, fontweight, PEColor( COLOR_PHONEME_TEXT ), rcText, + "File: %s, length %.2f seconds", m_WorkFile.m_szWaveFile, m_pWaveFile->GetRunningLength() ); + } + + OffsetRect( &rcText, 0, fontsize + 1 ); + + drawHelper.DrawColoredText( font, fontsize, fontweight, PEColor( COLOR_PHONEME_TEXT ), rcText, + "Number of samples %i at %ikhz (%i bits/sample) %s", (int) (m_pWaveFile->GetRunningLength() * m_pWaveFile->SampleRate() ), m_pWaveFile->SampleRate(), (m_pWaveFile->SampleSize()<<3), m_Tags.GetVoiceDuck() ? "duck other audio" : "no ducking" ); + + OffsetRect( &rcText, 0, fontsize + 1 ); + + drawHelper.DrawColoredText( font, fontsize, fontweight, PEColor( COLOR_PHONEME_TEXT ), rcText, + "[ %i ] Words [ %i ] Phonemes / Zoom %i %%", m_Tags.m_Words.Size(), m_Tags.CountPhonemes(), m_nTimeZoom ); + + if ( m_pEvent ) + { + OffsetRect( &rcText, 0, fontsize + 1 ); + + drawHelper.DrawColoredText( font, fontsize, fontweight, PEColor( COLOR_PHONEME_TEXT ), rcText, + "Event %s", m_pEvent->GetName() ); + } + + OffsetRect( &rcText, 0, fontsize + 1 ); + + drawHelper.DrawColoredText( font, fontsize, fontweight, PEColor( COLOR_PHONEME_TEXT ), rcText, + "Using: %s", GetSpeechAPIName() ); + + + char text[ 4096 ]; + sprintf( text, "Sentence Text: %s", m_Tags.GetText() ); + + int halfwidth = ( rc.right - rc.left ) / 2; + + rcText = rc; + rcText.left = halfwidth; + rcText.top = rcText.bottom + 5; + rcText.right = rcText.left + halfwidth * 0.6; + + drawHelper.CalcTextRect( font, fontsize, fontweight, halfwidth, rcText, text ); + + drawHelper.DrawColoredTextMultiline( font, fontsize, fontweight, PEColor( COLOR_PHONEME_TEXT ), rcText, + text ); + + CWordTag *cw = GetSelectedWord(); + if ( cw ) + { + char wordInfo[ 512 ]; + sprintf( wordInfo, "Word: %s, start %.2f end %.2f, duration %.2f ms phonemes %i", + cw->GetWord(), cw->m_flStartTime, cw->m_flEndTime, 1000.0f * ( cw->m_flEndTime - cw->m_flStartTime ), + cw->m_Phonemes.Size() ); + + int length = drawHelper.CalcTextWidth( font, fontsize, fontweight, wordInfo ); + + OffsetRect( &rcText, 0, ( rcText.bottom - rcText.top ) + 2 ); + + rcText.left = rcText.right - length - 10; + rcText.bottom = rcText.top + fontsize + 1; + + drawHelper.DrawColoredText( font, fontsize, fontweight, PEColor( COLOR_PHONEME_TEXT ), rcText, wordInfo ); + } + + CPhonemeTag *cp = GetSelectedPhoneme(); + if ( cp ) + { + char phonemeInfo[ 512 ]; + sprintf( phonemeInfo, "Phoneme: %s, start %.2f end %.2f, duration %.2f ms", + ConvertPhoneme( cp->GetPhonemeCode() ), cp->GetStartTime(), cp->GetEndTime(), 1000.0f * ( cp->GetEndTime() - cp->GetStartTime() ) ); + + int length = drawHelper.CalcTextWidth( font, fontsize, fontweight, phonemeInfo ); + + OffsetRect( &rcText, 0, ( rcText.bottom - rcText.top ) + 2 ); + + rcText.left = rcText.right - length - 10; + rcText.bottom = rcText.top + fontsize + 1; + + drawHelper.DrawColoredText( font, fontsize, fontweight, PEColor( COLOR_PHONEME_TEXT ), rcText, phonemeInfo ); + } + + // Draw playback rate + { + char sz[ 48 ]; + sprintf( sz, "Speed: %.2fx", m_flPlaybackRate ); + + int length = drawHelper.CalcTextWidth( font, fontsize, fontweight, sz); + + rcText = rc; + rcText.top = rc.bottom + 60; + rcText.bottom = rcText.top + fontsize + 1; + rcText.left = m_pPlaybackRate->x() + m_pPlaybackRate->w() - x(); + rcText.right = rcText.left + length + 2; + + drawHelper.DrawColoredText( font, fontsize, fontweight, + PEColor( COLOR_PHONEME_TEXT ), rcText, sz ); + } + + if ( m_UndoStack.Size() > 0 ) + { + int length = drawHelper.CalcTextWidth( font, fontsize, fontweight, + "Undo levels: %i/%i", m_nUndoLevel, m_UndoStack.Size() ); + + rcText = rc; + rcText.top = rc.bottom + 60; + rcText.bottom = rcText.top + fontsize + 1; + rcText.right -= 5; + rcText.left = rcText.right - length - 10; + + drawHelper.DrawColoredText( font, fontsize, fontweight, PEColor( COLOR_PHONEME_EXTRACTION_RESULT_SUCCESS ), rcText, + "Undo levels: %i/%i", m_nUndoLevel, m_UndoStack.Size() ); + } + + float endfrac = ( m_pWaveFile->GetRunningLength() - starttime ) / ( endtime - starttime ); + if ( endfrac >= 0.0f && endfrac <= 1.0f ) + { + int endpos = ( int ) ( rc.right * endfrac ); + + drawHelper.DrawColoredLine( PEColor( COLOR_PHONEME_WAV_ENDPOINT ), PS_DOT, 2, endpos, rc.top, endpos, rc.bottom - m_nTickHeight ); + } + + DrawPhonemes( drawHelper, rc, m_Tags, 0 ); + + DrawPhonemes( drawHelper, rc, m_TagsExt, 1, false ); + + DrawWords( drawHelper, rc, m_Tags, 0 ); + + DrawWords( drawHelper, rc, m_TagsExt, 1, false ); + + if ( GetMode() == MODE_EMPHASIS ) + { + Emphasis_Redraw( drawHelper, rc ); + } + + DrawScrubHandle( drawHelper ); +} + +#define MOTION_RANGE 3000 +#define MOTION_MAXSTEP 500 +//----------------------------------------------------------------------------- +// Purpose: Brown noise simulates brownian motion centered around 127.5 but we cap the walking +// to just a couple of units +// Input : *buffer - +// count - +// Output : static void +//----------------------------------------------------------------------------- +static void WriteBrownNoise( void *buffer, int count ) +{ + int currentValue = 127500; + int maxValue = currentValue + ( MOTION_RANGE / 2 ); + int minValue = currentValue - ( MOTION_RANGE / 2 ); + + unsigned char *pos = ( unsigned char *)buffer; + + while ( --count >= 0 ) + { + currentValue += random->RandomInt( -MOTION_MAXSTEP, MOTION_MAXSTEP ); + currentValue = min( maxValue, currentValue ); + currentValue = max( minValue, currentValue ); + + // Downsample to 0-255 range + *pos++ = (unsigned char)( ( (float)currentValue / 1000.0f ) + 0.5f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Replace with brownian noice parts of the wav file that we dont' want processed by the +// speech recognizer +// Input : store - +// *format - +// chunkname - +// *buffer - +// buffersize - +//----------------------------------------------------------------------------- +void PhonemeEditor::ResampleChunk( IterateOutputRIFF& store, void *format, int chunkname, char *buffer, int buffersize, int start_silence /*=0*/, int end_silence /*=0*/ ) +{ + WAVEFORMATEX *pFormat = ( WAVEFORMATEX * )format; + Assert( pFormat ); + + if ( pFormat->wFormatTag == WAVE_FORMAT_PCM ) + { + int silience_time = start_silence + end_silence; + + // Leave room for silence at start + end + int resamplesize = buffersize + silience_time * pFormat->nSamplesPerSec; + char *resamplebuffer = new char[ resamplesize + 4 ]; + memset( resamplebuffer, (unsigned char)128, resamplesize + 4 ); + + int startpos = (int)( start_silence * pFormat->nSamplesPerSec ); + + if ( startpos > 0 ) + { + WriteBrownNoise( resamplebuffer, startpos ); + } + + if ( startpos + buffersize < resamplesize ) + { + WriteBrownNoise( &resamplebuffer[ startpos + buffersize ], resamplesize - ( startpos + buffersize ) ); + } + + memcpy( &resamplebuffer[ startpos ], buffer, buffersize ); + + store.ChunkWriteData( resamplebuffer, resamplesize ); + return; + } + + store.ChunkWriteData( buffer, buffersize ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::ReadLinguisticTags( void ) +{ + if ( !m_pWaveFile ) + return; + + CAudioSource *wave = sound->LoadSound( m_WorkFile.m_szWorkingFile ); + if ( !wave ) + return; + + m_Tags.Reset(); + + CSentence *sentence = wave->GetSentence(); + if ( sentence ) + { + // Copy data from sentence to m_Tags + m_Tags.Reset(); + m_Tags = *sentence; + } + + delete wave; +} + +//----------------------------------------------------------------------------- +// Purpose: Switch wave files +// Input : *wavefile - +// force - +//----------------------------------------------------------------------------- +void PhonemeEditor::SetCurrentWaveFile( const char *wavefile, bool force /*=false*/, CChoreoEvent *event /*=NULL*/ ) +{ + // No change? + if ( !force && !stricmp( m_WorkFile.m_szWaveFile, wavefile ) ) + return; + + StopPlayback(); + + if ( GetDirty() ) + { + int retval = mxMessageBox( this, va( "Save current changes to %s", m_WorkFile.m_szWaveFile ), + "Phoneme Editor", MX_MB_QUESTION | MX_MB_YESNOCANCEL ); + + // Cancel + if ( retval == 2 ) + return; + + // Yes + if ( retval == 0 ) + { + CommitChanges(); + } + } + + ClearExtracted(); + + m_Tags.Reset(); + m_TagsExt.Reset(); + + Deselect(); + + if ( m_pWaveFile ) + { + char fn[ 512 ]; + Q_snprintf( fn, sizeof( fn ), "%s%s", m_WorkFile.m_szBasePath, m_WorkFile.m_szWorkingFile ); + filesystem->RemoveFile( fn, "GAME" ); + } + + delete m_pWaveFile; + m_pWaveFile = NULL; + + SetDirty( false ); + + // Set up event and scene + m_pEvent = event; + + // Try an dload new sound + m_pWaveFile = sound->LoadSound( wavefile ); + Q_strncpy( m_WorkFile.m_szWaveFile, wavefile, sizeof( m_WorkFile.m_szWaveFile ) ); + + char fullpath[ 512 ]; + filesystem->RelativePathToFullPath( wavefile, "GAME", fullpath, sizeof( fullpath ) ); + int len = Q_strlen( fullpath ); + int charstocopy = len - Q_strlen( wavefile ) + 1; + m_WorkFile.m_szBasePath[ 0 ] = 0; + if ( charstocopy >= 0 ) + { + Q_strncpy( m_WorkFile.m_szBasePath, fullpath, charstocopy ); + m_WorkFile.m_szBasePath[ charstocopy ] = 0; + } + Q_StripExtension( wavefile, m_WorkFile.m_szWorkingFile, sizeof( m_WorkFile.m_szWorkingFile ) ); + Q_strncat( m_WorkFile.m_szWorkingFile, "_work.wav", sizeof( m_WorkFile.m_szWorkingFile ), COPY_ALL_CHARACTERS ); + + Q_FixSlashes( m_WorkFile.m_szWaveFile ); + Q_FixSlashes( m_WorkFile.m_szWorkingFile ); + Q_FixSlashes( m_WorkFile.m_szBasePath ); + + if ( !m_pWaveFile ) + { + Con_ErrorPrintf( "Couldn't set current .wav file to %s\n", m_WorkFile.m_szWaveFile ); + return; + } + + Con_Printf( "Current .wav file set to %s\n", m_WorkFile.m_szWaveFile ); + + g_pWaveBrowser->SetCurrent( m_WorkFile.m_szWaveFile ); + + // Copy over and overwrite file + FPCopyFile( m_WorkFile.m_szWaveFile, m_WorkFile.m_szWorkingFile, false ); + // Make it writable + MakeFileWriteable( m_WorkFile.m_szWorkingFile ); + + ReadLinguisticTags(); + + Deselect(); + + RepositionHSlider(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : x - +//----------------------------------------------------------------------------- +void PhonemeEditor::MoveTimeSliderToPos( int x ) +{ + m_nLeftOffset = x; + m_pHorzScrollBar->setValue( m_nLeftOffset ); + InvalidateRect( (HWND)m_pHorzScrollBar->getHandle(), NULL, TRUE ); + redraw(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int PhonemeEditor::ComputeHPixelsNeeded( void ) +{ + int pixels = 0; + + if ( m_pWaveFile ) + { + float maxtime = m_pWaveFile->GetRunningLength(); + maxtime += 1.0f; + pixels = (int)( maxtime * GetPixelsPerSecond() ); + } + + return pixels; + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::RepositionHSlider( void ) +{ + int pixelsneeded = ComputeHPixelsNeeded(); + + if ( pixelsneeded <= w2() ) + { + m_pHorzScrollBar->setVisible( false ); + } + else + { + m_pHorzScrollBar->setVisible( true ); + } + + m_pHorzScrollBar->setBounds( 0, GetCaptionHeight(), w2(), 12 ); + + m_pHorzScrollBar->setRange( 0, pixelsneeded ); + m_pHorzScrollBar->setValue( 0 ); + m_nLeftOffset = 0; + + m_pHorzScrollBar->setPagesize( w2() ); + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float PhonemeEditor::GetPixelsPerSecond( void ) +{ + return m_flPixelsPerSecond * GetTimeZoomScale(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float PhonemeEditor::GetTimeZoomScale( void ) +{ + return ( float )m_nTimeZoom / 100.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : scale - +//----------------------------------------------------------------------------- +void PhonemeEditor::SetTimeZoomScale( int scale ) +{ + m_nTimeZoom = scale; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dt - +//----------------------------------------------------------------------------- +void PhonemeEditor::Think( float dt ) +{ + if ( !m_pWaveFile ) + return; + + bool scrubbing = ( m_nDragType == DRAGTYPE_SCRUBBER ) ? true : false; + ScrubThink( dt, scrubbing ); + + if ( m_pMixer && !sound->IsSoundPlaying( m_pMixer ) ) + { + m_pMixer = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +int PhonemeEditor::IsMouseOverBoundary( mxEvent *event ) +{ + int mx, my; + + mx = (short)event->x; + my = (short)event->y; + + // Deterime if phoneme boundary is under the cursor + // + if ( !m_pWaveFile ) + return BOUNDARY_NONE; + + if ( !(event->modifiers & mxEvent::KeyCtrl ) ) + { + return BOUNDARY_NONE; + } + + RECT rc; + GetWorkspaceRect( rc ); + + if ( IsMouseOverPhonemeRow( my ) ) + { + float starttime = m_nLeftOffset / GetPixelsPerSecond(); + float endtime = w2() / GetPixelsPerSecond() + starttime; + + int mouse_tolerance = 3; + + for ( int i = 0; i < m_Tags.m_Words.Size(); i++ ) + { + CWordTag *word = m_Tags.m_Words[ i ]; + + for ( int k = 0; k < word->m_Phonemes.Size(); k++ ) + { + CPhonemeTag *pPhoneme = word->m_Phonemes[ k ]; + + float t1 = pPhoneme->GetStartTime(); + float t2 = pPhoneme->GetEndTime(); + + // Tag it + float frac1 = ( t1 - starttime ) / ( endtime - starttime ); + float frac2 = ( t2 - starttime ) / ( endtime - starttime ); + + int xpos1 = ( int )( frac1 * w2() ); + int xpos2 = ( int )( frac2 * w2() ); + if ( abs( xpos1 - mx ) <= mouse_tolerance || + abs( xpos2 - mx ) <= mouse_tolerance ) + { + return BOUNDARY_PHONEME; + } + } + } + } + + if ( IsMouseOverWordRow( my ) ) + { + float starttime = m_nLeftOffset / GetPixelsPerSecond(); + float endtime = w2() / GetPixelsPerSecond() + starttime; + + int mouse_tolerance = 3; + + for ( int k = 0; k < m_Tags.m_Words.Size(); k++ ) + { + CWordTag *word = m_Tags.m_Words[ k ]; + + float t1 = word->m_flStartTime; + float t2 = word->m_flEndTime; + + // Tag it + float frac1 = ( t1 - starttime ) / ( endtime - starttime ); + float frac2 = ( t2 - starttime ) / ( endtime - starttime ); + + int xpos1 = ( int )( frac1 * w2() ); + int xpos2 = ( int )( frac2 * w2() ); + if ( ( abs( xpos1 - mx ) <= mouse_tolerance ) || + ( abs( xpos2 - mx ) <= mouse_tolerance ) ) + { + return BOUNDARY_WORD; + } + } + } + + return BOUNDARY_NONE; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::DrawFocusRect( char *reason ) +{ + HDC dc = GetDC( NULL ); + + for ( int i = 0; i < m_FocusRects.Size(); i++ ) + { + RECT rc = m_FocusRects[ i ].m_rcFocus; + + ::DrawFocusRect( dc, &rc ); + } + + ReleaseDC( NULL, dc ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &rc - +//----------------------------------------------------------------------------- +void PhonemeEditor::GetWorkspaceRect( RECT &rc ) +{ + GetClientRect( (HWND)getHandle(), &rc ); + + rc.top += TAG_BOTTOM; + rc.bottom = rc.bottom - 75 - MODE_TAB_OFFSET; + + InflateRect( &rc, -1, -1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +//----------------------------------------------------------------------------- +void PhonemeEditor::ShowWordMenu( CWordTag *word, int mx, int my ) +{ + CountSelected(); + + mxPopupMenu *pop = new mxPopupMenu(); + Assert( pop ); + + pop->add( va( "Edit sentence text..." ), IDC_EDITWORDLIST ); + + if ( m_nSelectedWordCount > 0 && word ) + { + pop->addSeparator(); + + pop->add( va( "Delete %s", m_nSelectedWordCount > 1 ? "words" : va( "'%s'", word->GetWord() ) ), IDC_EDIT_DELETEWORD ); + + if ( m_nSelectedWordCount == 1 ) + { + int index = IndexOfWord( word ); + bool valid = false; + if ( index != -1 ) + { + SetClickedPhoneme( index, -1 ); + valid = true; + } + + if ( valid ) + { + pop->add( va( "Edit word '%s'...", word->GetWord() ), IDC_EDIT_WORD ); + + float nextGap = GetTimeGapToNextWord( true, word ); + float prevGap = GetTimeGapToNextWord( false, word ); + + if ( nextGap > MINIMUM_WORD_GAP || + prevGap > MINIMUM_WORD_GAP ) + { + pop->addSeparator(); + if ( prevGap > MINIMUM_WORD_GAP ) + { + pop->add( va( "Insert word before '%s'...", word->GetWord() ), IDC_EDIT_INSERTWORDBEFORE ); + } + if ( nextGap > MINIMUM_WORD_GAP ) + { + pop->add( va( "Insert word after '%s'...", word->GetWord() ), IDC_EDIT_INSERTWORDAFTER ); + } + } + + if ( word->m_Phonemes.Size() == 0 ) + { + pop->addSeparator(); + pop->add( va( "Add phoneme to '%s'...", word->GetWord() ), IDC_EDIT_INSERTFIRSTPHONEMEOFWORD ); + } + + pop->addSeparator(); + pop->add( va( "Select all words after '%s'", word->GetWord() ), IDC_SELECT_WORDSRIGHT ); + pop->add( va( "Select all words before '%s'", word->GetWord() ), IDC_SELECT_WORDSLEFT ); + } + } + } + + if ( AreSelectedWordsContiguous() && m_nSelectedWordCount > 1 ) + { + pop->addSeparator(); + pop->add( va( "Merge words" ), IDC_SNAPWORDS ); + + if ( m_nSelectedWordCount == 2 ) + { + pop->add( va( "Separate words" ), IDC_SEPARATEWORDS ); + } + } + + if ( m_nSelectedWordCount > 0 ) + { + pop->addSeparator(); + + pop->add( va( "Deselect all" ), IDC_DESELECT_PHONEMESANDWORDS ); + } + + if ( m_Tags.m_Words.Size() > 0 ) + { + pop->addSeparator(); + pop->add( va( "Cleanup words/phonemes" ), IDC_CLEANUP ); + } + + if ( m_Tags.m_Words.Size() > 0 ) + { + pop->addSeparator(); + pop->add( va( "Realign phonemes to words" ), IDC_REALIGNPHONEMES ); + } + + + pop->popup( this, mx, my ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +//----------------------------------------------------------------------------- +void PhonemeEditor::ShowPhonemeMenu( CPhonemeTag *pho, int mx, int my ) +{ + CountSelected(); + + SetClickedPhoneme( -1, -1 ); + + if ( !pho ) + return; + + if ( m_Tags.CountPhonemes() == 0 ) + { + Con_Printf( "No phonemes, try extracting from .wav first\n" ); + return; + } + + mxPopupMenu *pop = new mxPopupMenu(); + bool valid = false; + CWordTag *tag = m_Tags.GetWordForPhoneme( pho ); + if ( tag ) + { + int wordNum = IndexOfWord( tag ); + int pi = tag->IndexOfPhoneme( pho ); + + SetClickedPhoneme( wordNum, pi ); + valid = true; + } + + if ( valid ) + { + if ( m_nSelectedPhonemeCount == 1 ) + { + pop->add( va( "Edit '%s'...", ConvertPhoneme( pho->GetPhonemeCode() ) ), IDC_EDIT_PHONEME ); + + float nextGap = GetTimeGapToNextPhoneme( true, pho ); + float prevGap = GetTimeGapToNextPhoneme( false, pho ); + + if ( nextGap > MINIMUM_PHONEME_GAP || + prevGap > MINIMUM_PHONEME_GAP ) + { + pop->addSeparator(); + if ( prevGap > MINIMUM_PHONEME_GAP ) + { + pop->add( va( "Insert phoneme before '%s'...", ConvertPhoneme( pho->GetPhonemeCode() ) ), IDC_EDIT_INSERTPHONEMEBEFORE ); + } + if ( nextGap > MINIMUM_PHONEME_GAP ) + { + pop->add( va( "Insert phoneme after '%s'...", ConvertPhoneme( pho->GetPhonemeCode() ) ), IDC_EDIT_INSERTPHONEMEAFTER ); + } + } + + pop->addSeparator(); + pop->add( va( "Select all phonemes after '%s'", ConvertPhoneme( pho->GetPhonemeCode() ) ), IDC_SELECT_PHONEMESRIGHT ); + pop->add( va( "Select all phonemes before '%s'",ConvertPhoneme( pho->GetPhonemeCode() ) ), IDC_SELECT_PHONEMESLEFT ); + + pop->addSeparator(); + } + + if ( AreSelectedPhonemesContiguous() && m_nSelectedPhonemeCount > 1 ) + { + pop->add( va( "Merge phonemes" ), IDC_SNAPPHONEMES ); + if ( m_nSelectedPhonemeCount == 2 ) + { + pop->add( va( "Separate phonemes" ), IDC_SEPARATEPHONEMES ); + } + + pop->addSeparator(); + } + + if ( m_nSelectedPhonemeCount >= 1 ) + { + pop->add( va( "Delete %s", + m_nSelectedPhonemeCount == 1 ? va( "'%s'", ConvertPhoneme( pho->GetPhonemeCode() ) ) : "phonemes" ), IDC_EDIT_DELETEPHONEME ); + + pop->addSeparator(); + pop->add( va( "Deselect all" ), IDC_DESELECT_PHONEMESANDWORDS ); + } + } + + + if ( m_Tags.m_Words.Size() > 0 ) + { + pop->addSeparator(); + pop->add( va( "Cleanup words/phonemes" ), IDC_CLEANUP ); + } + + if ( m_Tags.m_Words.Size() > 0 ) + { + pop->addSeparator(); + pop->add( va( "Realign words to phonemes" ), IDC_REALIGNWORDS ); + } + + pop->popup( this, mx, my ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// Output : float +//----------------------------------------------------------------------------- +float PhonemeEditor::GetTimeForPixel( int mx ) +{ + RECT rc; + GetWorkspaceRect( rc ); + + float starttime = m_nLeftOffset / GetPixelsPerSecond(); + float time = (float)mx / GetPixelsPerSecond() + starttime; + + return time; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : time - +// **pp1 - +// **pp2 - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool PhonemeEditor::FindSpanningPhonemes( float time, CPhonemeTag **pp1, CPhonemeTag **pp2 ) +{ + Assert( pp1 && pp2 ); + + *pp1 = NULL; + *pp2 = NULL; + + // Three pixels + double time_epsilon = ( 1.0f / GetPixelsPerSecond() ) * 3; + + CPhonemeTag *previous = NULL; + + for ( int w = 0; w < m_Tags.m_Words.Size(); w++ ) + { + CWordTag *word = m_Tags.m_Words[ w ]; + + for ( int i = 0; i < word->m_Phonemes.Size(); i++ ) + { + CPhonemeTag *current = word->m_Phonemes[ i ]; + double dt; + + if ( !previous ) + { + dt = fabs( current->GetStartTime() - time ); + if ( dt < time_epsilon ) + { + *pp2 = current; + return true; + } + } + else + { + int found = 0; + + dt = fabs( previous->GetEndTime() - time ); + if ( dt < time_epsilon ) + { + *pp1 = previous; + found++; + } + + dt = fabs( current->GetStartTime() - time ); + if ( dt < time_epsilon ) + { + *pp2 = current; + found++; + } + + if ( found != 0 ) + { + return true; + } + } + + previous = current; + } + } + + if ( m_Tags.m_Words.Size() > 0 ) + { + // Check last word, but only if it has some phonemes + CWordTag *lastWord = m_Tags.m_Words[ m_Tags.m_Words.Size() - 1 ]; + if ( lastWord && + ( lastWord->m_Phonemes.Size() > 0 ) ) + { + + CPhonemeTag *last = lastWord->m_Phonemes[ lastWord->m_Phonemes.Size() - 1 ]; + float dt; + dt = fabs( last->GetEndTime() - time ); + if ( dt < time_epsilon ) + { + *pp1 = last; + return true; + } + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : time - +// **pp1 - +// **pp2 - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool PhonemeEditor::FindSpanningWords( float time, CWordTag **pp1, CWordTag **pp2 ) +{ + Assert( pp1 && pp2 ); + + *pp1 = NULL; + *pp2 = NULL; + + // Three pixels + double time_epsilon = ( 1.0f / GetPixelsPerSecond() ) * 3; + + CWordTag *previous = NULL; + for ( int i = 0; i < m_Tags.m_Words.Size(); i++ ) + { + CWordTag *current = m_Tags.m_Words[ i ]; + double dt; + + if ( !previous ) + { + dt = fabs( current->m_flStartTime - time ); + if ( dt < time_epsilon ) + { + *pp2 = current; + return true; + } + } + else + { + int found = 0; + + dt = fabs( previous->m_flEndTime - time ); + if ( dt < time_epsilon ) + { + *pp1 = previous; + found++; + } + + dt = fabs( current->m_flStartTime - time ); + if ( dt < time_epsilon ) + { + *pp2 = current; + found++; + } + + if ( found != 0 ) + { + return true; + } + } + + previous = current; + } + + if ( m_Tags.m_Words.Size() > 0 ) + { + CWordTag *last = m_Tags.m_Words[ m_Tags.m_Words.Size() - 1 ]; + float dt; + dt = fabs( last->m_flEndTime - time ); + if ( dt < time_epsilon ) + { + *pp1 = last; + return true; + } + } + + return false; +} + +int PhonemeEditor::FindWordForTime( float time ) +{ + for ( int i = 0; i < m_Tags.m_Words.Size(); i++ ) + { + CWordTag *pCurrent = m_Tags.m_Words[ i ]; + + if ( time < pCurrent->m_flStartTime ) + continue; + + if ( time > pCurrent->m_flEndTime ) + continue; + + return i; + } + + return -1; +} + +void PhonemeEditor::FinishWordDrag( int startx, int endx ) +{ + float clicktime = GetTimeForPixel( startx ); + float endtime = GetTimeForPixel( endx ); + + float dt = endtime - clicktime; + + SetDirty( true ); + + PushUndo(); + + TraverseWords( &PhonemeEditor::ITER_MoveSelectedWords, dt ); + + RealignPhonemesToWords( false ); + CleanupWordsAndPhonemes( false ); + + PushRedo(); + + redraw(); +} + +void PhonemeEditor::FinishWordMove( int startx, int endx ) +{ + float clicktime = GetTimeForPixel( startx ); + float endtime = GetTimeForPixel( endx ); + + // Find the phonemes who have the closest start/endtime to the starting click time + CWordTag *current, *next; + + if ( !FindSpanningWords( clicktime, ¤t, &next ) ) + { + return; + } + + SetDirty( true ); + + PushUndo(); + + if ( current && !next ) + { + // cap movement + current->m_flEndTime += ( endtime - clicktime ); + } + else if ( !current && next ) + { + // cap movement + next->m_flStartTime += ( endtime - clicktime ); + } + else + { + // cap movement + endtime = min( endtime, next->m_flEndTime - 1.0f / GetPixelsPerSecond() ); + endtime = max( endtime, current->m_flStartTime + 1.0f / GetPixelsPerSecond() ); + + current->m_flEndTime = endtime; + next->m_flStartTime = endtime; + } + + RealignPhonemesToWords( false ); + CleanupWordsAndPhonemes( false ); + + PushRedo(); + + redraw(); +} + +CPhonemeTag *PhonemeEditor::FindPhonemeForTime( float time ) +{ + for ( int w = 0 ; w < m_Tags.m_Words.Size(); w++ ) + { + CWordTag *word = m_Tags.m_Words[ w ]; + + + for ( int i = 0; i < word->m_Phonemes.Size(); i++ ) + { + CPhonemeTag *pCurrent = word->m_Phonemes[ i ]; + + if ( time < pCurrent->GetStartTime() ) + continue; + + if ( time > pCurrent->GetEndTime() ) + continue; + + return pCurrent; + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : phoneme - +// startx - +// endx - +//----------------------------------------------------------------------------- +void PhonemeEditor::FinishPhonemeDrag( int startx, int endx ) +{ + float clicktime = GetTimeForPixel( startx ); + float endtime = GetTimeForPixel( endx ); + + float dt = endtime - clicktime; + + SetDirty( true ); + + PushUndo(); + + TraversePhonemes( &PhonemeEditor::ITER_MoveSelectedPhonemes, dt ); + + RealignWordsToPhonemes( false ); + CleanupWordsAndPhonemes( false ); + + PushRedo(); + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : phoneme - +// startx - +// endx - +//----------------------------------------------------------------------------- +void PhonemeEditor::FinishPhonemeMove( int startx, int endx ) +{ + float clicktime = GetTimeForPixel( startx ); + float endtime = GetTimeForPixel( endx ); + + // Find the phonemes who have the closest start/endtime to the starting click time + CPhonemeTag *current, *next; + + if ( !FindSpanningPhonemes( clicktime, ¤t, &next ) ) + { + return; + } + + SetDirty( true ); + + PushUndo(); + + if ( current && !next ) + { + // cap movement + current->AddEndTime( endtime - clicktime ); + } + else if ( !current && next ) + { + // cap movement + next->AddStartTime( endtime - clicktime ); + } + else + { + // cap movement + endtime = min( endtime, next->GetEndTime() - 1.0f / GetPixelsPerSecond() ); + endtime = max( endtime, current->GetStartTime() + 1.0f / GetPixelsPerSecond() ); + + current->SetEndTime( endtime ); + next->SetStartTime( endtime ); + } + + RealignWordsToPhonemes( false ); + CleanupWordsAndPhonemes( false ); + + PushRedo(); + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dirty - +//----------------------------------------------------------------------------- +void PhonemeEditor::SetDirty( bool dirty, bool clearundo /*=true*/ ) +{ + m_WorkFile.m_bDirty = dirty; + + if ( !dirty && clearundo ) + { + WipeUndo(); + redraw(); + } + + SetPrefix( dirty ? "* " : "" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool PhonemeEditor::GetDirty( void ) +{ + return m_WorkFile.m_bDirty; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::EditInsertPhonemeBefore( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + CPhonemeTag *cp = GetSelectedPhoneme(); + if ( !cp ) + return; + + float gap = GetTimeGapToNextPhoneme( false, cp ); + if ( gap < MINIMUM_PHONEME_GAP ) + { + Con_Printf( "Can't insert before, gap of %.2f ms is too small\n", 1000.0f * gap ); + return; + } + + // Don't have really long phonemes + gap = min( gap, DEFAULT_PHONEME_LENGTH ); + + CWordTag *word = m_Tags.GetWordForPhoneme( cp ); + if ( !word ) + { + Con_Printf( "EditInsertPhonemeBefore: phoneme not a member of any known word!!!\n" ); + return; + } + + int clicked = word->IndexOfPhoneme( cp ); + if ( clicked < 0 ) + { + Con_Printf( "EditInsertPhonemeBefore: phoneme not a member of any specified word!!!\n" ); + Assert( 0 ); + return; + } + + CPhonemeTag phoneme; + + CPhonemeParams params; + memset( ¶ms, 0, sizeof( params ) ); + strcpy( params.m_szDialogTitle, "Phoneme/Viseme Properties" ); + strcpy( params.m_szName, "" ); + + int iret = PhonemeProperties( ¶ms ); + SetFocus( (HWND)getHandle() ); + if ( !iret ) + { + return; + } + + SetDirty( true ); + + PushUndo(); + + phoneme.SetPhonemeCode( TextToPhoneme( params.m_szName ) ); + phoneme.SetTag( params.m_szName ); + + phoneme.SetEndTime( cp->GetStartTime() ); + phoneme.SetStartTime( cp->GetStartTime() - gap ); + phoneme.m_bSelected = true; + cp->m_bSelected = false; + + word->m_Phonemes.InsertBefore( clicked, new CPhonemeTag( phoneme ) ); + + PushRedo(); + + // Add it + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::EditInsertPhonemeAfter( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + CPhonemeTag *cp = GetSelectedPhoneme(); + if ( !cp ) + return; + + float gap = GetTimeGapToNextPhoneme( true, cp ); + if ( gap < MINIMUM_PHONEME_GAP ) + { + Con_Printf( "Can't insert after, gap of %.2f ms is too small\n", 1000.0f * gap ); + return; + } + + // Don't have really long phonemes + gap = min( gap, DEFAULT_PHONEME_LENGTH ); + + CWordTag *word = m_Tags.GetWordForPhoneme( cp ); + if ( !word ) + { + Con_Printf( "EditInsertPhonemeAfter: phoneme not a member of any known word!!!\n" ); + return; + } + + int clicked = word->IndexOfPhoneme( cp ); + if ( clicked < 0 ) + { + Con_Printf( "EditInsertPhonemeAfter: phoneme not a member of any specified word!!!\n" ); + Assert( 0 ); + return; + } + + CPhonemeTag phoneme; + + CPhonemeParams params; + memset( ¶ms, 0, sizeof( params ) ); + strcpy( params.m_szDialogTitle, "Phoneme/Viseme Properties" ); + strcpy( params.m_szName, "" ); + + int iret = PhonemeProperties( ¶ms ); + SetFocus( (HWND)getHandle() ); + + if ( !iret ) + { + return; + } + + SetDirty( true ); + + PushUndo(); + + phoneme.SetPhonemeCode( TextToPhoneme( params.m_szName ) ); + phoneme.SetTag( params.m_szName ); + + phoneme.SetEndTime( cp->GetEndTime() + gap ); + phoneme.SetStartTime( cp->GetEndTime() ); + phoneme.m_bSelected = true; + cp->m_bSelected = false; + + word->m_Phonemes.InsertAfter( clicked, new CPhonemeTag( phoneme ) ); + + PushRedo(); + + // Add it + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::EditInsertWordBefore( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + CWordTag *cw = GetSelectedWord(); + if ( !cw ) + return; + + float gap = GetTimeGapToNextWord( false, cw ); + if ( gap < MINIMUM_WORD_GAP ) + { + Con_Printf( "Can't insert before, gap of %.2f ms is too small\n", 1000.0f * gap ); + return; + } + + // Don't have really long words + gap = min( gap, DEFAULT_WORD_LENGTH ); + + int clicked = IndexOfWord( cw ); + if ( clicked < 0 ) + { + Con_Printf( "EditInsertWordBefore: word not in sentence!!!\n" ); + Assert( 0 ); + return; + } + + CInputParams params; + memset( ¶ms, 0, sizeof( params ) ); + strcpy( params.m_szDialogTitle, "Insert Word" ); + strcpy( params.m_szPrompt, "Word:" ); + strcpy( params.m_szInputText, "" ); + + params.m_nLeft = -1; + params.m_nTop = -1; + + params.m_bPositionDialog = true; + if ( params.m_bPositionDialog ) + { + RECT rcWord; + GetWordRect( cw, rcWord ); + + // Convert to screen coords + POINT pt; + pt.x = rcWord.left; + pt.y = rcWord.top; + + ClientToScreen( (HWND)getHandle(), &pt ); + + params.m_nLeft = pt.x; + params.m_nTop = pt.y; + } + + int iret = InputProperties( ¶ms ); + SetFocus( (HWND)getHandle() ); + if ( !iret ) + { + return; + } + + if ( strlen( params.m_szInputText ) <= 0 ) + { + return; + } + + int wordCount = CSentence::CountWords( params.m_szInputText ); + if ( wordCount > 1 ) + { + Con_Printf( "Can only insert one word at a time, %s has %i words in it!\n", + params.m_szInputText, wordCount ); + return; + } + + SetDirty( true ); + + PushUndo(); + + CWordTag newword; + + newword.SetWord( params.m_szInputText ); + + newword.m_flEndTime = cw->m_flStartTime; + newword.m_flStartTime = cw->m_flStartTime - gap; + newword.m_bSelected = true; + cw->m_bSelected = false; + + m_Tags.m_Words.InsertBefore( clicked, new CWordTag( newword ) ); + + PushRedo(); + + // Add it + redraw(); + + // Jump to phoneme insertion UI + EditInsertFirstPhonemeOfWord(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::EditInsertWordAfter( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + CWordTag *cw = GetSelectedWord(); + if ( !cw ) + return; + + float gap = GetTimeGapToNextWord( true, cw ); + if ( gap < MINIMUM_WORD_GAP ) + { + Con_Printf( "Can't insert after, gap of %.2f ms is too small\n", 1000.0f * gap ); + return; + } + + // Don't have really long words + gap = min( gap, DEFAULT_WORD_LENGTH ); + + int clicked = IndexOfWord( cw ); + if ( clicked < 0 ) + { + Con_Printf( "EditInsertWordBefore: word not in sentence!!!\n" ); + Assert( 0 ); + return; + } + + CInputParams params; + memset( ¶ms, 0, sizeof( params ) ); + strcpy( params.m_szDialogTitle, "Insert Word" ); + strcpy( params.m_szPrompt, "Word:" ); + strcpy( params.m_szInputText, "" ); + + params.m_nLeft = -1; + params.m_nTop = -1; + + params.m_bPositionDialog = true; + if ( params.m_bPositionDialog ) + { + RECT rcWord; + GetWordRect( cw, rcWord ); + + // Convert to screen coords + POINT pt; + pt.x = rcWord.left; + pt.y = rcWord.top; + + ClientToScreen( (HWND)getHandle(), &pt ); + + params.m_nLeft = pt.x; + params.m_nTop = pt.y; + } + + int iret = InputProperties( ¶ms ); + SetFocus( (HWND)getHandle() ); + if ( !iret ) + { + return; + } + + if ( strlen( params.m_szInputText ) <= 0 ) + { + return; + } + + int wordCount = CSentence::CountWords( params.m_szInputText ); + if ( wordCount > 1 ) + { + Con_Printf( "Can only insert one word at a time, %s has %i words in it!\n", + params.m_szInputText, wordCount ); + return; + } + + SetDirty( true ); + + PushUndo(); + + CWordTag newword; + + newword.SetWord( params.m_szInputText ); + + newword.m_flEndTime = cw->m_flEndTime + gap; + newword.m_flStartTime = cw->m_flEndTime; + newword.m_bSelected = true; + cw->m_bSelected = false; + + CWordTag *w = new CWordTag( newword ); + Assert( w ); + if ( w ) + { + m_Tags.m_Words.InsertAfter( clicked, w ); + } + + PushRedo(); + + // Add it + redraw(); + + EditInsertFirstPhonemeOfWord(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::EditDeletePhoneme( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + CountSelected(); + + if ( m_nSelectedPhonemeCount < 1 ) + { + return; + } + + SetDirty( true ); + + PushUndo(); + + for ( int i = m_Tags.m_Words.Size() - 1; i >= 0; i-- ) + { + CWordTag *word = m_Tags.m_Words[ i ]; + if ( !word ) + continue; + + for ( int j = word->m_Phonemes.Size() - 1; j >= 0; j-- ) + { + CPhonemeTag *p = word->m_Phonemes[ j ]; + if ( !p || !p->m_bSelected ) + continue; + + // Delete it + word->m_Phonemes.Remove( j ); + } + } + + PushRedo(); + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::EditDeleteWord( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + CountSelected(); + + if ( m_nSelectedWordCount < 1 ) + { + return; + } + + SetDirty( true ); + + PushUndo(); + + for ( int i = m_Tags.m_Words.Size() - 1; i >= 0; i-- ) + { + CWordTag *word = m_Tags.m_Words[ i ]; + if ( !word || !word->m_bSelected ) + continue; + + m_Tags.m_Words.Remove( i ); + } + + PushRedo(); + + redraw(); +} +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::PlayEditedWave( bool selection /* = false */ ) +{ + StopPlayback(); + + if ( !m_pWaveFile ) + return; + + // Make sure phonemes are loaded + FacePoser_EnsurePhonemesLoaded(); + + SaveLinguisticData(); + + SetScrubTime( 0.0f ); + SetScrubTargetTime( m_pWaveFile->GetRunningLength() ); +} + +typedef struct channel_s +{ + int leftvol; + int rightvol; + int rleftvol; + int rrightvol; + float pitch; +} channel_t; + +bool PhonemeEditor::CreateCroppedWave( char const *filename, int startsample, int endsample ) +{ + Assert( sound ); + + CAudioWaveOutput *pWaveOutput = ( CAudioWaveOutput * )sound->GetAudioOutput(); + if ( !pWaveOutput ) + return false; + + CAudioSource *wave = sound->LoadSound( m_WorkFile.m_szWaveFile ); + if ( !wave ) + return false; + + CAudioMixer *pMixer = wave->CreateMixer(); + if ( !pMixer ) + return false; + + // Create out put file + OutFileRIFF riffout( filename, io_out ); + // Create output iterator + IterateOutputRIFF store( riffout ); + + WAVEFORMATEX format; + format.cbSize = sizeof( format ); + + format.wFormatTag = WAVE_FORMAT_PCM; + format.nAvgBytesPerSec = (int)wave->SampleRate(); + format.nChannels = 1; + format.wBitsPerSample = 8; + format.nSamplesPerSec = (int)wave->SampleRate(); + format.nBlockAlign = 1; // (int)wave->SampleSize(); + + store.ChunkWrite( WAVE_FMT, &format, sizeof( format ) ); + + // Pull in data and write it out + + int currentsample = 0; + + store.ChunkStart( WAVE_DATA ); + + // need a bit of space + short samples[ 2 ]; + channel_t channel; + channel.leftvol = 255; + channel.rightvol = 255; + channel.pitch = 1.0; + + while ( 1 ) + { + pWaveOutput->m_audioDevice.MixBegin(); + + if ( !pMixer->MixDataToDevice( &pWaveOutput->m_audioDevice, &channel, currentsample, 1, wave->SampleRate(), true ) ) + break; + + pWaveOutput->m_audioDevice.TransferBufferStereo16( samples, 1 ); + + currentsample = pMixer->GetSamplePosition(); + + if ( currentsample >= startsample && currentsample <= endsample ) + { + // left + right (2 channels ) * 16 bits + float s1 = (float)( samples[ 0 ] >> 8 ); + float s2 = (float)( samples[ 1 ] >> 8 ); + + float avg = ( s1 + s2 ) / 2.0f; + unsigned char chopped = (unsigned char)( avg + 127.0f ); + + store.ChunkWriteData( &chopped, sizeof( byte ) ); + } + } + + store.ChunkFinish(); + + delete pMixer; + delete wave; + + return true; +} + +void PhonemeEditor::SentenceFromString( CSentence& sentence, char const *str ) +{ + sentence.Reset(); + + if ( !str || !str[0] || CSentence::CountWords( str ) == 0 ) + { + return; + } + + char word[ 256 ]; + unsigned char const *in = (unsigned char *)str; + char *out = word; + + while ( *in ) + { + if ( *in > 32 ) + { + *out++ = *in++; + } + else + { + *out = 0; + + while ( *in && *in <= 32 ) + { + in++; + } + + if ( strlen( word ) > 0 ) + { + CWordTag *w = new CWordTag( (char *)word ); + Assert( w ); + if ( w ) + { + sentence.m_Words.AddToTail( w ); + } + } + + out = word; + } + } + + *out = 0; + if ( strlen( word ) > 0 ) + { + CWordTag *w = new CWordTag( (char *)word ); + Assert( w ); + if ( w ) + { + sentence.m_Words.AddToTail( w ); + } + } + + sentence.SetText( str ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::RedoPhonemeExtractionSelected( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + if ( !CheckSpeechAPI() ) + return; + + if ( !m_pWaveFile ) + { + Con_Printf( "Can't redo extraction, no wavefile loaded!\n" ); + Assert( 0 ); + return; + } + + if ( !m_bSelectionActive ) + { + Con_Printf( "Please select a portion of the .wav from which to re-extract phonemes\n" ); + return; + } + + // Now copy data back into original list, offsetting by samplestart time + float numsamples = m_pWaveFile->GetRunningLength() * m_pWaveFile->SampleRate(); + float selectionstarttime = 0.0f; + if ( numsamples > 0.0f ) + { + // Convert sample #'s to time + selectionstarttime = ( m_nSelection[ 0 ] / numsamples ) * m_pWaveFile->GetRunningLength(); + selectionstarttime = max( 0.0f, selectionstarttime ); + } + else + { + Con_Printf( "Original .wav file %s has no samples!!!\n", m_WorkFile.m_szWaveFile ); + return; + } + + int i; + // Create input array of just selected words + CSentence m_InputWords; + CSentence m_Results; + + CountSelected(); + + bool usingselection = true; + + if ( m_nSelectedWordCount == 0 ) + { + // Allow user to type in text + // Build word string + char wordstring[ 1024 ]; + strcpy( wordstring, "" ); + + CInputParams params; + memset( ¶ms, 0, sizeof( params ) ); + strcpy( params.m_szDialogTitle, "Phrase Word List" ); + strcpy( params.m_szPrompt, "Phrase" ); + + strcpy( params.m_szInputText, wordstring ); + + if ( !InputProperties( ¶ms ) ) + return; + + if ( strlen( params.m_szInputText ) <= 0 ) + { + Con_ErrorPrintf( "Edit word list: No words entered!\n" ); + return; + } + + SentenceFromString( m_InputWords, params.m_szInputText ); + + if ( m_InputWords.m_Words.Size() == 0 ) + { + Con_Printf( "You must either select words, or type in a set of words in order to extract phonemes!\n" ); + return; + } + + usingselection = false; + } + else + { + if ( !AreSelectedWordsContiguous() ) + { + Con_Printf( "Can only redo extraction on a contiguous subset of words\n" ); + return; + } + + char temp[ 4096 ]; + bool killspace = false; + Q_strncpy( temp, m_InputWords.GetText(), sizeof( temp ) ); + + // Iterate existing words, looking for contiguous selected words + for ( i = 0; i < m_Tags.m_Words.Size(); i++ ) + { + CWordTag *word = m_Tags.m_Words[ i ]; + if ( !word || !word->m_bSelected ) + continue; + + // Now add "clean slate" to input list + m_InputWords.m_Words.AddToTail( new CWordTag( *word ) ); + + Q_strncat( temp, word->GetWord(), sizeof( temp ), COPY_ALL_CHARACTERS ); + Q_strncat( temp, " ", sizeof( temp ), COPY_ALL_CHARACTERS ); + killspace = true; + } + + // Kill terminal space character + int len = Q_strlen( temp ); + if ( killspace && ( len >= 1 ) ) + { + Assert( temp[ len -1 ] == ' ' ); + temp[ len - 1 ] = 0; + } + + m_InputWords.SetText( temp ); + } + + m_nLastExtractionResult = SR_RESULT_NORESULT; + + char szCroppedFile[ 512 ]; + char szBaseFile[ 512 ]; + Q_StripExtension( m_WorkFile.m_szWaveFile, szBaseFile, sizeof( szBaseFile ) ); + Q_snprintf( szCroppedFile, sizeof( szCroppedFile ), "%s%s_work1.wav", m_WorkFile.m_szBasePath, szBaseFile ); + + filesystem->RemoveFile( szCroppedFile, "GAME" ); + + if ( !CreateCroppedWave( szCroppedFile, m_nSelection[ 0 ], m_nSelection[ 1 ] ) ) + { + Con_Printf( "Unable to create cropped wave file %s from samples %i to %i\n", + szCroppedFile, + m_nSelection[ 0 ], + m_nSelection[ 1 ] ); + return; + } + + CAudioSource *m_pCroppedWave = sound->LoadSound( szCroppedFile ); + if ( !m_pCroppedWave ) + { + Con_Printf( "Unable to load cropped wave file %s from samples %i to %i\n", + szCroppedFile, + m_nSelection[ 0 ], + m_nSelection[ 1 ] ); + return; + } + + // Save any pending stuff + SaveLinguisticData(); + + // Store off copy of complete sentence + m_TagsExt = m_Tags; + + char filename[ 512 ]; + Q_snprintf( filename, sizeof( filename ), "%s%s", m_WorkFile.m_szBasePath, szCroppedFile ); + + m_nLastExtractionResult = m_pPhonemeExtractor->Extract( + filename, + (int)( m_pCroppedWave->GetRunningLength() * m_pCroppedWave->SampleRate() * m_pCroppedWave->TrueSampleSize() ), + Con_Printf, + m_InputWords, + m_Results ); + + if ( m_InputWords.m_Words.Size() != m_Results.m_Words.Size() ) + { + Con_Printf( "Extraction returned %i words, source had %i, try adjusting selection\n", + m_Results.m_Words.Size(), m_InputWords.m_Words.Size() ); + + filesystem->RemoveFile( filename, "GAME" ); + + redraw(); + return; + } + + float bytespersecond = m_pCroppedWave->SampleRate() * m_pCroppedWave->TrueSampleSize(); + + // Tracker 57389: + // Total hack to fix a bug where the Lipsinc extractor is messing up the # channels on 16 bit stereo waves + if ( m_pPhonemeExtractor->GetAPIType() == SPEECH_API_LIPSINC && + m_pCroppedWave->IsStereoWav() && + m_pCroppedWave->SampleSize() == 16 ) + { + bytespersecond *= 2.0f; + } + + // Now convert byte offsets to times + for ( i = 0; i < m_Results.m_Words.Size(); i++ ) + { + CWordTag *tag = m_Results.m_Words[ i ]; + Assert( tag ); + if ( !tag ) + continue; + + tag->m_flStartTime = ( float )(tag->m_uiStartByte ) / bytespersecond; + tag->m_flEndTime = ( float )(tag->m_uiEndByte ) / bytespersecond; + + for ( int j = 0; j < tag->m_Phonemes.Size(); j++ ) + { + CPhonemeTag *ptag = tag->m_Phonemes[ j ]; + Assert( ptag ); + if ( !ptag ) + continue; + + ptag->SetStartTime( ( float )(ptag->m_uiStartByte ) / bytespersecond ); + ptag->SetEndTime( ( float )(ptag->m_uiEndByte ) / bytespersecond ); + } + } + + if ( usingselection ) + { + // Copy data into m_TagsExt, offseting times by selectionstarttime + CWordTag *from; + CWordTag *to; + + int fromWord = 0; + + for ( i = 0; i < m_TagsExt.m_Words.Size() ; i++ ) + { + to = m_TagsExt.m_Words[ i ]; + if ( !to || !to->m_bSelected ) + continue; + + // Found start of contiguous run + if ( fromWord >= m_Results.m_Words.Size() ) + break; + + from = m_Results.m_Words[ fromWord++ ]; + Assert( from ); + if ( !from ) + continue; + + // Remove all phonemes from destination + while ( to->m_Phonemes.Size() > 0 ) + { + CPhonemeTag *p = to->m_Phonemes[ 0 ]; + Assert( p ); + to->m_Phonemes.Remove( 0 ); + delete p; + } + + // Now copy phonemes from source + for ( int j = 0; j < from->m_Phonemes.Size(); j++ ) + { + CPhonemeTag *fromPhoneme = from->m_Phonemes[ j ]; + Assert( fromPhoneme ); + if ( !fromPhoneme ) + continue; + + CPhonemeTag newPhoneme( *fromPhoneme ); + // Offset start time + newPhoneme.AddStartTime( selectionstarttime ); + newPhoneme.AddEndTime( selectionstarttime ); + + // Add it back in with corrected timing data + CPhonemeTag *p = new CPhonemeTag( newPhoneme ); + Assert( p ); + if ( p ) + { + to->m_Phonemes.AddToTail( p ); + } + } + + // Done + if ( fromWord >= m_Results.m_Words.Size() ) + break; + } + + } + else + { + // Find word just before starting point of selection and + // place input words into list starting that that point + + int startWord = 0; + + CWordTag *firstWordOfPhrase = m_Results.m_Words[ 0 ]; + Assert( firstWordOfPhrase ); + + for ( ; startWord < m_TagsExt.m_Words.Size(); startWord++ ) + { + CWordTag *w = m_TagsExt.m_Words[ startWord ]; + Assert( w ); + if ( !w ) + continue; + + if ( w->m_flStartTime > firstWordOfPhrase->m_flStartTime + selectionstarttime ) + break; + } + + for ( i = 0; i < m_Results.m_Words.Size(); i++ ) + { + CWordTag *from = m_Results.m_Words[ i ]; + Assert( from ); + if ( !from ) + continue; + + CWordTag *to = new CWordTag( *from ); + Assert( to ); + + to->m_flStartTime += selectionstarttime; + to->m_flEndTime += selectionstarttime; + + // Now adjust phoneme times + for ( int j = 0; j < to->m_Phonemes.Size(); j++ ) + { + CPhonemeTag *toPhoneme = to->m_Phonemes[ j ]; + Assert( toPhoneme ); + if ( !toPhoneme ) + continue; + + // Offset start time + toPhoneme->AddStartTime( selectionstarttime ); + toPhoneme->AddEndTime( selectionstarttime ); + } + + m_TagsExt.m_Words.InsertBefore( startWord++, to ); + } + } + + Con_Printf( "Cleaning up...\n" ); + filesystem->RemoveFile( filename, "GAME" ); + + SetFocus( (HWND)getHandle() ); + + redraw(); +} + +void PhonemeEditor::RedoPhonemeExtraction( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + if ( !CheckSpeechAPI() ) + return; + + m_nLastExtractionResult = SR_RESULT_NORESULT; + + if ( !m_pWaveFile ) + return; + + SaveLinguisticData(); + + // Send m_WorkFile.m_szWorkingFile to extractor and retrieve resulting data + // + + m_TagsExt.Reset(); + + Assert( m_pPhonemeExtractor ); + + char filename[ 512 ]; + Q_snprintf( filename, sizeof( filename ), "%s%s", m_WorkFile.m_szBasePath, m_WorkFile.m_szWorkingFile ); + + m_nLastExtractionResult = m_pPhonemeExtractor->Extract( + filename, + (int)( m_pWaveFile->GetRunningLength() * m_pWaveFile->SampleRate() * m_pWaveFile->TrueSampleSize() ), + Con_Printf, + m_Tags, + m_TagsExt ); + + float bytespersecond = m_pWaveFile->SampleRate() * m_pWaveFile->TrueSampleSize(); + + // Now convert byte offsets to times + int i; + for ( i = 0; i < m_TagsExt.m_Words.Size(); i++ ) + { + CWordTag *tag = m_TagsExt.m_Words[ i ]; + Assert( tag ); + if ( !tag ) + continue; + + tag->m_flStartTime = ( float )(tag->m_uiStartByte ) / bytespersecond; + tag->m_flEndTime = ( float )(tag->m_uiEndByte ) / bytespersecond; + + for ( int j = 0; j < tag->m_Phonemes.Size(); j++ ) + { + CPhonemeTag *ptag = tag->m_Phonemes[ j ]; + Assert( ptag ); + if ( !ptag ) + continue; + + ptag->SetStartTime( ( float )(ptag->m_uiStartByte ) / bytespersecond ); + ptag->SetEndTime( ( float )(ptag->m_uiEndByte ) / bytespersecond ); + } + } + + SetFocus( (HWND)getHandle() ); + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::Deselect( void ) +{ + m_nSelection[ 0 ] = m_nSelection[ 1 ] = 0; + m_bSelectionActive = false; +} + +void PhonemeEditor::ITER_SelectSpanningWords( CWordTag *word, float amount ) +{ + Assert( word ); + word->m_bSelected = false; + + if ( !m_bSelectionActive ) + return; + + if ( !m_pWaveFile ) + return; + + float numsamples = m_pWaveFile->GetRunningLength() * m_pWaveFile->SampleRate(); + if ( numsamples > 0.0f ) + { + // Convert sample #'s to time + float starttime = ( m_nSelection[ 0 ] / numsamples ) * m_pWaveFile->GetRunningLength(); + float endtime = ( m_nSelection[ 1 ] / numsamples ) * m_pWaveFile->GetRunningLength(); + + if ( word->m_flEndTime >= starttime && + word->m_flStartTime <= endtime ) + { + word->m_bSelected = true; + + m_bWordsActive = true; + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : start - +// end - +//----------------------------------------------------------------------------- +void PhonemeEditor::SelectSamples( int start, int end ) +{ + if ( !m_pWaveFile ) + return; + + // Make sure order is correct + if ( end < start ) + { + int temp = end; + end = start; + start = temp; + } + + Deselect(); + + m_nSelection[ 0 ] = start; + m_nSelection[ 1 ] = end; + m_bSelectionActive = true; + + // Select any words that span the selection + // + TraverseWords( &PhonemeEditor::ITER_SelectSpanningWords, 0.0f ); + + redraw(); +} + +void PhonemeEditor::FinishMoveSelection( int startx, int mx ) +{ + if ( !m_pWaveFile ) + return; + + int sampleStart = GetSampleForMouse( startx ); + int sampleEnd = GetSampleForMouse( mx ); + + int delta = sampleEnd - sampleStart; + + for ( int i = 0; i < 2; i++ ) + { + m_nSelection[ i ] += delta; + } + + // Select any words that span the selection + // + TraverseWords( &PhonemeEditor::ITER_SelectSpanningWords, 0.0f ); + + redraw(); +} + +void PhonemeEditor::FinishMoveSelectionStart( int startx, int mx ) +{ + if ( !m_pWaveFile ) + return; + + int sampleStart = GetSampleForMouse( startx ); + int sampleEnd = GetSampleForMouse( mx ); + + int delta = sampleEnd - sampleStart; + + m_nSelection[ 0 ] += delta; + + if ( m_nSelection[ 0 ] >= m_nSelection[ 1 ] ) + { + Deselect(); + } + + // Select any words that span the selection + // + TraverseWords( &PhonemeEditor::ITER_SelectSpanningWords, 0.0f ); + + redraw(); +} + +void PhonemeEditor::FinishMoveSelectionEnd( int startx, int mx ) +{ + if ( !m_pWaveFile ) + return; + + int sampleStart = GetSampleForMouse( startx ); + int sampleEnd = GetSampleForMouse( mx ); + + int delta = sampleEnd - sampleStart; + + m_nSelection[ 1 ] += delta; + + if ( m_nSelection[ 1 ] <= m_nSelection[ 0 ] ) + { + Deselect(); + } + + // Select any words that span the selection + // + TraverseWords( &PhonemeEditor::ITER_SelectSpanningWords, 0.0f ); + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : startx - +// mx - +//----------------------------------------------------------------------------- +void PhonemeEditor::FinishSelect( int startx, int mx ) +{ + if ( !m_pWaveFile ) + return; + + // Don't select really small areas + if ( abs( startx - mx ) < 2 ) + return; + + int sampleStart = GetSampleForMouse( startx ); + int sampleEnd = GetSampleForMouse( mx ); + + SelectSamples( sampleStart, sampleEnd ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool PhonemeEditor::IsMouseOverSamples( int mx, int my ) +{ + if ( GetMode() != MODE_PHONEMES ) + return false; + + // Deterime if phoneme boundary is under the cursor + // + if ( !m_pWaveFile ) + return false; + + RECT rc; + GetWorkspaceRect( rc ); + + // Over tag + if ( my >= TAG_TOP && my <= TAG_BOTTOM ) + return false; + + if ( IsMouseOverPhonemeRow( my ) ) + return false; + + if ( IsMouseOverWordRow( my ) ) + return false; + + RECT rcWord; + GetWordTrayTopBottom( rcWord ); + RECT rcPhoneme; + GetPhonemeTrayTopBottom( rcPhoneme ); + + if ( my < rcWord.bottom ) + return false; + + if ( my > rcPhoneme.top ) + return false; + + return true; +} + +void PhonemeEditor::GetScreenStartAndEndTime( float &starttime, float& endtime ) +{ + starttime = m_nLeftOffset / GetPixelsPerSecond(); + endtime = w2() / GetPixelsPerSecond() + starttime; +} + +float PhonemeEditor::GetTimePerPixel( void ) +{ + RECT rc; + GetWorkspaceRect( rc ); + + float starttime, endtime; + GetScreenStartAndEndTime( starttime, endtime ); + + if ( rc.right - rc.left <= 0 ) + { + return ( endtime - starttime ); + } + + float timeperpixel = ( endtime - starttime ) / (float)( rc.right - rc.left ); + return timeperpixel; +} + +int PhonemeEditor::GetPixelForSample( int sample ) +{ + RECT rc; + GetWorkspaceRect( rc ); + + if ( !m_pWaveFile ) + return rc.left; + + // Determine start/stop positions + int totalsamples = (int)( m_pWaveFile->GetRunningLength() * m_pWaveFile->SampleRate() ); + if ( totalsamples <= 0 ) + { + return rc.left; + } + + float starttime, endtime; + GetScreenStartAndEndTime( starttime, endtime ); + + float sampleFrac = (float)sample / (float)totalsamples; + float sampleTime = sampleFrac * (float)m_pWaveFile->GetRunningLength(); + + if ( endtime - starttime < 0.0f ) + { + return rc.left; + } + + float windowFrac = ( sampleTime - starttime ) / ( endtime - starttime ); + + return rc.left + (int)( windowFrac * ( rc.right - rc.left ) ); +} + +int PhonemeEditor::GetSampleForMouse( int mx ) +{ + if ( !m_pWaveFile ) + return 0; + + RECT rc; + GetWorkspaceRect( rc ); + + // Determine start/stop positions + int totalsamples = (int)( m_pWaveFile->GetRunningLength() * m_pWaveFile->SampleRate() ); + + float starttime, endtime; + GetScreenStartAndEndTime( starttime, endtime ); + + if ( GetPixelsPerSecond() <= 0 ) + return 0; + + // Start and end times + float clickTime = (float)mx / GetPixelsPerSecond() + starttime; + + // What sample do these correspond to + if ( (float)m_pWaveFile->GetRunningLength() <= 0.0f ) + return 0; + + int sampleNumber = (int) ( (float)totalsamples * clickTime / (float)m_pWaveFile->GetRunningLength() ); + + return sampleNumber; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool PhonemeEditor::IsMouseOverSelection( int mx, int my ) +{ + if ( GetMode() != MODE_PHONEMES ) + return false; + + if ( !m_pWaveFile ) + return false; + + if ( !m_bSelectionActive ) + return false; + + if ( !IsMouseOverSamples( mx, my ) ) + return false; + + int sampleNumber = GetSampleForMouse( mx ); + + if ( sampleNumber >= m_nSelection[ 0 ] - 3 && + sampleNumber <= m_nSelection[ 1 ] + 3 ) + { + return true; + } + + return false; +} + +bool PhonemeEditor::IsMouseOverSelectionStartEdge( mxEvent *event ) +{ + if ( GetMode() != MODE_PHONEMES ) + return false; + + if ( !m_pWaveFile ) + return false; + + int mx, my; + mx = (short)event->x; + my = (short)event->y; + + if ( !(event->modifiers & mxEvent::KeyCtrl ) ) + return false; + + if ( !IsMouseOverSelection( mx, my ) ) + return false; + + int sample = GetSampleForMouse( mx ); + + int mouse_tolerance = 5; + + RECT rc; + GetWorkspaceRect( rc ); + + // Determine start/stop positions + float timeperpixel = GetTimePerPixel(); + + int samplesperpixel = (int)( timeperpixel * m_pWaveFile->SampleRate() ); + + if ( abs( sample - m_nSelection[ 0 ] ) < mouse_tolerance * samplesperpixel ) + { + return true; + } + return false; +} + +bool PhonemeEditor::IsMouseOverSelectionEndEdge( mxEvent *event ) +{ + if ( GetMode() != MODE_PHONEMES ) + return false; + + if ( !m_pWaveFile ) + return false; + + int mx, my; + mx = (short)event->x; + my = (short)event->y; + + if ( !(event->modifiers & mxEvent::KeyCtrl ) ) + return false; + + if ( !IsMouseOverSelection( mx, my ) ) + return false; + + int sample = GetSampleForMouse( mx ); + + int mouse_tolerance = 5; + + RECT rc; + GetWorkspaceRect( rc ); + + if ( GetPixelsPerSecond() <= 0.0f ) + return false; + + if ( ( rc.right - rc.left ) <= 0 ) + return false; + + // Determine start/stop positions + float starttime = m_nLeftOffset / GetPixelsPerSecond(); + float endtime = w2() / GetPixelsPerSecond() + starttime; + + float timeperpixel = ( endtime - starttime ) / (float)( rc.right - rc.left ); + int samplesperpixel = (int)( timeperpixel * m_pWaveFile->SampleRate() ); + + if ( abs( sample - m_nSelection[ 1 ] ) < mouse_tolerance * samplesperpixel ) + { + return true; + } + return false; +} + +void PhonemeEditor::OnImport() +{ + char filename[ 512 ]; + if ( !FacePoser_ShowOpenFileNameDialog( filename, sizeof( filename ), "sound", "*" WORD_DATA_EXTENSION ) ) + { + return; + } + + ImportValveDataChunk( filename ); +} + +void PhonemeEditor::OnExport() +{ + if ( !m_pWaveFile ) + return; + + char filename[ 512 ]; + if ( !FacePoser_ShowSaveFileNameDialog( filename, sizeof( filename ), "sound", "*" WORD_DATA_EXTENSION ) ) + { + return; + } + + Q_SetExtension( filename, WORD_DATA_EXTENSION, sizeof( filename ) ); + + ExportValveDataChunk( filename ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : store - +//----------------------------------------------------------------------------- +void PhonemeEditor::StoreValveDataChunk( IterateOutputRIFF& store ) +{ + // Buffer and dump data + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + + m_Tags.SaveToBuffer( buf ); + + // Copy into store + store.ChunkWriteData( buf.Base(), buf.TellPut() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *tempfile - +//----------------------------------------------------------------------------- +void PhonemeEditor::ExportValveDataChunk( char const *tempfile ) +{ + if ( m_Tags.m_Words.Count() <= 0 ) + { + Con_ErrorPrintf( "PhonemeEditor::ExportValveDataChunk: Sentence has no word data\n" ); + return; + } + + FileHandle_t fh = filesystem->Open( tempfile, "wb" ); + if ( !fh ) + { + Con_ErrorPrintf( "PhonemeEditor::ExportValveDataChunk: Unable to write to %s (read-only?)\n", tempfile ); + return; + } + else + { + // Buffer and dump data + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + + m_Tags.SaveToBuffer( buf ); + + filesystem->Write( buf.Base(), buf.TellPut(), fh ); + filesystem->Close(fh); + + Con_Printf( "Exported %i words to %s\n", m_Tags.m_Words.Count(), tempfile ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *tempfile - +//----------------------------------------------------------------------------- +void PhonemeEditor::ImportValveDataChunk( char const *tempfile ) +{ + FileHandle_t fh = filesystem->Open( tempfile, "rb" ); + if ( !fh ) + { + Con_ErrorPrintf( "PhonemeEditor::ImportValveDataChunk: Unable to read from %s\n", tempfile ); + return; + } + + int len = filesystem->Size( fh ); + if ( len <= 4 ) + { + Con_ErrorPrintf( "PhonemeEditor::ImportValveDataChunk: File %s has length 0\n", tempfile ); + return; + } + + ClearExtracted(); + + unsigned char *buf = new unsigned char[ len + 1 ]; + + filesystem->Read( buf, len, fh ); + filesystem->Close( fh ); + + m_TagsExt.InitFromDataChunk( (void *)( buf ), len ); + + delete[] buf; + + Con_Printf( "Imported %i words from %s\n", m_TagsExt.m_Words.Count(), tempfile ); + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: Copy file over, but update phoneme lump with new data +//----------------------------------------------------------------------------- +void PhonemeEditor::SaveLinguisticData( void ) +{ + if ( !m_pWaveFile ) + return; + + InFileRIFF riff( m_WorkFile.m_szWaveFile, io_in ); + Assert( riff.RIFFName() == RIFF_WAVE ); + + // set up the iterator for the whole file (root RIFF is a chunk) + IterateRIFF walk( riff, riff.RIFFSize() ); + + char fullout[ 512 ]; + Q_snprintf( fullout, sizeof( fullout ), "%s%s", m_WorkFile.m_szBasePath, m_WorkFile.m_szWorkingFile ); + + OutFileRIFF riffout( fullout, io_out ); + + IterateOutputRIFF store( riffout ); + + bool formatset = false; + WAVEFORMATEX format; + + bool wordtrackwritten = false; + + // Walk input chunks and copy to output + while ( walk.ChunkAvailable() ) + { + unsigned int originalPos = store.ChunkGetPosition(); + + store.ChunkStart( walk.ChunkName() ); + + bool skipchunk = false; + + switch ( walk.ChunkName() ) + { + case WAVE_VALVEDATA: + // Overwrite data + StoreValveDataChunk( store ); + wordtrackwritten = true; + break; + case WAVE_FMT: + { + formatset = true; + + char *buffer = new char[ walk.ChunkSize() ]; + Assert( buffer ); + walk.ChunkRead( buffer ); + + format = *(WAVEFORMATEX *)buffer; + + store.ChunkWriteData( buffer, walk.ChunkSize() ); + + delete[] buffer; + } + break; + case WAVE_DATA: + { + Assert( formatset ); + + char *buffer = new char[ walk.ChunkSize() ]; + Assert( buffer ); + + walk.ChunkRead( buffer ); + // Resample it + ResampleChunk( store, (void *)&format, walk.ChunkName(), buffer, walk.ChunkSize() ); + + delete[] buffer; + } + break; + default: + store.CopyChunkData( walk ); + break; + } + + store.ChunkFinish(); + if ( skipchunk ) + { + store.ChunkSetPosition( originalPos ); + } + + walk.ChunkNext(); + } + + if ( !wordtrackwritten ) + { + store.ChunkStart( WAVE_VALVEDATA ); + StoreValveDataChunk( store ); + store.ChunkFinish(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Copy phoneme data in from wave file we sent for resprocessing +//----------------------------------------------------------------------------- +void PhonemeEditor::RetrieveLinguisticData( void ) +{ + if ( !m_pWaveFile ) + return; + + m_Tags.Reset(); + + ReadLinguisticTags(); + + redraw(); +} + +bool PhonemeEditor::StopPlayback( void ) +{ + bool bret = false; + if ( m_pWaveFile ) + { + SetScrubTargetTime( m_flScrub ); + + if ( sound->IsSoundPlaying( m_pMixer ) ) + { + sound->StopAll(); + bret = true; + } + } + + sound->Flush(); + + return bret; +} + +CPhonemeTag *PhonemeEditor::GetPhonemeTagUnderMouse( int mx, int my ) +{ + if ( GetMode() != MODE_PHONEMES ) + return NULL; + + if ( !m_pWaveFile ) + return NULL; + + // FIXME: Don't read from file, read from arrays after LISET finishes + // Deterime if phoneme boundary is under the cursor + // + RECT rc; + GetWorkspaceRect( rc ); + + if ( !IsMouseOverPhonemeRow( my ) ) + return NULL; + + if ( GetPixelsPerSecond() <= 0 ) + return NULL; + + float starttime = m_nLeftOffset / GetPixelsPerSecond(); + float endtime = w2() / GetPixelsPerSecond() + starttime; + + if ( endtime - starttime <= 0.0f ) + return NULL; + + for ( int i = 0; i < m_Tags.m_Words.Size(); i++ ) + { + CWordTag *word = m_Tags.m_Words[ i ]; + Assert( word ); + if ( !word ) + continue; + + for ( int k = 0; k < word->m_Phonemes.Size(); k++ ) + { + CPhonemeTag *pPhoneme = word->m_Phonemes[ k ]; + Assert( pPhoneme ); + if ( !pPhoneme ) + continue; + + float t1 = pPhoneme->GetStartTime(); + float t2 = pPhoneme->GetEndTime(); + + float frac1 = ( t1 - starttime ) / ( endtime - starttime ); + float frac2 = ( t2 - starttime ) / ( endtime - starttime ); + + frac1 = min( 1.0f, frac1 ); + frac1 = max( 0.0f, frac1 ); + frac2 = min( 1.0f, frac2 ); + frac2 = max( 0.0f, frac2 ); + + if ( frac1 == frac2 ) + continue; + + int x1 = ( int )( frac1 * w2() ); + int x2 = ( int )( frac2 * w2() ); + + if ( mx >= x1 && mx <= x2 ) + { + return pPhoneme; + } + } + } + + return NULL; +} + +CWordTag *PhonemeEditor::GetWordTagUnderMouse( int mx, int my ) +{ + if ( GetMode() != MODE_PHONEMES ) + return NULL; + + // Deterime if phoneme boundary is under the cursor + // + if ( !m_pWaveFile ) + return NULL; + + RECT rc; + GetWorkspaceRect( rc ); + + if ( !IsMouseOverWordRow( my ) ) + return NULL; + + if ( GetPixelsPerSecond() <= 0 ) + return NULL; + + float starttime = m_nLeftOffset / GetPixelsPerSecond(); + float endtime = w2() / GetPixelsPerSecond() + starttime; + + if ( endtime - starttime <= 0.0f ) + return NULL; + + for ( int k = 0; k < m_Tags.m_Words.Size(); k++ ) + { + CWordTag *word = m_Tags.m_Words[ k ]; + Assert( word ); + if ( !word ) + continue; + + float t1 = word->m_flStartTime; + float t2 = word->m_flEndTime; + + float frac1 = ( t1 - starttime ) / ( endtime - starttime ); + float frac2 = ( t2 - starttime ) / ( endtime - starttime ); + + frac1 = min( 1.0f, frac1 ); + frac1 = max( 0.0f, frac1 ); + frac2 = min( 1.0f, frac2 ); + frac2 = max( 0.0f, frac2 ); + + if ( frac1 == frac2 ) + continue; + + int x1 = ( int )( frac1 * w2() ); + int x2 = ( int )( frac2 * w2() ); + + if ( mx >= x1 && mx <= x2 ) + { + return word; + } + } + + return NULL; +} + +void PhonemeEditor::DeselectWords( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + for ( int i = 0 ; i < m_Tags.m_Words.Size(); i++ ) + { + CWordTag *w = m_Tags.m_Words[ i ]; + Assert( w ); + if ( !w ) + continue; + w->m_bSelected = false; + } + +} + +void PhonemeEditor::DeselectPhonemes( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + for ( int w = 0 ; w < m_Tags.m_Words.Size(); w++ ) + { + CWordTag *word = m_Tags.m_Words[ w ]; + Assert( word ); + if ( !word ) + continue; + + for ( int i = 0 ; i < word->m_Phonemes.Size(); i++ ) + { + CPhonemeTag *pt = word->m_Phonemes[ i ]; + Assert( pt ); + if ( !pt ) + continue; + pt->m_bSelected = false; + } + } +} + +void PhonemeEditor::SnapWords( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + if ( m_Tags.m_Words.Size() < 2 ) + { + Con_Printf( "Can't snap, need at least two contiguous selected words\n" ); + return; + } + + SetDirty( true ); + + PushUndo(); + + for ( int i = 0; i < m_Tags.m_Words.Size() - 1; i++ ) + { + CWordTag *current = m_Tags.m_Words[ i ]; + CWordTag *next = m_Tags.m_Words[ i + 1 ]; + + Assert( current && next ); + + if ( !current->m_bSelected || !next->m_bSelected ) + continue; + + // Move next word to end of current + next->m_flStartTime = current->m_flEndTime; + } + + PushRedo(); + + redraw(); +} + +void PhonemeEditor::SeparateWords( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + if ( GetPixelsPerSecond() <= 0.0f ) + return; + + if ( m_Tags.m_Words.Size() < 2 ) + { + Con_Printf( "Can't separate, need at least two contiguous selected words\n" ); + return; + } + + // Three pixels + double time_epsilon = ( 1.0f / GetPixelsPerSecond() ) * 6; + + SetDirty( true ); + + PushUndo(); + + for ( int i = 0; i < m_Tags.m_Words.Size() - 1; i++ ) + { + CWordTag *current = m_Tags.m_Words[ i ]; + CWordTag *next = m_Tags.m_Words[ i + 1 ]; + + Assert( current && next ); + + if ( !current->m_bSelected || !next->m_bSelected ) + continue; + + // Close enough? + if ( fabs( current->m_flEndTime - next->m_flStartTime ) > time_epsilon ) + { + Con_Printf( "Can't split %s and %s, already split apart\n", + current->GetWord(), next->GetWord() ); + continue; + } + + // Offset next word start time a bit + next->m_flStartTime += time_epsilon; + + break; + } + + PushRedo(); + + redraw(); +} + +void PhonemeEditor::CreateEvenWordDistribution( const char *wordlist ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + if( !m_pWaveFile ) + return; + + Assert( wordlist ); + if ( !wordlist ) + return; + + m_Tags.CreateEventWordDistribution( wordlist, m_pWaveFile->GetRunningLength() ); + + redraw(); +} + +void PhonemeEditor::EditWordList( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + if ( !m_pWaveFile ) + return; + + // Build word string + char wordstring[ 1024 ]; + V_strcpy_safe( wordstring, m_Tags.GetText() ); + + CInputParams params; + memset( ¶ms, 0, sizeof( params ) ); + strcpy( params.m_szDialogTitle, "Word List" ); + strcpy( params.m_szPrompt, "Sentence:" ); + + strcpy( params.m_szInputText, wordstring ); + + if ( !InputProperties( ¶ms ) ) + return; + + if ( strlen( params.m_szInputText ) <= 0 ) + { + // Could be foreign language... + Warning( "Edit word list: No words entered!\n" ); + } + + SetDirty( true ); + + PushUndo(); + + // Clear any current LISET results + ClearExtracted(); + + // Force text + m_Tags.SetText( params.m_szInputText ); + + if ( m_Tags.m_Words.Size() == 0 ) + { + // First text we've seen, just distribute words evenly + CreateEvenWordDistribution( params.m_szInputText ); + + // Redo liset + RedoPhonemeExtraction(); + } + + PushRedo(); + + SetFocus( (HWND)getHandle() ); + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: Overwrite original wave with changes +//----------------------------------------------------------------------------- +void PhonemeEditor::CommitChanges( void ) +{ + SaveLinguisticData(); + + // Make it writable - if possible + MakeFileWriteable( m_WorkFile.m_szWaveFile ); + + //Open a message box to warn the user if the file was unable to be made non-read only + if ( !IsFileWriteable( m_WorkFile.m_szWaveFile ) ) + { + mxMessageBox( NULL, va( "Unable to save file '%s'. File is read-only or in use.", + m_WorkFile.m_szWaveFile ), g_appTitle, MX_MB_OK ); + } + else + { + // Copy over and overwrite file + FPCopyFile( m_WorkFile.m_szWorkingFile, m_WorkFile.m_szWaveFile, true ); + Msg( "Changes saved to '%s'\n", m_WorkFile.m_szWaveFile ); + SetDirty( false, false ); + } + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::LoadWaveFile( void ) +{ + char filename[ 512 ]; + if ( !FacePoser_ShowOpenFileNameDialog( filename, sizeof( filename ), "sound", "*.wav" ) ) + { + return; + } + + StopPlayback(); + + // Strip out the game directory + SetCurrentWaveFile( filename ); +} + +void PhonemeEditor::SnapPhonemes( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + SetDirty( true ); + + PushUndo(); + + CPhonemeTag *prev = NULL; + + for ( int w = 0; w < m_Tags.m_Words.Size(); w++ ) + { + CWordTag *word = m_Tags.m_Words[ w ]; + Assert( word ); + if ( !word ) + continue; + + for ( int i = 0; i < word->m_Phonemes.Size(); i++ ) + { + CPhonemeTag *current = word->m_Phonemes[ i ]; + + Assert( current ); + + if ( current->m_bSelected ) + { + if (prev) + { + // More start of next to end of previous + prev->SetEndTime( current->GetStartTime() ); + } + prev = current; + } + else + { + prev = NULL; + } + } + } + + PushRedo(); + + redraw(); +} + +void PhonemeEditor::SeparatePhonemes( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + SetDirty( true ); + + PushUndo(); + + // Three pixels + double time_epsilon = ( 1.0f / GetPixelsPerSecond() ) * 6; + + CPhonemeTag *prev = NULL; + + for ( int w = 0; w < m_Tags.m_Words.Size(); w++ ) + { + CWordTag *word = m_Tags.m_Words[ w ]; + Assert( word ); + if ( !word ) + continue; + + for ( int i = 0; i < word->m_Phonemes.Size(); i++ ) + { + CPhonemeTag *current = word->m_Phonemes[ i ]; + + Assert( current ); + + if ( current->m_bSelected ) + { + if ( prev ) + { + // Close enough? + if ( fabs( prev->GetEndTime() - current->GetStartTime() ) > time_epsilon ) + { + Con_Printf( "Can't split already split apart\n" ); + continue; + } + + current->AddStartTime( time_epsilon ); + } + + prev = current; + } + else + { + prev = NULL; + } + } + } + + PushRedo(); + + redraw(); +} + +bool PhonemeEditor::IsMouseOverWordRow( int my ) +{ + if ( GetMode() != MODE_PHONEMES ) + return false; + + RECT rc; + + GetWordTrayTopBottom( rc ); + + if ( my < rc.top ) + return false; + + if ( my > rc.bottom ) + return false; + + return true; +} + +bool PhonemeEditor::IsMouseOverPhonemeRow( int my ) +{ + if ( GetMode() != MODE_PHONEMES ) + return false; + + RECT rc; + + GetPhonemeTrayTopBottom( rc ); + + if ( my < rc.top ) + return false; + + if ( my > rc.bottom ) + return false; + + return true; +} + +void PhonemeEditor::GetPhonemeTrayTopBottom( RECT& rc ) +{ + RECT wkrc; + GetWorkspaceRect( wkrc ); + + rc.top = wkrc.bottom - 2 * m_nTickHeight; + rc.bottom = wkrc.bottom - m_nTickHeight; +} + +void PhonemeEditor::GetWordTrayTopBottom( RECT& rc ) +{ + RECT wkrc; + GetWorkspaceRect( wkrc ); + + rc.top = wkrc.top; + rc.bottom = wkrc.top + m_nTickHeight; +} + +int PhonemeEditor::GetMouseForTime( float time ) +{ + RECT rc; + GetWorkspaceRect( rc ); + + if ( GetPixelsPerSecond() < 0.0f ) + return rc.left; + + float starttime = m_nLeftOffset / GetPixelsPerSecond(); + float endtime = w2() / GetPixelsPerSecond() + starttime; + + if ( endtime - starttime <= 0.0f ) + return rc.left; + + float frac; + + frac = ( time - starttime ) / ( endtime - starttime ); + + return rc.left + ( int )( rc.right * frac ); +} + +void PhonemeEditor::GetWordRect( const CWordTag *tag, RECT& rc ) +{ + Assert( tag ); + GetWordTrayTopBottom( rc ); + + rc.left = GetMouseForTime( tag->m_flStartTime ); + rc.right = GetMouseForTime( tag->m_flEndTime ); + +} + +void PhonemeEditor::GetPhonemeRect( const CPhonemeTag *tag, RECT& rc ) +{ + Assert( tag ); + + GetPhonemeTrayTopBottom( rc ); + rc.left = GetMouseForTime( tag->GetStartTime() ); + rc.right = GetMouseForTime( tag->GetEndTime() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::CommitExtracted( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + m_nLastExtractionResult = SR_RESULT_NORESULT; + + if ( !m_TagsExt.m_Words.Size() ) + return; + + SetDirty( true ); + + PushUndo(); + + m_Tags.Reset(); + m_Tags = m_TagsExt; + + PushRedo(); + + ClearExtracted(); + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::ClearExtracted( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + m_nLastExtractionResult = SR_RESULT_NORESULT; + + m_TagsExt.Reset(); + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : resultCode - +// Output : const char +//----------------------------------------------------------------------------- +const char *PhonemeEditor::GetExtractionResultString( int resultCode ) +{ + switch ( resultCode ) + { + case SR_RESULT_NORESULT: + return "no extraction info."; + case SR_RESULT_ERROR: + return "an error occurred during extraction."; + case SR_RESULT_SUCCESS: + return "successful."; + case SR_RESULT_FAILED: + return "results retrieved, but full recognition failed."; + default: + break; + } + + return "unknown result code."; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// Output : CEventRelativeTag +//----------------------------------------------------------------------------- +CEventRelativeTag *PhonemeEditor::GetTagUnderMouse( int mx ) +{ + if ( GetMode() != MODE_PHONEMES ) + return NULL; + + // Figure out tag positions + if ( !m_pEvent || !m_pWaveFile ) + return NULL; + + RECT rc; + GetWorkspaceRect( rc ); + RECT rcTags = rc; + + if ( GetPixelsPerSecond() <= 0.0f ) + return NULL; + + float starttime = m_nLeftOffset / GetPixelsPerSecond(); + float endtime = w2() / GetPixelsPerSecond() + starttime; + + if ( endtime - starttime < 0 ) + return NULL; + + for ( int i = 0; i < m_pEvent->GetNumRelativeTags(); i++ ) + { + CEventRelativeTag *tag = m_pEvent->GetRelativeTag( i ); + if ( !tag ) + continue; + + // + float tagtime = tag->GetPercentage() * m_pWaveFile->GetRunningLength(); + if ( tagtime < starttime || tagtime > endtime ) + continue; + + float frac = ( tagtime - starttime ) / ( endtime - starttime ); + + int left = rcTags.left + (int)( frac * ( float )( rcTags.right - rcTags.left ) + 0.5f ); + + if ( abs( mx - left ) < 10 ) + return tag; + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool PhonemeEditor::IsMouseOverTag( int mx, int my ) +{ + if ( GetMode() != MODE_PHONEMES ) + return false; + + if ( !IsMouseOverTagRow( my ) ) + return false; + + CEventRelativeTag *tag = GetTagUnderMouse( mx ); + if ( !tag ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : startx - +// endx - +//----------------------------------------------------------------------------- +void PhonemeEditor::FinishEventTagDrag( int startx, int endx ) +{ + if ( !m_pWaveFile ) + return; + + if ( !m_pWaveFile->GetRunningLength() ) + return; + + // Find starting tag + CEventRelativeTag *tag = GetTagUnderMouse( startx ); + if ( !tag ) + return; + + if ( GetPixelsPerSecond() <= 0 ) + return; + + // Convert mouse position to time + float starttime = m_nLeftOffset / GetPixelsPerSecond(); + + float clicktime = (float)endx / GetPixelsPerSecond() + starttime; + + float percent = clicktime / m_pWaveFile->GetRunningLength(); + percent = clamp( percent, 0.0f, 1.0f ); + + tag->SetPercentage( percent ); + + redraw(); + + if ( g_pChoreoView ) + { + g_pChoreoView->InvalidateLayout(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : my - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool PhonemeEditor::IsMouseOverTagRow( int my ) +{ + if ( GetMode() != MODE_PHONEMES ) + return false; + + if ( my < TAG_TOP || my > TAG_BOTTOM ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +//----------------------------------------------------------------------------- +void PhonemeEditor::ShowTagMenu( int mx, int my ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + // Figure out tag positions + if ( !m_pEvent || !m_pWaveFile ) + return; + + if ( !IsMouseOverTagRow( my ) ) + return; + + CEventRelativeTag *tag = GetTagUnderMouse( mx ); + + mxPopupMenu *pop = new mxPopupMenu(); + + if ( tag ) + { + pop->add( va( "Delete tag '%s'", tag->GetName() ), IDC_DELETETAG ); + } + else + { + pop->add( va( "Add tag..." ), IDC_ADDTAG ); + } + + m_nClickX = mx; + + pop->popup( this, mx, my ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::DeleteTag( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + // Figure out tag positions + if ( !m_pEvent ) + return; + + CEventRelativeTag *tag = GetTagUnderMouse( m_nClickX ); + if ( !tag ) + return; + + // Remove it + m_pEvent->RemoveRelativeTag( tag->GetName() ); + + g_pChoreoView->InvalidateLayout(); + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::AddTag( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + // Figure out tag positions + if ( !m_pEvent || !m_pWaveFile ) + return; + + CInputParams params; + memset( ¶ms, 0, sizeof( params ) ); + strcpy( params.m_szDialogTitle, "Event Tag Name" ); + strcpy( params.m_szPrompt, "Name:" ); + strcpy( params.m_szInputText, "" ); + + if ( !InputProperties( ¶ms ) ) + return; + + if ( strlen( params.m_szInputText ) <= 0 ) + { + Con_ErrorPrintf( "Event Tag Name: No name entered!\n" ); + return; + } + + // Convert mouse position to time + float starttime = m_nLeftOffset / GetPixelsPerSecond(); + float clicktime = (float)m_nClickX / GetPixelsPerSecond() + starttime; + + float percent = clicktime / m_pWaveFile->GetRunningLength(); + percent = min( 1.0f, percent ); + percent = max( 0.0f, percent ); + + m_pEvent->AddRelativeTag( params.m_szInputText, percent ); + + g_pChoreoView->InvalidateLayout(); + + SetFocus( (HWND)getHandle() ); + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::ClearEvent( void ) +{ + m_pEvent = NULL; + redraw(); +} + +void PhonemeEditor::TraverseWords( PEWORDITERFUNC pfn, float fparam ) +{ + for ( int i = 0; i < m_Tags.m_Words.Size(); i++ ) + { + CWordTag *word = m_Tags.m_Words[ i ]; + if ( !word ) + continue; + + (this->*pfn)( word, fparam ); + } +} + +void PhonemeEditor::TraversePhonemes( PEPHONEMEITERFUNC pfn, float fparam ) +{ + for ( int i = 0; i < m_Tags.m_Words.Size(); i++ ) + { + CWordTag *word = m_Tags.m_Words[ i ]; + if ( !word ) + continue; + + for ( int j = 0; j < word->m_Phonemes.Size(); j++ ) + { + CPhonemeTag *phoneme = word->m_Phonemes[ j ]; + if ( !phoneme ) + continue; + + (this->*pfn)( phoneme, word, fparam ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : amount - +//----------------------------------------------------------------------------- +void PhonemeEditor::ITER_MoveSelectedWords( CWordTag *word, float amount ) +{ + if ( !word->m_bSelected ) + return; + + word->m_flStartTime += amount; + word->m_flEndTime += amount; +} + +void PhonemeEditor::ITER_MoveSelectedPhonemes( CPhonemeTag *phoneme, CWordTag *word, float amount ) +{ + if ( !phoneme->m_bSelected ) + return; + + phoneme->AddStartTime( amount ); + phoneme->AddEndTime( amount ); + +} + +void PhonemeEditor::ITER_ExtendSelectedPhonemeEndTimes( CPhonemeTag *phoneme, CWordTag *word, float amount ) +{ + if ( !phoneme->m_bSelected ) + return; + + if ( phoneme->GetEndTime() + amount <= phoneme->GetStartTime() ) + return; + + phoneme->AddEndTime( amount ); + + // Fixme, check for extending into next phoneme +} + + +void PhonemeEditor::ITER_ExtendSelectedWordEndTimes( CWordTag *word, float amount ) +{ + if ( !word->m_bSelected ) + return; + + if ( word->m_flEndTime + amount <= word->m_flStartTime ) + return; + + word->m_flEndTime += amount; + + // Fixme, check for extending into next word +} + +void PhonemeEditor::ITER_AddFocusRectSelectedWords( CWordTag *word, float amount ) +{ + if ( !word->m_bSelected ) + return; + + RECT wordRect; + GetWordRect( word, wordRect ); + + AddFocusRect( wordRect ); +} + +void PhonemeEditor::ITER_AddFocusRectSelectedPhonemes( CPhonemeTag *phoneme, CWordTag *word, float amount ) +{ + if ( !phoneme->m_bSelected ) + return; + + RECT phonemeRect; + GetPhonemeRect( phoneme, phonemeRect ); + + AddFocusRect( phonemeRect ); +} + + +void PhonemeEditor::AddFocusRect( RECT& rc ) +{ + RECT rcFocus = rc; + + POINT offset; + offset.x = 0; + offset.y = 0; + ClientToScreen( (HWND)getHandle(), &offset ); + OffsetRect( &rcFocus, offset.x, offset.y ); + + // Convert to screen space? + CFocusRect fr; + fr.m_rcFocus = rcFocus; + fr.m_rcOrig = rcFocus; + + m_FocusRects.AddToTail( fr ); +} + +void PhonemeEditor::CountSelected( void ) +{ + m_nSelectedPhonemeCount = 0; + m_nSelectedWordCount = 0; + + TraverseWords( &PhonemeEditor::ITER_CountSelectedWords, 0.0f ); + TraversePhonemes( &PhonemeEditor::ITER_CountSelectedPhonemes, 0.0f ); +} + +void PhonemeEditor::ITER_CountSelectedWords( CWordTag *word, float amount ) +{ + if ( !word->m_bSelected ) + return; + + m_nSelectedWordCount++; + +} + +void PhonemeEditor::ITER_CountSelectedPhonemes( CPhonemeTag *phoneme, CWordTag *word, float amount ) +{ + if ( !phoneme->m_bSelected ) + return; + + m_nSelectedPhonemeCount++; +} + +// Undo/Redo +void PhonemeEditor::Undo( void ) +{ + if ( m_UndoStack.Size() > 0 && m_nUndoLevel > 0 ) + { + m_nUndoLevel--; + PEUndo *u = m_UndoStack[ m_nUndoLevel ]; + Assert( u->undo ); + m_Tags = *(u->undo); + + SetClickedPhoneme( -1, -1 ); + } + + redraw(); +} + +void PhonemeEditor::Redo( void ) +{ + if ( m_UndoStack.Size() > 0 && m_nUndoLevel <= m_UndoStack.Size() - 1 ) + { + PEUndo *u = m_UndoStack[ m_nUndoLevel ]; + Assert( u->redo ); + m_Tags = *(u->redo); + m_nUndoLevel++; + + SetClickedPhoneme( -1, -1 ); + } + + redraw(); +} + +void PhonemeEditor::PushUndo( void ) +{ + Assert( !m_bRedoPending ); + m_bRedoPending = true; + WipeRedo(); + + // Copy current data + CSentence *u = new CSentence(); + *u = m_Tags; + PEUndo *undo = new PEUndo; + undo->undo = u; + undo->redo = NULL; + m_UndoStack.AddToTail( undo ); + m_nUndoLevel++; +} + +void PhonemeEditor::PushRedo( void ) +{ + Assert( m_bRedoPending ); + m_bRedoPending = false; + + // Copy current data + CSentence *r = new CSentence(); + *r = m_Tags; + PEUndo *undo = m_UndoStack[ m_nUndoLevel - 1 ]; + undo->redo = r; +} + +void PhonemeEditor::WipeUndo( void ) +{ + while ( m_UndoStack.Size() > 0 ) + { + PEUndo *u = m_UndoStack[ 0 ]; + delete u->undo; + delete u->redo; + delete u; + m_UndoStack.Remove( 0 ); + } + m_nUndoLevel = 0; +} + +void PhonemeEditor::WipeRedo( void ) +{ + // Wipe everything above level + while ( m_UndoStack.Size() > m_nUndoLevel ) + { + PEUndo *u = m_UndoStack[ m_nUndoLevel ]; + delete u->undo; + delete u->redo; + delete u; + m_UndoStack.Remove( m_nUndoLevel ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : word - +// phoneme - +//----------------------------------------------------------------------------- +void PhonemeEditor::SetClickedPhoneme( int word, int phoneme ) +{ + m_nClickedPhoneme = phoneme; + m_nClickedWord = word; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CPhonemeTag +//----------------------------------------------------------------------------- +CPhonemeTag *PhonemeEditor::GetClickedPhoneme( void ) +{ + if ( m_nClickedPhoneme < 0 || m_nClickedWord < 0 ) + return NULL; + + if ( m_nClickedWord >= m_Tags.m_Words.Size() ) + return NULL; + + CWordTag *word = m_Tags.m_Words[ m_nClickedWord ]; + if ( !word ) + return NULL; + + if ( m_nClickedPhoneme >= word->m_Phonemes.Size() ) + return NULL; + + CPhonemeTag *phoneme = word->m_Phonemes[ m_nClickedPhoneme ]; + return phoneme; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CWordTag +//----------------------------------------------------------------------------- +CWordTag *PhonemeEditor::GetClickedWord( void ) +{ + if ( m_nClickedWord < 0 ) + return NULL; + + if ( m_nClickedWord >= m_Tags.m_Words.Size() ) + return NULL; + + CWordTag *word = m_Tags.m_Words[ m_nClickedWord ]; + return word; +} + +void PhonemeEditor::ShowContextMenu_Phonemes( int mx, int my ) +{ + CountSelected(); + + // Construct main + mxPopupMenu *pop = new mxPopupMenu(); + + if ( m_pWaveFile ) + { + mxPopupMenu *play = new mxPopupMenu; + play->add( va( "Original" ), IDC_PHONEME_PLAY_ORIG ); + play->add( va( "Edited" ), IDC_PLAY_EDITED ); + if ( m_bSelectionActive ) + { + play->add( va( "Selection" ), IDC_PLAY_EDITED_SELECTION ); + } + + pop->addMenu( "Play", play ); + + if ( sound->IsSoundPlaying( m_pMixer ) ) + { + pop->add( va( "Cancel playback" ), IDC_CANCELPLAYBACK ); + } + + pop->addSeparator(); + } + + pop->add( va( "Load..." ), IDC_LOADWAVEFILE ); + + if ( m_pWaveFile ) + { + pop->add( va( "Save" ), IDC_SAVE_LINGUISTIC ); + } + + if ( m_bSelectionActive ) + { + pop->addSeparator(); + pop->add( va( "Deselect" ), IDC_DESELECT ); + } + + if ( m_pWaveFile ) + { + pop->addSeparator(); + pop->add( va( "Redo Extraction" ), IDC_REDO_PHONEMEEXTRACTION ); + + if ( m_nSelectedWordCount < 1 || AreSelectedWordsContiguous() ) + { + pop->add( va( "Redo Extraction of selected words" ), IDC_REDO_PHONEMEEXTRACTION_SELECTION ); + } + } + + if ( m_pWaveFile && m_TagsExt.m_Words.Size() ) + { + pop->addSeparator(); + pop->add( va( "Commit extraction" ) , IDC_COMMITEXTRACTED ); + pop->add( va( "Clear extraction" ), IDC_CLEAREXTRACTED ); + } + + if ( m_nUndoLevel != 0 || m_nUndoLevel != m_UndoStack.Size() ) + { + pop->addSeparator(); + if ( m_nUndoLevel != 0 ) + { + pop->add( va( "Undo" ), IDC_UNDO ); + } + if ( m_nUndoLevel != m_UndoStack.Size() ) + { + pop->add( va( "Redo" ), IDC_REDO ); + } + pop->add( va( "Clear Undo Info" ), IDC_CLEARUNDO ); + } + + if ( m_Tags.m_Words.Size() > 0 ) + { + pop->addSeparator(); + pop->add( va( "Cleanup words/phonemes" ), IDC_CLEANUP ); + } + + // Show hierarchical options menu + { + mxPopupMenu *api = 0; + + if ( DoesExtractorExistFor( SPEECH_API_SAPI ) ) + { + api = new mxPopupMenu(); + api->add( "Microsoft Speech API", IDC_API_SAPI ); + if ( g_viewerSettings.speechapiindex == SPEECH_API_SAPI ) + { + api->setChecked( IDC_API_SAPI, true ); + } + } + + if ( DoesExtractorExistFor( SPEECH_API_LIPSINC ) ) + { + if ( !api ) + api = new mxPopupMenu(); + + api->add( "Lipsinc Speech API", IDC_API_LIPSINC ); + if ( g_viewerSettings.speechapiindex == SPEECH_API_LIPSINC ) + { + api->setChecked( IDC_API_LIPSINC, true ); + } + } + + pop->addSeparator(); + pop->addMenu( "Change Speech API", api ); + } + + // Import export menu + if ( m_pWaveFile ) + { + pop->addSeparator(); + if ( m_Tags.m_Words.Count() > 0 ) + { + pop->add( "Export word data to " WORD_DATA_EXTENSION "...", IDC_EXPORT_SENTENCE ); + } + pop->add( "Import word data from " WORD_DATA_EXTENSION "...", IDC_IMPORT_SENTENCE ); + pop->add( va("%s Voice Duck", m_Tags.GetVoiceDuck() ? "Disable" : "Enable" ), IDC_TOGGLE_VOICEDUCK ); + } + + pop->popup( this, mx, my ); +} + +void PhonemeEditor::ShowContextMenu_Emphasis( int mx, int my ) +{ + Emphasis_CountSelected(); + + // Construct main + mxPopupMenu *pop = new mxPopupMenu(); + + pop->add( va( "Select All" ), IDC_EMPHASIS_SELECTALL ); + if ( m_nNumSelected > 0 ) + { + pop->add( va( "Deselect All" ), IDC_EMPHASIS_DESELECT ); + } + + if ( m_nUndoLevel != 0 || m_nUndoLevel != m_UndoStack.Size() ) + { + pop->addSeparator(); + + if ( m_nUndoLevel != 0 ) + { + pop->add( va( "Undo" ), IDC_UNDO ); + } + if ( m_nUndoLevel != m_UndoStack.Size() ) + { + pop->add( va( "Redo" ), IDC_REDO ); + } + pop->add( va( "Clear Undo Info" ), IDC_CLEARUNDO ); + } + pop->popup( this, mx, my ); +} + +void PhonemeEditor::ShowContextMenu( int mx, int my ) +{ + switch ( GetMode() ) + { + default: + case MODE_PHONEMES: + ShowContextMenu_Phonemes( mx, my ); + break; + case MODE_EMPHASIS: + ShowContextMenu_Emphasis( mx, my ); + break; + } +} + +void PhonemeEditor::ShiftSelectedPhoneme( int direction ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + CountSelected(); + + switch ( m_nSelectedPhonemeCount ) + { + case 1: + break; + case 0: + Con_Printf( "Can't shift phonemes, none selected\n" ); + return; + default: + Con_Printf( "Can only shift one phoneme at a time via keyboard\n" ); + return; + } + + RECT rc; + GetWorkspaceRect( rc ); + + // Determine start/stop positions + float starttime = m_nLeftOffset / GetPixelsPerSecond(); + float endtime = w2() / GetPixelsPerSecond() + starttime; + + float timeperpixel = ( endtime - starttime ) / (float)( rc.right - rc.left ); + + float movetime = timeperpixel * (float)direction; + + float maxmove = ComputeMaxPhonemeShift( direction > 0 ? true : false, false ); + + if ( direction > 0 ) + { + if ( movetime > maxmove ) + { + movetime = maxmove; + Con_Printf( "Further shift is blocked on right\n" ); + } + } + else + { + if ( movetime < -maxmove ) + { + movetime = -maxmove; + Con_Printf( "Further shift is blocked on left\n" ); + } + } + + if ( fabs( movetime ) < 0.0001f ) + return; + + SetDirty( true ); + + PushUndo(); + + TraversePhonemes( &PhonemeEditor::ITER_MoveSelectedPhonemes, movetime ); + + PushRedo(); + + m_bWordsActive = false; + + redraw(); + + Con_Printf( "Shift phoneme %s\n", direction == -1 ? "left" : "right" ); +} + +void PhonemeEditor::ExtendSelectedPhonemeEndTime( int direction ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + CountSelected(); + + if ( m_nSelectedPhonemeCount != 1 ) + return; + + RECT rc; + GetWorkspaceRect( rc ); + + // Determine start/stop positions + float starttime = m_nLeftOffset / GetPixelsPerSecond(); + float endtime = w2() / GetPixelsPerSecond() + starttime; + + float timeperpixel = ( endtime - starttime ) / (float)( rc.right - rc.left ); + + float movetime = timeperpixel * (float)direction; + + SetDirty( true ); + + PushUndo(); + + TraversePhonemes( &PhonemeEditor::ITER_ExtendSelectedPhonemeEndTimes, movetime ); + + PushRedo(); + + m_bWordsActive = false; + + redraw(); + + Con_Printf( "Extend phoneme end %s\n", direction == -1 ? "left" : "right" ); +} + +void PhonemeEditor::SelectNextPhoneme( int direction ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + CountSelected(); + + if ( m_nSelectedPhonemeCount != 1 ) + { + if ( m_nSelectedWordCount == 1 ) + { + CWordTag *word = GetSelectedWord(); + if ( word && word->m_Phonemes.Size() > 0 ) + { + m_nSelectedPhonemeCount = 1; + CPhonemeTag *p = word->m_Phonemes[ direction ? word->m_Phonemes.Size() - 1 : 0 ]; + p->m_bSelected = true; + } + else + { + return; + } + } + else + { + return; + } + } + + Con_Printf( "Move to next phoneme %s\n", direction == -1 ? "left" : "right" ); + + for ( int i = 0; i < m_Tags.m_Words.Size(); i++ ) + { + CWordTag *word = m_Tags.m_Words[ i ]; + if ( !word ) + continue; + + for ( int j = 0; j < word->m_Phonemes.Size(); j++ ) + { + CPhonemeTag *phoneme = word->m_Phonemes[ j ]; + if ( !phoneme ) + continue; + + if ( !phoneme->m_bSelected ) + continue; + + // Deselect this one and move + int nextindex = j + direction; + if ( nextindex < 0 ) + { + nextindex = word->m_Phonemes.Size() - 1; + } + else if ( nextindex >= word->m_Phonemes.Size() ) + { + nextindex = 0; + } + + phoneme->m_bSelected = false; + + phoneme = word->m_Phonemes[ nextindex ]; + + phoneme->m_bSelected = true; + + m_bWordsActive = false; + + redraw(); + return; + } + } +} + +bool PhonemeEditor::IsPhonemeSelected( CWordTag *word ) +{ + for ( int i = 0 ; i < word->m_Phonemes.Size(); i++ ) + { + CPhonemeTag *p = word->m_Phonemes[ i ]; + if ( !p || !p->m_bSelected ) + continue; + + return true; + } + return false; +} + +void PhonemeEditor::SelectNextWord( int direction ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + CountSelected(); + + if ( m_nSelectedWordCount != 1 && + m_nSelectedPhonemeCount != 1 ) + { + // Selected first word then + if ( m_nSelectedWordCount == 0 && m_Tags.m_Words.Size() > 0 ) + { + CWordTag *word = m_Tags.m_Words[ direction ? m_Tags.m_Words.Size() - 1 : 0 ]; + word->m_bSelected = true; + m_nSelectedWordCount = 1; + } + else + { + return; + } + } + + Con_Printf( "Move to next word %s\n", direction == -1 ? "left" : "right" ); + + for ( int i = 0; i < m_Tags.m_Words.Size(); i++ ) + { + CWordTag *word = m_Tags.m_Words[ i ]; + if ( !word ) + continue; + + if ( m_nSelectedWordCount == 1 ) + { + if ( !word->m_bSelected ) + continue; + } + else + { + if ( !IsPhonemeSelected( word ) ) + continue; + } + + // Deselect word + word->m_bSelected = false; + + for ( int j = 0; j < word->m_Phonemes.Size(); j++ ) + { + CPhonemeTag *phoneme = word->m_Phonemes[ j ]; + if ( !phoneme ) + continue; + + if ( !phoneme->m_bSelected ) + continue; + + phoneme->m_bSelected = false; + } + + // Deselect this one and move + int nextword = i + direction; + if ( nextword < 0 ) + { + nextword = m_Tags.m_Words.Size() - 1; + } + else if ( nextword >= m_Tags.m_Words.Size() ) + { + nextword = 0; + } + + word = m_Tags.m_Words[ nextword ]; + word->m_bSelected = true; + + if ( word->m_Phonemes.Size() > 0 ) + { + CPhonemeTag *phoneme = NULL; + + if ( direction > 0 ) + { + phoneme = word->m_Phonemes[ 0 ]; + } + else + { + phoneme = word->m_Phonemes[ word->m_Phonemes.Size() - 1 ]; + } + + phoneme->m_bSelected = true; + } + + m_bWordsActive = true; + + redraw(); + return; + } +} + +void PhonemeEditor::ShiftSelectedWord( int direction ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + CountSelected(); + + switch ( m_nSelectedWordCount ) + { + case 1: + break; + case 0: + Con_Printf( "Can't shift words, none selected\n" ); + return; + default: + Con_Printf( "Can only shift one word at a time via keyboard\n" ); + return; + } + + RECT rc; + GetWorkspaceRect( rc ); + + // Determine start/stop positions + float starttime = m_nLeftOffset / GetPixelsPerSecond(); + float endtime = w2() / GetPixelsPerSecond() + starttime; + + float timeperpixel = ( endtime - starttime ) / (float)( rc.right - rc.left ); + + float movetime = timeperpixel * (float)direction; + + float maxmove = ComputeMaxWordShift( direction > 0 ? true : false, false ); + + if ( direction > 0 ) + { + if ( movetime > maxmove ) + { + movetime = maxmove; + Con_Printf( "Further shift is blocked on right\n" ); + } + } + else + { + if ( movetime < -maxmove ) + { + movetime = -maxmove; + Con_Printf( "Further shift is blocked on left\n" ); + } + } + + if ( fabs( movetime ) < 0.0001f ) + return; + + SetDirty( true ); + + PushUndo(); + + TraverseWords( &PhonemeEditor::ITER_MoveSelectedWords, movetime ); + + PushRedo(); + + m_bWordsActive = true; + + redraw(); + + Con_Printf( "Shift word %s\n", direction == -1 ? "left" : "right" ); +} + +void PhonemeEditor::ExtendSelectedWordEndTime( int direction ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + CountSelected(); + + if ( m_nSelectedWordCount != 1 ) + return; + + RECT rc; + GetWorkspaceRect( rc ); + + // Determine start/stop positions + float starttime = m_nLeftOffset / GetPixelsPerSecond(); + float endtime = w2() / GetPixelsPerSecond() + starttime; + + float timeperpixel = ( endtime - starttime ) / (float)( rc.right - rc.left ); + + float movetime = timeperpixel * (float)direction; + + SetDirty( true ); + + PushUndo(); + + TraverseWords( &PhonemeEditor::ITER_ExtendSelectedWordEndTimes, movetime ); + + PushRedo(); + + m_bWordsActive = true; + + redraw(); + + Con_Printf( "Extend word end %s\n", direction == -1 ? "left" : "right" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *word - +// Output : int +//----------------------------------------------------------------------------- +int PhonemeEditor::IndexOfWord( CWordTag *word ) +{ + for ( int i = 0 ; i < m_Tags.m_Words.Size(); i++ ) + { + if ( m_Tags.m_Words[ i ] == word ) + return i; + } + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : forward - +// *currentWord - +// **nextWord - +// Output : float +//----------------------------------------------------------------------------- +float PhonemeEditor::GetTimeGapToNextWord( bool forward, CWordTag *currentWord, CWordTag **ppNextWord /* = NULL */ ) +{ + if ( ppNextWord ) + { + *ppNextWord = NULL; + } + + if ( !currentWord ) + return 0.0f; + + int wordnum = IndexOfWord( currentWord ); + if ( wordnum == -1 ) + return 0.0f; + + // Go in correct direction + int newwordnum = wordnum + ( forward ? 1 : -1 ); + + // There is no next word + if ( newwordnum >= m_Tags.m_Words.Size() ) + { + return PLENTY_OF_TIME; + } + + // There is no previous word + if ( newwordnum < 0 ) + { + return PLENTY_OF_TIME; + } + + if ( ppNextWord ) + { + *ppNextWord = m_Tags.m_Words[ newwordnum ]; + } + + // Otherwise, figure out time gap + if ( forward ) + { + float currentEnd = currentWord->m_flEndTime; + float nextStart = m_Tags.m_Words[ newwordnum ]->m_flStartTime; + + return ( nextStart - currentEnd ); + } + else + { + float previousEnd = m_Tags.m_Words[ newwordnum ]->m_flEndTime; + float currentStart = currentWord->m_flStartTime; + + return ( currentStart - previousEnd ); + } + + + Assert( 0 ); + return 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : forward - +// *currentPhoneme - +// **word - +// **phoneme - +// Output : float +//----------------------------------------------------------------------------- +float PhonemeEditor::GetTimeGapToNextPhoneme( bool forward, CPhonemeTag *currentPhoneme, + CWordTag **ppword /* = NULL */, CPhonemeTag **ppphoneme /* = NULL */ ) +{ + if ( ppword ) + { + *ppword = NULL; + } + if ( ppphoneme ) + { + *ppphoneme = NULL; + } + + if ( !currentPhoneme ) + return 0.0f; + + CWordTag *word = m_Tags.GetWordForPhoneme( currentPhoneme ); + if ( !word ) + return 0.0f; + + int wordnum = IndexOfWord( word ); + Assert( wordnum != -1 ); + + int phonemenum = word->IndexOfPhoneme( currentPhoneme ); + if ( phonemenum < 0 ) + return 0.0f; + + CPhonemeTag *nextPhoneme = NULL; + + int nextphoneme = phonemenum + ( forward ? 1 : -1 ); + + // Try last phoneme of previous word + if ( nextphoneme < 0 ) + { + wordnum--; + while ( wordnum >= 0 ) + { + if ( ppword ) + { + *ppword = m_Tags.m_Words[ wordnum ]; + } + if ( m_Tags.m_Words.Size() > 0 ) + { + if ( m_Tags.m_Words[ wordnum ]->m_Phonemes.Size() > 0 ) + { + nextPhoneme = m_Tags.m_Words[ wordnum ]->m_Phonemes[ m_Tags.m_Words[ wordnum ]->m_Phonemes.Size() - 1 ]; + break; + } + } + wordnum--; + } + } + // Try first phoneme of next word, if there is one + else if ( nextphoneme >= word->m_Phonemes.Size() ) + { + wordnum++; + while ( wordnum < m_Tags.m_Words.Size() ) + { + if ( ppword ) + { + *ppword = m_Tags.m_Words[ wordnum ]; + } + // Really it can't be zero, but check anyway + if ( m_Tags.m_Words.Size() > 0 ) + { + if ( m_Tags.m_Words[ wordnum ]->m_Phonemes.Size() > 0 ) + { + nextPhoneme = m_Tags.m_Words[ wordnum ]->m_Phonemes[ 0 ]; + break; + } + } + wordnum++; + } + } + else + { + nextPhoneme = word->m_Phonemes[ nextphoneme ]; + } + + if ( !nextPhoneme ) + return PLENTY_OF_TIME; + + if ( ppphoneme ) + { + *ppphoneme = nextPhoneme; + } + + // Now compute time delta + float dt = 0.0f; + if ( forward ) + { + dt = nextPhoneme->GetStartTime() - currentPhoneme->GetEndTime(); + } + else + { + dt = currentPhoneme->GetStartTime() - nextPhoneme->GetEndTime(); + } + + return dt; +} + +CPhonemeTag *PhonemeEditor::GetSelectedPhoneme( void ) +{ + CountSelected(); + + if ( m_nSelectedPhonemeCount != 1 ) + return NULL; + + for ( int i = 0; i < m_Tags.m_Words.Size(); i++ ) + { + CWordTag *w = m_Tags.m_Words[ i ]; + if ( !w ) + continue; + + for ( int j = 0; j < w->m_Phonemes.Size() ; j++ ) + { + CPhonemeTag *p = w->m_Phonemes[ j ]; + if ( !p || !p->m_bSelected ) + continue; + + return p; + } + } + return NULL; +} + +CWordTag *PhonemeEditor::GetSelectedWord( void ) +{ + CountSelected(); + + if ( m_nSelectedWordCount != 1 ) + return NULL; + + for ( int i = 0; i < m_Tags.m_Words.Size(); i++ ) + { + CWordTag *w = m_Tags.m_Words[ i ]; + if ( !w || !w->m_bSelected ) + continue; + + return w; + } + return NULL; +} + +void PhonemeEditor::OnMouseMove( mxEvent *event ) +{ + int mx = (short)event->x; + + LimitDrag( mx ); + + event->x = (short)mx; + + if ( m_nDragType != DRAGTYPE_NONE ) + { + DrawFocusRect( "moving old" ); + + for ( int i = 0; i < m_FocusRects.Size(); i++ ) + { + CFocusRect *f = &m_FocusRects[ i ]; + f->m_rcFocus = f->m_rcOrig; + + switch ( m_nDragType ) + { + default: + { + // Only X Shifts supported + OffsetRect( &f->m_rcFocus, ( (short)event->x - m_nStartX ), + 0 ); + } + break; + case DRAGTYPE_EMPHASIS_SELECT: + { + RECT rcWork; + GetWorkspaceRect( rcWork ); + RECT rcEmphasis; + Emphasis_GetRect( rcWork, rcEmphasis ); + + RECT rcFocus; + + rcFocus = f->m_rcOrig; + + rcFocus.left = m_nStartX < (short)event->x ? m_nStartX : (short)event->x; + rcFocus.right = m_nStartX < (short)event->x ? (short)event->x : m_nStartX; + + rcFocus.top = m_nStartY < (short)event->y ? m_nStartY : (short)event->y; + rcFocus.bottom = m_nStartY < (short)event->y ? (short)event->y : m_nStartY; + + rcFocus.top = clamp( rcFocus.top, rcEmphasis.top, rcEmphasis.bottom ); + rcFocus.bottom = clamp( rcFocus.bottom, rcEmphasis.top, rcEmphasis.bottom ); + + //OffsetRect( &rcFocus, 0, -rcEmphasis.top ); + + POINT offset; + offset.x = 0; + offset.y = 0; + ClientToScreen( (HWND)getHandle(), &offset ); + OffsetRect( &rcFocus, offset.x, offset.y ); + + f->m_rcFocus = rcFocus; + } + break; + } + } + + if ( m_nDragType == DRAGTYPE_EMPHASIS_MOVE ) + { + redraw(); + } + + DrawFocusRect( "moving new" ); + } + else + { + if ( m_hPrevCursor ) + { + SetCursor( m_hPrevCursor ); + m_hPrevCursor = NULL; + } + + CountSelected(); + + int overhandle = IsMouseOverBoundary( event ); + if ( overhandle == BOUNDARY_PHONEME && m_nSelectedPhonemeCount <= 1 ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + } + else if ( overhandle == BOUNDARY_WORD && m_nSelectedWordCount <= 1 ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + } + else if ( IsMouseOverSelection( (short)event->x, (short)event->y ) ) + { + if ( IsMouseOverSelectionStartEdge( event ) ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + } + else if ( IsMouseOverSelectionEndEdge( event ) ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEWE ) ); + } + else + { + if ( event->modifiers & mxEvent::KeyShift ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEALL ) ); + } + } + } + else + { + if ( IsMouseOverTag( (short)event->x, (short)event->y ) ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEALL ) ); + } + else + { + CPhonemeTag *pt = GetPhonemeTagUnderMouse( (short)event->x, (short)event->y ); + CWordTag *wt = GetWordTagUnderMouse( (short)event->x, (short)event->y ); + if ( wt || pt ) + { + if ( pt ) + { + // Select it + SelectExpression( pt ); + } + if ( event->modifiers & mxEvent::KeyShift ) + { + m_hPrevCursor = SetCursor( LoadCursor( NULL, IDC_SIZEALL ) ); + } + } + } + } + } + + switch ( m_nDragType ) + { + default: + break; + case DRAGTYPE_EMPHASIS_MOVE: + { + Emphasis_MouseDrag( (short)event->x, (short)event->y ); + m_Tags.Resort(); + } + break; + case DRAGTYPE_SCRUBBER: + { + float t = GetTimeForPixel( (short)event->x ); + t += m_flScrubberTimeOffset; + + ClampTimeToSelectionInterval( t ); + + float dt = t - m_flScrub; + + SetScrubTargetTime( t ); + + ScrubThink( dt, true ); + + SetScrubTime( t ); + + DrawScrubHandle(); + } + break; + } + + m_nLastX = (short)event->x; + m_nLastY = (short)event->y; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::EditInsertFirstPhonemeOfWord( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + CWordTag *cw = GetSelectedWord(); + if ( !cw ) + return; + + if ( cw->m_Phonemes.Size() != 0 ) + { + Con_Printf( "Can't insert first phoneme into %s, already has phonemes\n", cw->GetWord() ); + return; + } + + CPhonemeParams params; + memset( ¶ms, 0, sizeof( params ) ); + strcpy( params.m_szDialogTitle, "Phoneme/Viseme Properties" ); + strcpy( params.m_szName, "" ); + + params.m_nLeft = -1; + params.m_nTop = -1; + + params.m_bPositionDialog = true; + params.m_bMultiplePhoneme = true; + + if ( params.m_bPositionDialog ) + { + RECT rcWord; + GetWordRect( cw, rcWord ); + + // Convert to screen coords + POINT pt; + pt.x = rcWord.left; + pt.y = rcWord.top; + + ClientToScreen( (HWND)getHandle(), &pt ); + + params.m_nLeft = pt.x; + params.m_nTop = pt.y; + } + + int iret = PhonemeProperties( ¶ms ); + SetFocus( (HWND)getHandle() ); + if ( !iret ) + { + return; + } + + int phonemeCount = CSentence::CountWords( params.m_szName ); + if ( phonemeCount <= 0 ) + { + return; + } + + float wordLength = cw->m_flEndTime - cw->m_flStartTime; + float timePerPhoneme = wordLength / (float)phonemeCount; + + float currentTime = cw->m_flStartTime; + + SetDirty( true ); + + PushUndo(); + + unsigned char *in; + char *out; + + char phonemeName[ 128 ]; + + in = (unsigned char *)params.m_szName; + + do + { + out = phonemeName; + + while ( *in > 32 ) + { + *out++ = *in++; + } + *out = 0; + + CPhonemeTag phoneme; + + phoneme.SetPhonemeCode( TextToPhoneme( phonemeName ) ); + phoneme.SetTag( phonemeName ); + + phoneme.SetStartTime( currentTime ); + phoneme.SetEndTime( currentTime + timePerPhoneme ); + phoneme.m_bSelected = false; + + cw->m_Phonemes.AddToTail( new CPhonemeTag( phoneme ) ); + + currentTime += timePerPhoneme; + + if ( !*in ) + break; + + // Skip whitespace + in++; + + } while ( 1 ); + + cw->m_Phonemes[ 0 ]->m_bSelected = true; + + PushRedo(); + + // Add it + redraw(); +} + +void PhonemeEditor::SelectPhonemes( bool forward ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + CountSelected(); + + if ( m_nSelectedPhonemeCount != 1 ) + return; + + CPhonemeTag *phoneme = GetSelectedPhoneme(); + if ( !phoneme ) + return; + + // Figure out it's word and index + CWordTag *word = m_Tags.GetWordForPhoneme( phoneme ); + if ( !word ) + return; + + int wordNum = IndexOfWord( word ); + if ( wordNum == -1 ) + return; + + // Select remaining phonemes in current word + int i; + + i = word->IndexOfPhoneme( phoneme ); + if ( i == -1 ) + return; + + if ( forward ) + { + // Start at next one + i++; + + for ( ; i < word->m_Phonemes.Size(); i++ ) + { + phoneme = word->m_Phonemes[ i ]; + phoneme->m_bSelected = true; + } + + // Now start at next word + wordNum++; + + for ( ; wordNum < m_Tags.m_Words.Size(); wordNum++ ) + { + word = m_Tags.m_Words[ wordNum ]; + + for ( int j = 0; j < word->m_Phonemes.Size(); j++ ) + { + phoneme = word->m_Phonemes[ j ]; + phoneme->m_bSelected = true; + } + } + } + else + { + // Start at previous + i--; + + for ( ; i >= 0; i-- ) + { + phoneme = word->m_Phonemes[ i ]; + phoneme->m_bSelected = true; + } + + // Now start at previous word + wordNum--; + + for ( ; wordNum >= 0 ; wordNum-- ) + { + word = m_Tags.m_Words[ wordNum ]; + + for ( int j = 0; j < word->m_Phonemes.Size(); j++ ) + { + phoneme = word->m_Phonemes[ j ]; + phoneme->m_bSelected = true; + } + } + } + + redraw(); +} + +void PhonemeEditor::SelectWords( bool forward ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + CountSelected(); + + if ( m_nSelectedWordCount != 1 ) + return; + + // Figure out it's word and index + CWordTag *word = GetSelectedWord(); + if ( !word ) + return; + + int wordNum = IndexOfWord( word ); + if ( wordNum == -1 ) + return; + + if ( forward ) + { + wordNum++; + + for ( ; wordNum < m_Tags.m_Words.Size(); wordNum++ ) + { + word = m_Tags.m_Words[ wordNum ]; + word->m_bSelected = true; + } + } + else + { + wordNum--; + + for ( ; wordNum >= 0; wordNum-- ) + { + word = m_Tags.m_Words[ wordNum ]; + word->m_bSelected = true; + } + + } + + redraw(); +} + +bool PhonemeEditor::AreSelectedWordsContiguous( void ) +{ + CountSelected(); + + if ( m_nSelectedWordCount < 1 ) + return false; + + if ( m_nSelectedWordCount == 1 ) + return true; + + // Find first selected word + int runcount = 0; + bool parity = false; + + for ( int i = 0 ; i < m_Tags.m_Words.Size() ; i++ ) + { + CWordTag *word = m_Tags.m_Words[ i ]; + if ( !word ) + continue; + + if ( word->m_bSelected ) + { + if ( !parity ) + { + parity = true; + runcount++; + } + } + else + { + if ( parity ) + { + parity = false; + } + } + } + + if ( runcount == 1 ) + return true; + + return false; +} + +bool PhonemeEditor::AreSelectedPhonemesContiguous( void ) +{ + CountSelected(); + + if ( m_nSelectedPhonemeCount < 1 ) + return false; + + if ( m_nSelectedPhonemeCount == 1 ) + return true; + + // Find first selected word + int runcount = 0; + bool parity = false; + + for ( int i = 0 ; i < m_Tags.m_Words.Size() ; i++ ) + { + CWordTag *word = m_Tags.m_Words[ i ]; + if ( !word ) + continue; + + for ( int j = 0 ; j < word->m_Phonemes.Size(); j++ ) + { + CPhonemeTag *phoneme = word->m_Phonemes[ j ]; + if ( !phoneme ) + continue; + + if ( phoneme->m_bSelected ) + { + if ( !parity ) + { + parity = true; + runcount++; + } + } + else + { + if ( parity ) + { + parity = false; + } + } + } + } + + if ( runcount == 1 ) + return true; + + return false; + +} + +void PhonemeEditor::SortWords( bool prepareundo ) +{ + if ( prepareundo ) + { + SetDirty( true ); + PushUndo(); + } + + // Just bubble sort by start time + int c = m_Tags.m_Words.Count(); + + int i; + + // check for start > end + for ( i = 0; i < c; i++ ) + { + CWordTag *p1 = m_Tags.m_Words[ i ]; + if (p1->m_flStartTime > p1->m_flEndTime ) + { + float swap = p1->m_flStartTime; + p1->m_flStartTime = p1->m_flEndTime; + p1->m_flEndTime = swap; + } + } + + for ( i = 0; i < c; i++ ) + { + for ( int j = i + 1; j < c; j++ ) + { + CWordTag *p1 = m_Tags.m_Words[ i ]; + CWordTag *p2 = m_Tags.m_Words[ j ]; + + if ( p1->m_flStartTime < p2->m_flStartTime ) + continue; + + // Swap them + m_Tags.m_Words[ i ] = p2; + m_Tags.m_Words[ j ] = p1; + } + } + + if ( prepareundo ) + { + PushRedo(); + } +} + +void PhonemeEditor::SortPhonemes( bool prepareundo ) +{ + if ( prepareundo ) + { + SetDirty( true ); + PushUndo(); + } + + // Just bubble sort by start time + int wc = m_Tags.m_Words.Count(); + for ( int w = 0; w < wc; w++ ) + { + CWordTag *word = m_Tags.m_Words[ w ]; + Assert( word ); + + int c = word->m_Phonemes.Count(); + int i; + + // check for start > end + for ( i = 0; i < c; i++ ) + { + CPhonemeTag *p1 = word->m_Phonemes[ i ]; + + if (p1->GetStartTime() > p1->GetEndTime() ) + { + float swap = p1->GetStartTime(); + p1->SetStartTime( p1->GetEndTime() ); + p1->SetEndTime( swap ); + } + } + + for ( i = 0; i < c; i++ ) + { + for ( int j = i + 1; j < c; j++ ) + { + CPhonemeTag *p1 = word->m_Phonemes[ i ]; + CPhonemeTag *p2 = word->m_Phonemes[ j ]; + + if ( p1->GetStartTime() < p2->GetStartTime() ) + continue; + + // Swap them + word->m_Phonemes[ i ] = p2; + word->m_Phonemes[ j ] = p1; + } + } + } + + if ( prepareundo ) + { + PushRedo(); + } +} + +void PhonemeEditor::CleanupWordsAndPhonemes( bool prepareundo ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + // 2 pixel gap + float snap_epsilon = 2.49f / GetPixelsPerSecond(); + + if ( prepareundo ) + { + SetDirty( true ); + PushUndo(); + } + + SortWords( false ); + SortPhonemes( false ); + + for ( int i = 0 ; i < m_Tags.m_Words.Size() ; i++ ) + { + CWordTag *word = m_Tags.m_Words[ i ]; + if ( !word ) + continue; + + CWordTag *next = NULL; + if ( i < m_Tags.m_Words.Size() - 1 ) + { + next = m_Tags.m_Words[ i + 1 ]; + } + + if ( word && next ) + { + // Check for words close enough + float eps = next->m_flStartTime - word->m_flEndTime; + if ( eps && eps <= snap_epsilon ) + { + float t = (word->m_flEndTime + next->m_flStartTime) * 0.5; + word->m_flEndTime = t; + next->m_flStartTime = t; + } + } + + for ( int j = 0 ; j < word->m_Phonemes.Size(); j++ ) + { + CPhonemeTag *phoneme = word->m_Phonemes[ j ]; + if ( !phoneme ) + continue; + + CPhonemeTag *next = NULL; + if ( j < word->m_Phonemes.Size() - 1 ) + { + next = word->m_Phonemes[ j + 1 ]; + } + + if ( phoneme && next ) + { + float eps = next->GetStartTime() - phoneme->GetEndTime(); + if ( eps && eps <= snap_epsilon ) + { + float t = (phoneme->GetEndTime() + next->GetStartTime() ) * 0.5; + phoneme->SetEndTime( t ); + next->SetStartTime( t ); + } + } + } + } + + if ( prepareundo ) + { + PushRedo(); + } + + // NOTE: Caller must call "redraw()" to get screen to update +} + + + +void PhonemeEditor::RealignPhonemesToWords( bool prepareundo ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + if ( prepareundo ) + { + SetDirty( true ); + PushUndo(); + } + + SortWords( false ); + SortPhonemes( false ); + + for ( int i = 0 ; i < m_Tags.m_Words.Size() ; i++ ) + { + CWordTag *word = m_Tags.m_Words[ i ]; + if ( !word ) + continue; + + CWordTag *next = NULL; + if ( i < m_Tags.m_Words.Size() - 1 ) + { + next = m_Tags.m_Words[ i + 1 ]; + } + + float word_dt = word->m_flEndTime - word->m_flStartTime; + + CPhonemeTag *FirstPhoneme = word->m_Phonemes[ 0 ]; + if ( !FirstPhoneme ) + continue; + + CPhonemeTag *LastPhoneme = word->m_Phonemes[ word->m_Phonemes.Size() - 1 ]; + if ( !LastPhoneme ) + continue; + + float phoneme_dt = LastPhoneme->GetEndTime() - FirstPhoneme->GetStartTime(); + + float phoneme_shift = FirstPhoneme->GetStartTime(); + + for ( int j = 0 ; j < word->m_Phonemes.Size(); j++ ) + { + CPhonemeTag *phoneme = word->m_Phonemes[ j ]; + if ( !phoneme ) + continue; + + CPhonemeTag *next = NULL; + if ( j < word->m_Phonemes.Size() - 1 ) + { + next = word->m_Phonemes[ j + 1 ]; + } + + if (j == 0) + { + float t = (phoneme->GetStartTime() - phoneme_shift ) * (word_dt / phoneme_dt) + word->m_flStartTime; + phoneme->SetStartTime( t ); + } + + float t = (phoneme->GetEndTime() - phoneme_shift ) * (word_dt / phoneme_dt) + word->m_flStartTime; + phoneme->SetEndTime( t ); + if (next) + { + next->SetStartTime( t ); + } + } + } + + if ( prepareundo ) + { + PushRedo(); + } + + // NOTE: Caller must call "redraw()" to get screen to update +} + + +void PhonemeEditor::RealignWordsToPhonemes( bool prepareundo ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + if ( prepareundo ) + { + SetDirty( true ); + PushUndo(); + } + + SortWords( false ); + SortPhonemes( false ); + + for ( int i = 0 ; i < m_Tags.m_Words.Size() ; i++ ) + { + CWordTag *word = m_Tags.m_Words[ i ]; + if ( !word ) + continue; + + CPhonemeTag *FirstPhoneme = word->m_Phonemes[ 0 ]; + if ( !FirstPhoneme ) + continue; + + CPhonemeTag *LastPhoneme = word->m_Phonemes[ word->m_Phonemes.Size() - 1 ]; + if ( !LastPhoneme ) + continue; + + word->m_flStartTime = FirstPhoneme->GetStartTime(); + word->m_flEndTime = LastPhoneme->GetEndTime(); + } + + if ( prepareundo ) + { + PushRedo(); + } + + // NOTE: Caller must call "redraw()" to get screen to update +} + + + +float PhonemeEditor::ComputeMaxWordShift( bool forward, bool allowcrop ) +{ + // skipping selected words, figure out max time shift of words before they selection touches any + // unselected words + // if allowcrop is true, then the maximum extends up to end of next word + float maxshift = PLENTY_OF_TIME; + + if ( forward ) + { + for ( int i = 0; i < m_Tags.m_Words.Size(); i++ ) + { + CWordTag *w1 = m_Tags.m_Words[ i ]; + if ( !w1 || !w1->m_bSelected ) + continue; + + CWordTag *w2 = NULL; + for ( int search = i + 1; search < m_Tags.m_Words.Size() ; search++ ) + { + CWordTag *check = m_Tags.m_Words[ search ]; + if ( !check || check->m_bSelected ) + continue; + + w2 = check; + break; + } + + if ( w2 ) + { + float shift; + if ( allowcrop ) + { + shift = w2->m_flEndTime - w1->m_flEndTime; + } + else + { + shift = w2->m_flStartTime - w1->m_flEndTime; + } + + if ( shift < maxshift ) + { + maxshift = shift; + } + } + } + } + else + { + for ( int i = m_Tags.m_Words.Size() -1; i >= 0; i-- ) + { + CWordTag *w1 = m_Tags.m_Words[ i ]; + if ( !w1 || !w1->m_bSelected ) + continue; + + CWordTag *w2 = NULL; + for ( int search = i - 1; search >= 0 ; search-- ) + { + CWordTag *check = m_Tags.m_Words[ search ]; + if ( !check || check->m_bSelected ) + continue; + + w2 = check; + break; + } + + if ( w2 ) + { + float shift; + if ( allowcrop ) + { + shift = w1->m_flStartTime - w2->m_flStartTime; + } + else + { + shift = w1->m_flStartTime - w2->m_flEndTime; + } + + if ( shift < maxshift ) + { + maxshift = shift; + } + } + } + } + + return maxshift; +} + +float PhonemeEditor::ComputeMaxPhonemeShift( bool forward, bool allowcrop ) +{ + // skipping selected phonemes, figure out max time shift of phonemes before they selection touches any + // unselected words + // if allowcrop is true, then the maximum extends up to end of next word + float maxshift = PLENTY_OF_TIME; + + if ( forward ) + { + for ( int i = 0; i < m_Tags.m_Words.Size(); i++ ) + { + CWordTag *word = m_Tags.m_Words[ i ]; + if ( !word ) + continue; + + for ( int j = 0; j < word->m_Phonemes.Size(); j++ ) + { + CPhonemeTag *p1 = word->m_Phonemes[ j ]; + if ( !p1 || !p1->m_bSelected ) + continue; + + // Find next unselected phoneme + CPhonemeTag *p2 = NULL; + + CPhonemeTag *start = p1; + do + { + CPhonemeTag *test = NULL; + GetTimeGapToNextPhoneme( forward, start, NULL, &test ); + if ( !test ) + break; + + if ( test->m_bSelected ) + { + start = test; + continue; + } + + p2 = test; + break; + } while ( 1 ); + + if ( p2 ) + { + float shift; + if ( allowcrop ) + { + shift = p2->GetEndTime() - p1->GetEndTime(); + } + else + { + shift = p2->GetStartTime() - p1->GetEndTime(); + } + + if ( shift < maxshift ) + { + maxshift = shift; + } + } + } + } + } + else + { + for ( int i = m_Tags.m_Words.Size() -1; i >= 0; i-- ) + { + CWordTag *word = m_Tags.m_Words[ i ]; + if ( !word ) + continue; + + for ( int j = word->m_Phonemes.Size() - 1; j >= 0; j-- ) + { + CPhonemeTag *p1 = word->m_Phonemes[ j ]; + if ( !p1 || !p1->m_bSelected ) + continue; + + // Find previous unselected phoneme + CPhonemeTag *p2 = NULL; + + CPhonemeTag *start = p1; + do + { + CPhonemeTag *test = NULL; + GetTimeGapToNextPhoneme( forward, start, NULL, &test ); + if ( !test ) + break; + + if ( test->m_bSelected ) + { + start = test; + continue; + } + + p2 = test; + break; + } while ( 1 ); + + if ( p2 ) + { + float shift; + if ( allowcrop ) + { + shift = p1->GetStartTime() - p2->GetStartTime(); + } + else + { + shift = p1->GetStartTime() - p2->GetEndTime(); + } + + if ( shift < maxshift ) + { + maxshift = shift; + } + } + } + } + } + + return maxshift; +} + +int PhonemeEditor::PixelsForDeltaTime( float dt ) +{ + if ( !dt ) + return 0; + + RECT rc; + GetWorkspaceRect( rc ); + + float starttime = m_nLeftOffset / GetPixelsPerSecond(); + float endtime = w2() / GetPixelsPerSecond() + starttime; + + float timeperpixel = ( endtime - starttime ) / (float)( rc.right - rc.left ); + + float pixels = dt / timeperpixel; + + return abs( (int)pixels ); +} + +void PhonemeEditor::ClearDragLimit( void ) +{ + m_bLimitDrag = false; + m_nLeftLimit = -1; + m_nRightLimit = -1; +} + +void PhonemeEditor::SetDragLimit( int dragtype ) +{ + ClearDragLimit(); + + float nextW, nextP; + float prevW, prevP; + + nextW = ComputeMaxWordShift( true, false ); + prevW = ComputeMaxWordShift( false, false ); + nextP = ComputeMaxPhonemeShift( true, false ); + prevP = ComputeMaxPhonemeShift( false, false ); + + /* + Con_Printf( "+w %f -w %f +p %f -p %f\n", + 1000.0f * nextW, + 1000.0f * prevW, + 1000.0f * nextP, + 1000.0f * prevP ); + */ + + switch ( dragtype ) + { + case DRAGTYPE_MOVEWORD: + m_bLimitDrag = true; + m_nLeftLimit = PixelsForDeltaTime( prevW ); + m_nRightLimit = PixelsForDeltaTime( nextW ); + break; + case DRAGTYPE_MOVEPHONEME: + m_bLimitDrag = true; + m_nLeftLimit = PixelsForDeltaTime( prevP ); + m_nRightLimit = PixelsForDeltaTime( nextP ); + break; + default: + ClearDragLimit(); + break; + } +} + +void PhonemeEditor::LimitDrag( int& mousex ) +{ + if ( m_nDragType == DRAGTYPE_NONE ) + return; + + if ( !m_bLimitDrag ) + return; + + int delta = mousex - m_nStartX; + if ( delta > 0 ) + { + if ( m_nRightLimit >= 0 ) + { + if ( delta > m_nRightLimit ) + { + mousex = m_nStartX + m_nRightLimit; + } + } + } + else if ( delta < 0 ) + { + if ( m_nLeftLimit >= 0 ) + { + if ( abs( delta ) > abs( m_nLeftLimit ) ) + { + mousex = m_nStartX - m_nLeftLimit; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Wipe undo/redo data +//----------------------------------------------------------------------------- +void PhonemeEditor::ClearUndo( void ) +{ + WipeUndo(); + WipeRedo(); + + SetDirty( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *tag - +//----------------------------------------------------------------------------- +void PhonemeEditor::SelectExpression( CPhonemeTag *tag ) +{ + if ( !models->GetActiveStudioModel() ) + return; + + CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); + if ( !hdr ) + return; + + // Make sure phonemes are loaded + FacePoser_EnsurePhonemesLoaded(); + + CExpClass *cl = expressions->FindClass( "phonemes", true ); + if ( !cl ) + { + Con_Printf( "Couldn't load expressions/phonemes.txt!\n" ); + return; + } + + if ( expressions->GetActiveClass() != cl ) + { + expressions->ActivateExpressionClass( cl ); + } + + CExpression *exp = cl->FindExpression( ConvertPhoneme( tag->GetPhonemeCode() ) ); + if ( !exp ) + { + Con_Printf( "Couldn't find phoneme '%s'\n", ConvertPhoneme( tag->GetPhonemeCode() ) ); + return; + } + + float *settings = exp->GetSettings(); + for (LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++) + { + int j = hdr->pFlexcontroller( i )->localToGlobal; + + models->GetActiveStudioModel()->SetFlexController( i, settings[j] ); + } +} + +void PhonemeEditor::OnSAPI( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + g_viewerSettings.speechapiindex = SPEECH_API_SAPI; + + m_pPhonemeExtractor = NULL; + + CheckSpeechAPI(); + + redraw(); +} + +void PhonemeEditor::OnLipSinc( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + return; + + g_viewerSettings.speechapiindex = SPEECH_API_LIPSINC; + + m_pPhonemeExtractor = NULL; + + CheckSpeechAPI(); + + redraw(); +} + +void PhonemeEditor::LoadPhonemeConverters() +{ + m_pPhonemeExtractor = NULL; + + // Enumerate modules under bin folder of exe + FileFindHandle_t findHandle; + const char *pFilename = filesystem->FindFirstEx( "phonemeextractors/*.dll", "EXECUTABLE_PATH", &findHandle ); + while( pFilename ) + { + char fullpath[ 512 ]; + Q_snprintf( fullpath, sizeof( fullpath ), "phonemeextractors/%s", pFilename ); + + Con_Printf( "Loading extractor from %s\n", fullpath ); + + Extractor e; + e.module = Sys_LoadModule( fullpath ); + if ( !e.module ) + { + pFilename = filesystem->FindNext( findHandle ); + continue; + } + + CreateInterfaceFn factory = Sys_GetFactory( e.module ); + if ( !factory ) + { + pFilename = filesystem->FindNext( findHandle ); + continue; + } + + e.extractor = ( IPhonemeExtractor * )factory( VPHONEME_EXTRACTOR_INTERFACE, NULL ); + if ( !e.extractor ) + { + Warning( "Unable to get IPhonemeExtractor interface version %s from %s\n", VPHONEME_EXTRACTOR_INTERFACE, fullpath ); + pFilename = filesystem->FindNext( findHandle ); + continue; + } + + e.apitype = e.extractor->GetAPIType(); + + g_Extractors.AddToTail( e ); + pFilename = filesystem->FindNext( findHandle ); + } + + filesystem->FindClose( findHandle ); +} + +void PhonemeEditor::ValidateSpeechAPIIndex() +{ + if ( !DoesExtractorExistFor( (PE_APITYPE)g_viewerSettings.speechapiindex ) ) + { + if ( g_Extractors.Count() > 0 ) + g_viewerSettings.speechapiindex = g_Extractors[0].apitype; + } +} + +void PhonemeEditor::UnloadPhonemeConverters() +{ + int c = g_Extractors.Count(); + for ( int i = c - 1; i >= 0; i-- ) + { + Extractor *e = &g_Extractors[ i ]; + Sys_UnloadModule( e->module ); + } + + g_Extractors.RemoveAll(); + + m_pPhonemeExtractor = NULL; +} + +bool PhonemeEditor::CheckSpeechAPI( void ) +{ + if ( GetMode() != MODE_PHONEMES ) + { + return false; + } + + if ( !m_pPhonemeExtractor ) + { + int c = g_Extractors.Count(); + for ( int i = 0; i < c; i++ ) + { + Extractor *e = &g_Extractors[ i ]; + if ( e->apitype == (PE_APITYPE)g_viewerSettings.speechapiindex ) + { + m_pPhonemeExtractor = e->extractor; + break; + } + } + + if ( !m_pPhonemeExtractor ) + { + Con_ErrorPrintf( "Couldn't find phoneme extractor %i\n", + g_viewerSettings.speechapiindex ); + } + } + + return m_pPhonemeExtractor != NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +char const *PhonemeEditor::GetSpeechAPIName( void ) +{ + CheckSpeechAPI(); + + if ( m_pPhonemeExtractor ) + { + return m_pPhonemeExtractor->GetName(); + } + + return "Unknown Speech API"; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool PhonemeEditor::PaintBackground( void ) +{ + redraw(); + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : PhonemeEditor::EditorMode +//----------------------------------------------------------------------------- +PhonemeEditor::EditorMode PhonemeEditor::GetMode( void ) const +{ + return m_CurrentMode; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rcWorkSpace - +// rcEmphasis - +//----------------------------------------------------------------------------- +void PhonemeEditor::Emphasis_GetRect( RECT const & rcWorkSpace, RECT& rcEmphasis ) +{ + rcEmphasis = rcWorkSpace; + + int ybottom = rcWorkSpace.bottom - 2 * m_nTickHeight - 2; + int workspaceheight = rcWorkSpace.bottom - rcWorkSpace.top; + + // Just past midpoint + rcEmphasis.top = rcWorkSpace.top + workspaceheight / 2 + 2; + // 60 units or + rcEmphasis.bottom = clamp( rcEmphasis.top + 60, rcEmphasis.top + 20, ybottom ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::OnModeChanged( void ) +{ + // Show/hide controls as necessary +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *parent - +//----------------------------------------------------------------------------- +void PhonemeEditor::Emphasis_Init( void ) +{ + m_nNumSelected = 0; +} + +CEmphasisSample *PhonemeEditor::Emphasis_GetSampleUnderMouse( mxEvent *event ) +{ + if ( GetMode() != MODE_EMPHASIS ) + return NULL; + + if ( !m_pWaveFile ) + return NULL; + + if ( w2() <= 0 ) + return NULL; + + if ( GetPixelsPerSecond() <= 0 ) + return NULL; + + float timeperpixel = 1.0f / GetPixelsPerSecond(); + float closest_dist = 999999.0f; + CEmphasisSample *bestsample = NULL; + + int samples = m_Tags.GetNumSamples(); + + float clickTime = GetTimeForPixel( (short)event->x ); + + for ( int i = 0; i < samples; i++ ) + { + CEmphasisSample *sample = m_Tags.GetSample( i ); + + float dist = fabs( sample->time - clickTime ); + if ( dist < closest_dist ) + { + bestsample = sample; + closest_dist = dist; + } + + } + + // Not close to any of them!!! + if ( closest_dist > ( 5.0f * timeperpixel ) ) + { + return NULL; + } + + return bestsample; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::Emphasis_DeselectAll( void ) +{ + if ( GetMode() != MODE_EMPHASIS ) + return; + + for ( int i = 0; i < m_Tags.GetNumSamples(); i++ ) + { + CEmphasisSample *sample = m_Tags.GetSample( i ); + sample->selected = false; + } + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::Emphasis_SelectAll( void ) +{ + if ( GetMode() != MODE_EMPHASIS ) + return; + + for ( int i = 0; i < m_Tags.GetNumSamples(); i++ ) + { + CEmphasisSample *sample = m_Tags.GetSample( i ); + sample->selected = true; + } + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::Emphasis_Delete( void ) +{ + if ( GetMode() != MODE_EMPHASIS ) + return; + + SetDirty( true ); + + PushUndo(); + + for ( int i = m_Tags.GetNumSamples() - 1; i >= 0 ; i-- ) + { + CEmphasisSample *sample = m_Tags.GetSample( i ); + if ( !sample->selected ) + continue; + + m_Tags.m_EmphasisSamples.Remove( i ); + + SetDirty( true ); + } + + PushRedo(); + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : sample - +//----------------------------------------------------------------------------- +void PhonemeEditor::Emphasis_AddSample( CEmphasisSample const& sample ) +{ + if ( GetMode() != MODE_EMPHASIS ) + return; + + SetDirty( true ); + + PushUndo(); + + m_Tags.m_EmphasisSamples.AddToTail( sample ); + m_Tags.Resort(); + + PushRedo(); + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::Emphasis_CountSelected( void ) +{ + m_nNumSelected = 0; + + for ( int i = 0; i < m_Tags.GetNumSamples(); i++ ) + { + CEmphasisSample *sample = m_Tags.GetSample( i ); + if ( !sample || !sample->selected ) + continue; + + m_nNumSelected++; + } +} + +void PhonemeEditor::Emphasis_ShowContextMenu( mxEvent *event ) +{ + if ( GetMode() != MODE_EMPHASIS ) + return; + + CountSelected(); + + // Construct main menu + mxPopupMenu *pop = new mxPopupMenu(); + + if ( m_nNumSelected > 0 ) + { + pop->add( va( "Delete" ), IDC_EMPHASIS_DELETE ); + pop->add( "Deselect all", IDC_EMPHASIS_DESELECT ); + } + pop->add( "Select all", IDC_EMPHASIS_SELECTALL ); + + pop->popup( this, (short)event->x, (short)event->y ); +} + +void PhonemeEditor::Emphasis_MouseDrag( int x, int y ) +{ + if ( m_nDragType != DRAGTYPE_EMPHASIS_MOVE ) + return; + + RECT rcWork; + GetWorkspaceRect( rcWork ); + + RECT rc; + Emphasis_GetRect( rcWork, rc ); + + int height = rc.bottom - rc.top; + + int dx = x - m_nLastX; + int dy = y - m_nLastY; + + float dfdx = (float)dx * GetTimePerPixel(); + float dfdy = (float)dy / (float)height; + + for ( int i = 0; i < m_Tags.GetNumSamples(); i++ ) + { + CEmphasisSample *sample = m_Tags.GetSample( i ); + if ( !sample || !sample->selected ) + continue; + + sample->time += dfdx; + //sample->time = clamp( sample->time, 0.0f, 1.0f ); + + sample->value -= dfdy; + sample->value = clamp( sample->value, 0.0f, 1.0f ); + } +} + +void PhonemeEditor::Emphasis_Redraw( CChoreoWidgetDrawHelper& drawHelper, RECT& rcWorkSpace ) +{ + if ( GetMode() != MODE_EMPHASIS && + GetMode() != MODE_PHONEMES ) + return; + + bool fullmode = GetMode() == MODE_EMPHASIS; + RECT rcClient; + + Emphasis_GetRect( rcWorkSpace, rcClient ); + + RECT rcText; + rcText = rcClient; + + InflateRect( &rcText, -15, 0 ); + + OffsetRect( &rcText, 0, -20 ); + rcText.bottom = rcText.top + 20; + + if ( fullmode ) + { + drawHelper.DrawColoredText( "Arial", 15, FW_BOLD, PEColor( COLOR_PHONEME_EMPHASIS_TEXT ), rcText, "Emphasis..." ); + } + + { + int h = rcClient.bottom - rcClient.top; + int offset = h/3; + RECT rcSpot = rcClient; + rcSpot.bottom = rcSpot.top + offset; + + drawHelper.DrawGradientFilledRect( + rcSpot, + PEColor( COLOR_PHONEME_EMPHASIS_BG_STRONG ), + PEColor( COLOR_PHONEME_EMPHASIS_BG ), + true ); + + OffsetRect( &rcSpot, 0, offset ); + + drawHelper.DrawFilledRect( PEColor( COLOR_PHONEME_EMPHASIS_BG ), rcSpot ); + + OffsetRect( &rcSpot, 0, offset ); + + drawHelper.DrawGradientFilledRect( + rcSpot, + PEColor( COLOR_PHONEME_EMPHASIS_BG ), + PEColor( COLOR_PHONEME_EMPHASIS_BG_WEAK ), + true ); + } + + COLORREF gray = PEColor( COLOR_PHONEME_EMPHASIS_MIDLINE ); + + drawHelper.DrawOutlinedRect( PEColor( COLOR_PHONEME_EMPHASIS_BORDER ), PS_SOLID, 1, rcClient ); + + COLORREF lineColor = PEColor( COLOR_PHONEME_EMPHASIS_LINECOLOR ); + COLORREF dotColor = PEColor( COLOR_PHONEME_EMPHASIS_DOTCOLOR ); + COLORREF dotColorSelected = PEColor( COLOR_PHONEME_EMPHASIS_DOTCOLOR_SELECTED ); + + int midy = ( rcClient.bottom + rcClient.top ) / 2; + + drawHelper.DrawColoredLine( gray, PS_SOLID, 1, rcClient.left, midy, + rcClient.right, midy ); + int height = rcClient.bottom - rcClient.top; + int bottom = rcClient.bottom - 1; + + if ( !m_pWaveFile ) + return; + + float running_length = m_pWaveFile->GetRunningLength(); + + // FIXME: adjust this based on framerate.... + float timeperpixel = GetTimePerPixel(); + + float starttime, endtime; + GetScreenStartAndEndTime( starttime, endtime ); + + int prevx = 0; + float prev_t = starttime; + float prev_value = m_Tags.GetIntensity( prev_t, running_length ); + + int dx = 5; + + for ( int x = 0; x < ( w2() + dx ); x += dx ) + { + float t = GetTimeForPixel( x ); + + float value = m_Tags.GetIntensity( t, running_length ); + + // Draw segment + drawHelper.DrawColoredLine( lineColor, PS_SOLID, 1, + prevx, + bottom - prev_value * height, + x, + bottom - value * height ); + + prev_t = t; + prev_value = value; + prevx = x; + + } + + int numsamples = m_Tags.GetNumSamples(); + + for ( int sample = 0; sample < numsamples; sample++ ) + { + CEmphasisSample *start = m_Tags.GetSample( sample ); + + int x = ( start->time - starttime ) / timeperpixel; + + float value = m_Tags.GetIntensity( start->time, running_length ); + int y = bottom - value * height; + + int dotsize = 4; + int dotSizeSelected = 5; + + COLORREF clr = dotColor; + COLORREF clrSelected = dotColorSelected; + + drawHelper.DrawCircle( + start->selected ? clrSelected : clr, + x, y, + start->selected ? dotSizeSelected : dotsize, + true ); + + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool PhonemeEditor::Emphasis_IsValid( void ) +{ + if ( m_Tags.GetNumSamples() > 0 ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PhonemeEditor::Emphasis_SelectPoints( void ) +{ + if ( GetMode() != MODE_EMPHASIS ) + return; + + RECT rcWork, rcEmphasis; + GetWorkspaceRect( rcWork ); + + Emphasis_GetRect( rcWork, rcEmphasis ); + + RECT rcSelection; + + rcSelection.left = m_nStartX < m_nLastX ? m_nStartX : m_nLastX; + rcSelection.right = m_nStartX < m_nLastX ? m_nLastX : m_nStartX; + + rcSelection.top = m_nStartY < m_nLastY ? m_nStartY : m_nLastY; + rcSelection.bottom = m_nStartY < m_nLastY ? m_nLastY : m_nStartY; + + rcSelection.top = max( rcSelection.top, rcEmphasis.top ); + rcSelection.bottom = min( rcSelection.bottom, rcEmphasis.bottom ); + + int eh, ew; + + eh = rcEmphasis.bottom - rcEmphasis.top; + ew = rcEmphasis.right - rcEmphasis.left; + + InflateRect( &rcSelection, 5, 5 ); + + if ( !w2() || !h2() ) + return; + + float fleft = GetTimeForPixel( rcSelection.left ); + float fright = GetTimeForPixel( rcSelection.right ); + + float ftop = (float)( rcSelection.top - rcEmphasis.top ) / (float)eh; + float fbottom = (float)( rcSelection.bottom- rcEmphasis.top ) / (float)eh; + + //fleft = clamp( fleft, 0.0f, 1.0f ); + //fright = clamp( fright, 0.0f, 1.0f ); + ftop = clamp( ftop, 0.0f, 1.0f ); + fbottom = clamp( fbottom, 0.0f, 1.0f ); + + float eps = 0.005; + + for ( int i = 0; i < m_Tags.GetNumSamples(); i++ ) + { + CEmphasisSample *sample = m_Tags.GetSample( i ); + + if ( sample->time + eps < fleft ) + continue; + + if ( sample->time - eps > fright ) + continue; + + if ( (1.0f - sample->value ) + eps < ftop ) + continue; + + if ( (1.0f - sample->value ) - eps > fbottom ) + continue; + + sample->selected = true; + } + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rcHandle - +//----------------------------------------------------------------------------- +void PhonemeEditor::GetScrubHandleRect( RECT& rcHandle, bool clipped ) +{ + float pixel = 0.0f; + + if ( m_pWaveFile ) + { + float currenttime = m_flScrub; + float starttime, endtime; + GetScreenStartAndEndTime( starttime, endtime ); + + float screenfrac = ( currenttime - starttime ) / ( endtime - starttime ); + + pixel = screenfrac * w2(); + + if ( clipped ) + { + pixel = clamp( pixel, SCRUBBER_HANDLE_WIDTH/2, w2() - SCRUBBER_HANDLE_WIDTH/2 ); + } + } + + rcHandle.left = pixel-SCRUBBER_HANDLE_WIDTH/2; + rcHandle.right = pixel + SCRUBBER_HANDLE_WIDTH/2; + rcHandle.top = 2 + GetCaptionHeight() + 12; + rcHandle.bottom = rcHandle.top + SCRUBBER_HANDLE_HEIGHT; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rcArea - +//----------------------------------------------------------------------------- +void PhonemeEditor::GetScrubAreaRect( RECT& rcArea ) +{ + rcArea.left = 0; + rcArea.right = w2(); + rcArea.top = 2 + GetCaptionHeight() + 12; + rcArea.bottom = rcArea.top + SCRUBBER_HEIGHT - 4; +} + +void PhonemeEditor::DrawScrubHandle() +{ + RECT rcHandle; + GetScrubHandleRect( rcHandle, true ); + rcHandle.left = 0; + rcHandle.right = w2(); + + CChoreoWidgetDrawHelper drawHelper( this, rcHandle ); + + DrawScrubHandle( drawHelper ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rcHandle - +//----------------------------------------------------------------------------- +void PhonemeEditor::DrawScrubHandle( CChoreoWidgetDrawHelper& drawHelper ) +{ + RECT rcHandle; + GetScrubHandleRect( rcHandle, true ); + + HBRUSH br = CreateSolidBrush( RGB( 0, 150, 100 ) ); + + COLORREF areaBorder = RGB( 230, 230, 220 ); + + drawHelper.DrawColoredLine( areaBorder, + PS_SOLID, 1, 0, rcHandle.top, w2(), rcHandle.top ); + drawHelper.DrawColoredLine( areaBorder, + PS_SOLID, 1, 0, rcHandle.bottom, w2(), rcHandle.bottom ); + + drawHelper.DrawFilledRect( br, rcHandle ); + + // + char sz[ 32 ]; + sprintf( sz, "%.3f", m_flScrub ); + + int len = drawHelper.CalcTextWidth( "Arial", 9, 500, sz ); + + RECT rcText = rcHandle; + int textw = rcText.right - rcText.left; + + rcText.left += ( textw - len ) / 2; + + drawHelper.DrawColoredText( "Arial", 9, 500, RGB( 255, 255, 255 ), rcText, sz ); + + DeleteObject( br ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool PhonemeEditor::IsMouseOverScrubHandle( mxEvent *event ) +{ + RECT rcHandle; + GetScrubHandleRect( rcHandle, true ); + InflateRect( &rcHandle, 2, 2 ); + + POINT pt; + pt.x = (short)event->x; + pt.y = (short)event->y; + if ( PtInRect( &rcHandle, pt ) ) + { + return true; + } + return false; +} + +bool PhonemeEditor::IsMouseOverScrubArea( mxEvent *event ) +{ + RECT rcArea; + + rcArea.left = 0; + rcArea.right = w2(); + rcArea.top = 2 + GetCaptionHeight() + 12; + rcArea.bottom = rcArea.top + 10; + + InflateRect( &rcArea, 2, 2 ); + + POINT pt; + pt.x = (short)event->x; + pt.y = (short)event->y; + if ( PtInRect( &rcArea, pt ) ) + { + return true; + } + + return false; +} + +float PhonemeEditor::GetTimeForSample( int sample ) +{ + if ( !m_pWaveFile ) + { + return 0.0f; + } + + float duration = m_pWaveFile->GetRunningLength(); + int sampleCount = m_pWaveFile->SampleCount(); + if ( sampleCount <= 0 ) + return 0.0f; + + float frac = (float)sample / (float)sampleCount; + + return frac * duration; +} + +void PhonemeEditor::ClampTimeToSelectionInterval( float& timeval ) +{ + if ( !m_pWaveFile ) + { + return; + } + if ( !m_pMixer || !sound->IsSoundPlaying( m_pMixer ) ) + { + return; + } + + if ( !m_bSelectionActive ) + return; + + float starttime = GetTimeForSample( m_nSelection[ 0 ] ); + float endtime = GetTimeForSample( m_nSelection[ 1 ] ); + + Assert( starttime <= endtime ); + + timeval = clamp( timeval, starttime, endtime ); +} + +void PhonemeEditor::ScrubThink( float dt, bool scrubbing ) +{ + ClampTimeToSelectionInterval( m_flScrub ); + ClampTimeToSelectionInterval( m_flScrubTarget ); + + if ( m_flScrubTarget == m_flScrub && !scrubbing ) + { + if ( sound->IsSoundPlaying( m_pMixer ) ) + { + sound->StopSound( m_pMixer ); + } + return; + } + + if ( !m_pWaveFile ) + return; + + bool newmixer = false; + if ( !m_pMixer || !sound->IsSoundPlaying( m_pMixer ) ) + { + m_pMixer = NULL; + SaveLinguisticData(); + + StudioModel *model = NULL;//models->GetActiveStudioModel(); + + sound->PlaySound( model, VOL_NORM, m_WorkFile.m_szWorkingFile, &m_pMixer ); + newmixer = true; + } + + if ( !m_pMixer ) + { + return; + } + + if ( m_flScrub > m_flScrubTarget ) + { + m_pMixer->SetDirection( false ); + } + else + { + m_pMixer->SetDirection( true ); + } + + float duration = m_pWaveFile->GetRunningLength(); + if ( !duration ) + return; + + float d = m_flScrubTarget - m_flScrub; + int sign = d > 0.0f ? 1 : -1; + + float maxmove = dt * m_flPlaybackRate; + + if ( sign > 0 ) + { + if ( d < maxmove ) + { + m_flScrub = m_flScrubTarget; + } + else + { + m_flScrub += maxmove; + } + } + else + { + if ( -d < maxmove ) + { + m_flScrub = m_flScrubTarget; + } + else + { + m_flScrub -= maxmove; + } + } + + int sampleCount = m_pMixer->GetSource()->SampleCount(); + + int cursample = sampleCount * ( m_flScrub / duration ); + + int realsample = m_pMixer->GetSamplePosition(); + + int dsample = cursample - realsample; + + int onehundredth = m_pMixer->GetSource()->SampleRate() * 0.01f; + + if ( abs( dsample ) > onehundredth ) + { + m_pMixer->SetSamplePosition( cursample, true ); + } + m_pMixer->SetActive( true ); + + RECT rcArea; + GetScrubAreaRect( rcArea ); + + CChoreoWidgetDrawHelper drawHelper( this, rcArea ); + DrawScrubHandle( drawHelper ); + + if ( scrubbing ) + { + g_pMatSysWindow->Frame(); + } +} + +void PhonemeEditor::SetScrubTime( float t ) +{ + m_flScrub = t; + ClampTimeToSelectionInterval( m_flScrub ); +} + +void PhonemeEditor::SetScrubTargetTime( float t ) +{ + m_flScrubTarget = t; + ClampTimeToSelectionInterval( m_flScrubTarget ); +} + + +void PhonemeEditor::OnToggleVoiceDuck() +{ + SetDirty( true ); + PushUndo(); + m_Tags.SetVoiceDuck( !m_Tags.GetVoiceDuck() ); + PushRedo(); + redraw(); +} + +void PhonemeEditor::Play() +{ + PlayEditedWave( m_bSelectionActive ); +} + + + + + diff --git a/utils/hlfaceposer/phonemeeditor.h b/utils/hlfaceposer/phonemeeditor.h new file mode 100644 index 0000000..98972c7 --- /dev/null +++ b/utils/hlfaceposer/phonemeeditor.h @@ -0,0 +1,591 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef PHONEEDITOR_H +#define PHONEEDITOR_H +#ifdef _WIN32 +#pragma once +#endif + +#include <mxtk/mx.h> +class CAudioSource; +class CAudioMixer; +class mxBitmapButton; +class mxButton; + +#include "utlvector.h" +#include "faceposertoolwindow.h" + +#define IDC_PHONEME_SCROLL 1001 +#define IDC_PHONEME_PLAY_ORIG 1002 +#define IDC_EDIT_PHONEME 1004 +#define IDC_EDIT_INSERTPHONEMEBEFORE 1005 +#define IDC_EDIT_INSERTPHONEMEAFTER 1006 +#define IDC_EDIT_DELETEPHONEME 1007 + +#define IDC_PLAY_EDITED_SELECTION 1008 + +#define IDC_REDO_PHONEMEEXTRACTION 1009 +#define IDC_REDO_PHONEMEEXTRACTION_SELECTION 1010 +#define IDC_DESELECT 1011 +#define IDC_PLAY_EDITED 1012 +#define IDC_SAVE_LINGUISTIC 1013 +#define IDC_CANCELPLAYBACK 1014 + +#define IDC_EDITWORDLIST 1015 +#define IDC_SNAPWORDS 1016 +#define IDC_SEPARATEWORDS 1017 +#define IDC_LOADWAVEFILE 1018 +#define IDC_SNAPPHONEMES 1019 +#define IDC_SEPARATEPHONEMES 1020 + +#define IDC_COMMITEXTRACTED 1021 +#define IDC_CLEAREXTRACTED 1022 + +#define IDC_ADDTAG 1023 +#define IDC_DELETETAG 1024 + +#define IDC_CVUNDO 1025 +#define IDC_CVREDO 1026 + +#define IDC_EDIT_DELETEWORD 1027 +#define IDC_EDIT_WORD 1028 +#define IDC_EDIT_INSERTWORDBEFORE 1029 +#define IDC_EDIT_INSERTWORDAFTER 1030 +#define IDC_EDIT_INSERTFIRSTPHONEMEOFWORD 1031 + +#define IDC_SELECT_WORDSRIGHT 1032 +#define IDC_SELECT_WORDSLEFT 1033 +#define IDC_SELECT_PHONEMESRIGHT 1034 +#define IDC_SELECT_PHONEMESLEFT 1035 + +#define IDC_DESELECT_PHONEMESANDWORDS 1036 +#define IDC_CLEANUP 1037 +#define IDC_CLEARUNDO 1038 + +#define IDC_PLAYBUTTON 1039 + +#define IDC_MODE_TAB 1040 + +#define IDC_EMPHASIS_DELETE 1041 +#define IDC_EMPHASIS_DESELECT 1042 +#define IDC_EMPHASIS_SELECTALL 1043 + +#define IDC_PLAYBACKRATE 1044 + +#define IDC_REALIGNPHONEMES 1045 +#define IDC_REALIGNWORDS 1046 + +// Support for multiple speech api's +#define IDC_API_SAPI 1050 +#define IDC_API_LIPSINC 1051 + +#define IDC_EXPORT_SENTENCE 1075 +#define IDC_IMPORT_SENTENCE 1076 +#define IDC_TOGGLE_VOICEDUCK 1077 + +#define IDC_PE_LANGUAGESTART 1100 +// #define IDC_PE_LANGUAGEEND 1106 or so + +class IterateOutputRIFF; +class IterateRIFF; +class CChoreoWidgetDrawHelper; +class CChoreoEvent; +class CEventRelativeTag; +class CChoreoView; +class IPhonemeExtractor; +class CPhonemeModeTab; +class mxPopupMenu; + +#include "sentence.h" + +enum +{ + COLOR_PHONEME_BACKGROUND = 0, + COLOR_PHONEME_TEXT, + COLOR_PHONEME_LIGHTTEXT, + COLOR_PHONEME_PLAYBACKTICK, + COLOR_PHONEME_WAVDATA, + COLOR_PHONEME_TIMELINE, + COLOR_PHONEME_TIMELINE_MAJORTICK, + COLOR_PHONEME_TIMELINE_MINORTICK, + COLOR_PHONEME_EXTRACTION_RESULT_FAIL, + COLOR_PHONEME_EXTRACTION_RESULT_SUCCESS, + COLOR_PHONEME_EXTRACTION_RESULT_ERROR, + COLOR_PHONEME_EXTRACTION_RESULT_OTHER, + COLOR_PHONEME_TAG_BORDER, + COLOR_PHONEME_TAG_BORDER_SELECTED, + COLOR_PHONEME_TAG_FILLER_NORMAL, + COLOR_PHONEME_TAG_SELECTED, + COLOR_PHONEME_TAG_TEXT, + COLOR_PHONEME_TAG_TEXT_SELECTED, + COLOR_PHONEME_WAV_ENDPOINT, + COLOR_PHONEME_AB, + COLOR_PHONEME_AB_LINE, + COLOR_PHONEME_AB_TEXT, + + COLOR_PHONEME_ACTIVE_BORDER, + COLOR_PHONEME_SELECTED_BORDER, + COLOR_PHONEME_TIMING_TAG, + + COLOR_PHONEME_EMPHASIS_BG, + COLOR_PHONEME_EMPHASIS_BG_STRONG, + COLOR_PHONEME_EMPHASIS_BG_WEAK, + + COLOR_PHONEME_EMPHASIS_BORDER, + COLOR_PHONEME_EMPHASIS_LINECOLOR, + COLOR_PHONEME_EMPHASIS_DOTCOLOR, + COLOR_PHONEME_EMPHASIS_DOTCOLOR_SELECTED, + COLOR_PHONEME_EMPHASIS_TEXT, + COLOR_PHONEME_EMPHASIS_MIDLINE, + + NUM_COLORS, +}; + +//----------------------------------------------------------------------------- +// Purpose: Shows WAV data and allows blanking it out and tweaking phoneme tags +//----------------------------------------------------------------------------- +class PhonemeEditor : public mxWindow, public IFacePoserToolWindow +{ +public: + enum + { + BOUNDARY_NONE = 0, + BOUNDARY_PHONEME, + BOUNDARY_WORD, + }; + + typedef enum + { + MODE_PHONEMES = 0, + MODE_EMPHASIS + } EditorMode; + + // Construction + PhonemeEditor( mxWindow *parent ); + ~PhonemeEditor( void ); + + virtual void Think( float dt ); + + virtual void OnDelete(); + virtual bool CanClose(); + + void ValidateSpeechAPIIndex(); + + virtual int handleEvent( mxEvent *event ); + virtual void redraw( void ); + virtual bool PaintBackground( void ); + + EditorMode GetMode( void ) const; + void SetupPhonemeEditorColors( void ); + COLORREF PEColor( int colornum ); + void OnModeChanged( void ); + + // Change wave file being edited + void SetCurrentWaveFile( const char *wavefile, bool force = false, CChoreoEvent *event = NULL ); + + // called when scene is unloaded in choreview or when event/channel/actor gets deleted + // so we don't have dangling pointers to tags, events, scene + void ClearEvent( void ); + + void Play(); + +private: + void DrawWords( CChoreoWidgetDrawHelper& drawHelper, RECT& rcWorkSpace, CSentence &sentence, int type, bool showactive = true ); + void DrawPhonemes( CChoreoWidgetDrawHelper& drawHelper, RECT& rcWorkSpace, CSentence &sentence, int type, bool showactive = true ); + void DrawRelativeTags( CChoreoWidgetDrawHelper& drawHelper, RECT& rc ); + + void Emphasis_Redraw( CChoreoWidgetDrawHelper& drawHelper, RECT& rcWorkSpace ); + void Emphasis_GetRect( RECT const & rcWorkSpace, RECT& rcEmphasis ); + + void Emphasis_Init( void ); + CEmphasisSample *Emphasis_GetSampleUnderMouse( mxEvent *event ); + void Emphasis_DeselectAll( void ); + void Emphasis_SelectAll( void ); + void Emphasis_Delete( void ); + void Emphasis_AddSample( CEmphasisSample const& sample ); + void Emphasis_CountSelected( void ); + void Emphasis_ShowContextMenu( mxEvent *event ); + void Emphasis_MouseDrag( int x, int y ); + bool Emphasis_IsValid( void ); + void Emphasis_SelectPoints( void ); + + // Data + int m_nNumSelected; + + // Readjust slider + void MoveTimeSliderToPos( int x ); + + // Handle scrollbar + void SetTimeZoomScale( int scale ); + float GetTimeZoomScale( void ); + float GetPixelsPerSecond( void ); + // Adjust scroll bars + void RepositionHSlider( void ); + + // Edit commands + void EditPhoneme( CPhonemeTag *pPhoneme, bool positionDialog = false ); + void EditPhoneme( void ); + void EditInsertPhonemeBefore( void ); + void EditInsertPhonemeAfter( void ); + void EditDeletePhoneme( void ); + + void SelectPhonemes( bool forward ); + + void EditInsertFirstPhonemeOfWord( void ); + + void EditWord( CWordTag *pWord, bool positionDialog = false ); + void EditWord( void ); + void EditInsertWordBefore( void ); + void EditInsertWordAfter( void ); + void EditDeleteWord( void ); + + void SelectWords( bool forward ); + + // Edit word list + void EditWordList( void ); + void SentenceFromString( CSentence& sentence, char const *str ); + + + // Wav processing commands + void RedoPhonemeExtraction( void ); + // Redo extraction of selected words only + void RedoPhonemeExtractionSelected( void ); + void Deselect( void ); + + void PlayEditedWave( bool selection = false ); + void CommitChanges( void ); + + // Context menu + void ShowPhonemeMenu( CPhonemeTag *pho, int mx, int my ); + void ShowWordMenu( CWordTag *word, int mx, int my ); + + void ShowContextMenu( int mx, int my ); + void ShowContextMenu_Phonemes( int mx, int my ); + void ShowContextMenu_Emphasis( int mx, int my ); + + // UI helpers + void GetWorkspaceRect( RECT &rc ); + + + bool IsMouseOverWordRow( int my ); + bool IsMouseOverPhonemeRow( int my ); + + int IsMouseOverBoundary( mxEvent *event ); + int GetWordUnderMouse( int mx, int my ); + int ComputeHPixelsNeeded( void ); + void DrawFocusRect( char *reason ); + void StartDragging( int dragtype, int startx, int starty, HCURSOR cursor ); + + void FinishPhonemeMove( int startx, int endx ); + void FinishPhonemeDrag( int startx, int endx ); + void FinishWordMove( int startx, int endx ); + void FinishWordDrag( int startx, int endx ); + + float GetTimeForPixel( int mx ); + void GetScreenStartAndEndTime( float &starttime, float& endtime ); + float GetTimePerPixel( void ); + int GetSampleForMouse( int mx ); + int GetPixelForSample( int sample ); + + bool FindSpanningPhonemes( float time, CPhonemeTag **pp1, CPhonemeTag **pp2 ); + bool FindSpanningWords( float time, CWordTag **pp1, CWordTag **pp2 ); + int FindWordForTime( float time ); + CPhonemeTag *FindPhonemeForTime( float time ); + void DeselectWords( void ); + void SnapWords( void ); + void SeparateWords( void ); + + void DeselectPhonemes( void ); + void SnapPhonemes( void ); + void SeparatePhonemes( void ); + + void CreateEvenWordDistribution( const char *wordlist ); + + // Dirty flag + void SetDirty( bool dirty, bool clearundo = true ); + bool GetDirty( void ); + + // FIXME: Do something else here + void ResampleChunk( IterateOutputRIFF& store, void *format, int chunkname, char *buffer, int buffersize, int start_silence = 0, int end_silence = 0 ); + + // Mouse control over selected samples + void SelectSamples( int start, int end ); + void FinishSelect( int startx, int mx ); + void FinishMoveSelection( int startx, int mx ); + void FinishMoveSelectionStart( int startx, int mx ); + void FinishMoveSelectionEnd( int startx, int mx ); + + bool IsMouseOverSamples( int mx, int my ); + bool IsMouseOverSelection( int mx, int my ); + bool IsMouseOverSelectionStartEdge( mxEvent *event ); + bool IsMouseOverSelectionEndEdge( mxEvent *event ); + + bool IsMouseOverTag( int mx, int my ); + void FinishEventTagDrag( int startx, int endx ); + CEventRelativeTag *GetTagUnderMouse( int mx ); + bool IsMouseOverTagRow( int my ); + void ShowTagMenu( int mx, int my ); + void AddTag( void ); + void DeleteTag( void ); + + // After running liset/sapi, retrieve phoneme tag data from stream + void RetrieveLinguisticData( void ); + + // Copy current phoneme chunk over existing data chunk of .wav file + void SaveLinguisticData( void ); + void StoreValveDataChunk( IterateOutputRIFF& store ); + + void ExportValveDataChunk( char const *tempfile ); + void ImportValveDataChunk( char const *tempfile ); + + void OnImport(); + void OnExport(); + + // Playback (returns true if sound had been playing) + bool StopPlayback( void ); + + CPhonemeTag *GetPhonemeTagUnderMouse( int mx, int my ); + CWordTag *GetWordTagUnderMouse( int mx, int my ); + + void ReadLinguisticTags( void ); + + void LoadWaveFile( void ); + + void GetPhonemeTrayTopBottom( RECT& rc ); + void GetWordTrayTopBottom( RECT& rc ); + + void GetWordRect( const CWordTag *tag, RECT& rc ); + void GetPhonemeRect( const CPhonemeTag *tag, RECT& rc ); + int GetMouseForTime( float time ); + + void CommitExtracted( void ); + void ClearExtracted( void ); + + const char * GetExtractionResultString( int resultCode ); + + void AddFocusRect( RECT& rc ); + + void CountSelected( void ); + + typedef void (PhonemeEditor::*PEWORDITERFUNC)( CWordTag *word, float fparam ); + typedef void (PhonemeEditor::*PEPHONEMEITERFUNC)( CPhonemeTag *phoneme, CWordTag *word, float fparam ); + + void TraverseWords( PEWORDITERFUNC pfn, float fparam ); + void TraversePhonemes( PEPHONEMEITERFUNC pfn, float fparam ); + + // Iteration functions + void ITER_MoveSelectedWords( CWordTag *word, float amount ); + void ITER_MoveSelectedPhonemes( CPhonemeTag *phoneme, CWordTag *word, float amount ); + + void ITER_ExtendSelectedPhonemeEndTimes( CPhonemeTag *phoneme, CWordTag *word, float amount ); + void ITER_ExtendSelectedWordEndTimes( CWordTag *word, float amount ); + + void ITER_AddFocusRectSelectedWords( CWordTag *word, float amount ); + void ITER_AddFocusRectSelectedPhonemes( CPhonemeTag *phoneme, CWordTag *word, float amount ); + + void ITER_CountSelectedWords( CWordTag *word, float amount ); + void ITER_CountSelectedPhonemes( CPhonemeTag *phoneme, CWordTag *word, float amount ); + + void ITER_SelectSpanningWords( CWordTag *word, float amount ); + +// Undo/Redo + void Undo( void ); + void Redo( void ); + void ClearUndo( void ); + + // Do push before changes + void PushUndo( void ); + // Do this push after changes, must match pushundo 1for1 + void PushRedo( void ); + + void WipeUndo( void ); + void WipeRedo( void ); + + CPhonemeTag *GetClickedPhoneme( void ); + CWordTag *GetClickedWord( void ); + void SetClickedPhoneme( int word, int phoneme ); + + void ShiftSelectedPhoneme( int direction ); + void ExtendSelectedPhonemeEndTime( int direction ); + void SelectNextPhoneme( int direction ); + void SelectNextWord( int direction ); + bool IsPhonemeSelected( CWordTag *word ); + void ShiftSelectedWord( int direction ); + void ExtendSelectedWordEndTime( int direction ); + + float GetTimeGapToNextWord( bool forward, CWordTag *currentWord, CWordTag **ppNextWord = NULL ); + float GetTimeGapToNextPhoneme( bool forward, CPhonemeTag *currentPhoneme, CWordTag **ppword = NULL, CPhonemeTag **phoneme = NULL ); + int IndexOfWord( CWordTag *word ); + CPhonemeTag *GetSelectedPhoneme( void ); + CWordTag *GetSelectedWord( void ); + + void OnMouseMove( mxEvent *event ); + + bool AreSelectedWordsContiguous( void ); + bool AreSelectedPhonemesContiguous( void ); + + bool CreateCroppedWave( char const *filename, int startsample, int endsample ); + void CleanupWordsAndPhonemes( bool prepareundo ); + void RealignPhonemesToWords( bool prepareundo ); + void RealignWordsToPhonemes( bool prepareundo ); + void SortWords( bool prepareundo ); + void SortPhonemes( bool prepareundo ); + + float ComputeMaxWordShift( bool forward, bool allowcrop ); + float ComputeMaxPhonemeShift( bool forward, bool allowcrop ); + + int PixelsForDeltaTime( float dt ); + + void ClearDragLimit( void ); + void SetDragLimit( int dragtype ); + void LimitDrag( int& mousex ); + + void SelectExpression( CPhonemeTag *tag ); + + void OnSAPI( void ); + void OnLipSinc( void ); + + bool CheckSpeechAPI( void ); + char const *GetSpeechAPIName( void ); + + void LoadPhonemeConverters(); + void UnloadPhonemeConverters(); + + bool IsMouseOverScrubHandle( mxEvent *event ); + bool IsMouseOverScrubArea( mxEvent *event ); + void GetScrubHandleRect( RECT& rcHandle, bool clipped = false ); + void GetScrubAreaRect( RECT& rcArea ); + void DrawScrubHandle(); + + void DrawScrubHandle( CChoreoWidgetDrawHelper& drawHelper ); + void ScrubThink( float dt, bool scrubbing ); + + void SetScrubTime( float t ); + void SetScrubTargetTime( float t ); + + float GetTimeForSample( int sample ); + void ClampTimeToSelectionInterval( float& timeval ); + void OnToggleVoiceDuck(); + + // Data +private: + // Type of mouse movement + enum + { + DRAGTYPE_NONE = 0, + DRAGTYPE_PHONEME , + DRAGTYPE_WORD, + DRAGTYPE_SELECTSAMPLES, + DRAGTYPE_MOVESELECTION, + DRAGTYPE_MOVESELECTIONSTART, + DRAGTYPE_MOVESELECTIONEND, + DRAGTYPE_MOVEWORD, + DRAGTYPE_MOVEPHONEME, + DRAGTYPE_EVENTTAG_MOVE, + DRAGTYPE_EMPHASIS_SELECT, + DRAGTYPE_EMPHASIS_MOVE, + DRAGTYPE_SCRUBBER + }; + + float m_flScrub; + float m_flScrubTarget; + + EditorMode m_CurrentMode; + // Graph scale + float m_flPixelsPerSecond; + // Graph scale + int m_nTimeZoom; + int m_nTimeZoomStep; + + int m_nTickHeight; + + // Current wave file + CAudioSource *m_pWaveFile; + CAudioMixer *m_pMixer; + CChoreoEvent *m_pEvent; + int m_nClickX; + + struct CWorkFile + { + public: + char m_szWaveFile[ 256 ]; + char m_szWorkingFile[ 256 ]; + char m_szBasePath[ 256 ]; + bool m_bDirty; + }; + CWorkFile m_WorkFile; + + mxScrollbar *m_pHorzScrollBar; + // Current sb value + int m_nLeftOffset; + + CPhonemeModeTab *m_pModeTab; + + mxSlider *m_pPlaybackRate; + float m_flPlaybackRate; + + mxButton *m_btnRedoPhonemeExtraction; + mxButton *m_btnSave; + mxButton *m_btnLoad; + + mxButton *m_btnPlay; // selection or full depending + + // Mouse dragging + HCURSOR m_hPrevCursor; + + int m_nStartX; + int m_nStartY; + int m_nLastX; + int m_nLastY; + int m_nDragType; + struct CFocusRect + { + RECT m_rcOrig; + RECT m_rcFocus; + }; + CUtlVector < CFocusRect > m_FocusRects; + + int m_nClickedPhoneme; + int m_nClickedWord; + + // Current set of tags + CSentence m_Tags; + + CSentence m_TagsExt; + + int m_nSelection[ 2 ]; + bool m_bSelectionActive; + + int m_nLastExtractionResult; + + int m_nSelectedPhonemeCount; + int m_nSelectedWordCount; + + bool m_bWordsActive; + + struct PEUndo + { + CSentence *undo; + CSentence *redo; + }; + + CUtlVector< PEUndo * > m_UndoStack; + int m_nUndoLevel; + bool m_bRedoPending; + + bool m_bLimitDrag; + int m_nLeftLimit; + int m_nRightLimit; + + IPhonemeExtractor *m_pPhonemeExtractor; + float m_flScrubberTimeOffset; +}; + +extern PhonemeEditor *g_pPhonemeEditor; + +#endif // PHONEEDITOR_H diff --git a/utils/hlfaceposer/phonemeeditorcolors.h b/utils/hlfaceposer/phonemeeditorcolors.h new file mode 100644 index 0000000..cb68c06 --- /dev/null +++ b/utils/hlfaceposer/phonemeeditorcolors.h @@ -0,0 +1,14 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef PHONEMEEDITORCOLORS_H +#define PHONEMEEDITORCOLORS_H +#ifdef _WIN32 +#pragma once +#endif + +#endif // PHONEMEEDITORCOLORS_H diff --git a/utils/hlfaceposer/phonemeproperties.cpp b/utils/hlfaceposer/phonemeproperties.cpp new file mode 100644 index 0000000..c1e2da2 --- /dev/null +++ b/utils/hlfaceposer/phonemeproperties.cpp @@ -0,0 +1,434 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "hlfaceposer.h" +#include <mxtk/mx.h> +#include "resource.h" +#include "PhonemeProperties.h" +#include "expressions.h" +#include "expclass.h" +#include "mdlviewer.h" + +static CPhonemeParams g_Params; + +static int g_nPhonemeCount = 0; +static HWND *g_rgButtons = NULL; + +#define IDC_PHONEME 2000 + +#define PHONEME_WIDTH 50 +#define PHONEME_HEIGHT 18 +#define PHONEME_GAP 10 +#define PHONEME_VGAP 5 + +typedef long (__stdcall *WINPROCTYPE)( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); +static WINPROCTYPE lpfnOldButtonProc; + +static BOOL CALLBACK PhonemeBtnProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + switch ( uMsg ) + { + case WM_MOUSEMOVE: + { + HWND dialog = GetParent( hwnd ); + if ( dialog ) + { + // Get the hint text item + HWND helpText = GetDlgItem( dialog, IDC_STATIC_HELPTEXT ); + if ( helpText ) + { + CExpression *exp = ( CExpression * )GetWindowLong( (HWND)hwnd, GWL_USERDATA ); + if ( exp ) + { + SendMessage( helpText, WM_SETTEXT, 0, (LPARAM)exp->description ); + } + } + } + } + return 0; + default: + break; + } + + return CallWindowProc( lpfnOldButtonProc, hwnd, uMsg, wParam, lParam ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : phoneme - +// Output : static void +//----------------------------------------------------------------------------- +static void ClickedPhoneme( HWND hwndDlg, int phoneme ) +{ + HWND ctrl = GetDlgItem( hwndDlg, IDC_EDIT_PHONEME ); + if ( !ctrl ) + return; + + if ( !g_Params.m_bMultiplePhoneme ) + { + g_Params.m_szName[ 0 ] = 0; + } + else + { + SendMessage( ctrl, WM_GETTEXT, (WPARAM)sizeof( g_Params.m_szName ), (LPARAM)g_Params.m_szName ); + } + if ( phoneme >= g_nPhonemeCount || phoneme < 0 ) + { + Assert( 0 ); + return; + } + + HWND button = g_rgButtons[ phoneme ]; + CExpression *exp = ( CExpression * )GetWindowLong( (HWND)button, GWL_USERDATA ); + if ( exp ) + { + if ( strlen( g_Params.m_szName ) > 0 ) + { + strcat( g_Params.m_szName, " " ); + } + strcat( g_Params.m_szName, exp->name ); + + if ( g_Params.m_bMultiplePhoneme ) + { + SetFocus( ctrl ); + SendMessage( ctrl, WM_SETTEXT, 0, (LPARAM)g_Params.m_szName ); + SendMessage( ctrl, EM_SETSEL, 0, MAKELONG(0, 0xffff) ); + } + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// Output : static void +//----------------------------------------------------------------------------- +static void CreateAndLayoutControls( HWND hwndDlg, CPhonemeParams* params ) +{ + g_nPhonemeCount = 0; + // Find phomemes + // Make sure phonemes are loaded + FacePoser_EnsurePhonemesLoaded(); + + CExpClass *cl = expressions->FindClass( "phonemes", true ); + if ( !cl ) + return; + + g_nPhonemeCount = cl->GetNumExpressions(); + if ( g_nPhonemeCount == 0 ) + return; + + g_rgButtons = new HWND[ g_nPhonemeCount ]; + Assert( g_rgButtons ); + + int columns = 7; + int rows = ( g_nPhonemeCount / columns ) + 1; + + int dialogW = columns * ( PHONEME_WIDTH + PHONEME_GAP ) + 2 * PHONEME_GAP; + int dialogH = rows * ( PHONEME_HEIGHT + PHONEME_VGAP ) + 40 + 55 + 30; + + int startx = PHONEME_GAP; + int starty = 40; + + if ( params->m_bPositionDialog ) + { + int top = params->m_nTop - dialogH - 5; + int left = params->m_nLeft; + + MoveWindow( hwndDlg, + left, + top, + dialogW, + dialogH, + TRUE ); + } + else + { + MoveWindow( hwndDlg, + ( GetSystemMetrics( SM_CXFULLSCREEN ) - dialogW ) / 2, + ( GetSystemMetrics( SM_CYFULLSCREEN ) - dialogH ) / 2, + dialogW, + dialogH, + TRUE ); + } + + HWND ctrl = GetDlgItem( hwndDlg, IDOK ); + if ( ctrl ) + { + MoveWindow( ctrl, dialogW - 220, dialogH - 58, 100, 20, TRUE ); + } + ctrl = GetDlgItem( hwndDlg, IDCANCEL ); + if ( ctrl ) + { + MoveWindow( ctrl, dialogW - 110, dialogH - 58, 100, 20, TRUE ); + } + ctrl = GetDlgItem( hwndDlg, IDC_PHONEMETEXTPROMPT ); + if ( ctrl ) + { + MoveWindow( ctrl, startx, dialogH - 55, 50, 20, TRUE ); + } + ctrl = GetDlgItem( hwndDlg, IDC_EDIT_PHONEME ); + if ( ctrl ) + { + MoveWindow( ctrl, startx + 50, dialogH - 58, 100, 20, TRUE ); + } + ctrl = GetDlgItem( hwndDlg, IDC_STATIC_HELPTEXT ); + if ( ctrl ) + { + MoveWindow( ctrl, startx, dialogH - 85, dialogW - startx - 20, 20, TRUE ); + } + + int r = 0; + int c = 0; + for ( int i = 0; i < g_nPhonemeCount; i++ ) + { + CExpression *exp = cl->GetExpression( i ); + if ( !exp ) + continue; + + HWND button = CreateWindowEx( + 0, + "BUTTON", + va( "%s", exp->name ), + WS_CHILD | WS_VISIBLE | BS_LEFT, + startx + c * ( PHONEME_WIDTH + PHONEME_GAP ), + starty + r * ( PHONEME_HEIGHT + PHONEME_VGAP ), + PHONEME_WIDTH, + PHONEME_HEIGHT, + hwndDlg, + (HMENU)( IDC_PHONEME + i ), + (HINSTANCE)GetModuleHandle( 0 ), + NULL ); + Assert( button ); + SetWindowLong( (HWND)button, GWL_USERDATA, (LONG)exp ); + + // Subclass it + lpfnOldButtonProc = (WINPROCTYPE)SetWindowLong( (HWND)button, GWL_WNDPROC, (LONG)PhonemeBtnProc ); + + SendMessage ((HWND)button, WM_SETFONT, (WPARAM) (HFONT) GetStockObject (ANSI_VAR_FONT), MAKELPARAM (TRUE, 0)); + + g_rgButtons[ i ] = button; + + c++; + if ( c >= columns ) + { + r++; + c = 0; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// Output : static void +//----------------------------------------------------------------------------- +static void DestroyControls( HWND hwndDlg ) +{ + for ( int i = 0 ; i < g_nPhonemeCount; i++ ) + { + if ( g_rgButtons[ i ] ) + { + DestroyWindow( g_rgButtons[ i ] ); + g_rgButtons[ i ] = NULL; + } + } + + delete[] g_rgButtons; + g_nPhonemeCount = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// Output : static void +//----------------------------------------------------------------------------- +static void PhonemePropertiesDialogExit( HWND hwndDlg, int exitCode ) +{ + DestroyControls( hwndDlg ); + + EndDialog( hwndDlg, exitCode ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : allowmultiple - +// *input - +// *output - +// Output : static bool +//----------------------------------------------------------------------------- +static bool ValidatePhonemeString( bool allowmultiple, char const *input, char *output ) +{ + // Make sure phonemes are loaded + FacePoser_EnsurePhonemesLoaded(); + + CExpClass *cl = expressions->FindClass( "phonemes", true ); + if ( !cl ) + return false; + + if ( !input || !input[ 0 ] ) + return false; + + // Go one by one + int count = 1; + char phoneme[ 128 ]; + char *in, *out; + + *output = 0; + + in = (char *)input; + do + { + out = phoneme; + + while ( *in > 32 ) + { + *out++ = *in++; + } + *out = 0; + + // Validate phoneme entered + for ( int i = 0; i < g_nPhonemeCount; i++ ) + { + CExpression *exp = cl->GetExpression( i ); + if ( !exp ) + continue; + + if ( !stricmp( exp->name, phoneme ) ) + { + // Found it + if ( count != 1 ) + { + strcat( output, " " ); + } + strcat( output, phoneme ); + break; + } + } + + if ( !*in ) + break; + + // Skip whitespace + in++; + count++; + + // Only keep first one + if ( !allowmultiple ) + break; + + } while ( 1 ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK PhonemePropertiesDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + switch(uMsg) + { + case WM_INITDIALOG: + // Insert code here to put the string (to find and replace with) + // into the edit controls. + // ... + { + g_Params.PositionSelf( hwndDlg ); + + SetWindowText( hwndDlg, g_Params.m_szDialogTitle ); + + CreateAndLayoutControls( hwndDlg, &g_Params ); + + HWND control = GetDlgItem( hwndDlg, IDC_PHONEMENAME ); + if ( control ) + { + SendMessage( control, WM_SETTEXT , 0, + ( LPARAM )( + g_Params.m_bMultiplePhoneme ? + "Click or enter one or more phonemes from list below" + : + va( "Phoneme/Viseme: %s", g_Params.m_szName ) ) ); + } + + control = GetDlgItem( hwndDlg, IDC_EDIT_PHONEME ); + if ( control ) + { + SetFocus( control ); + SendMessage( control, WM_SETTEXT , 0, ( LPARAM )g_Params.m_szName ); + SendMessage( control, EM_SETSEL, 0, MAKELONG(0, 0xffff) ); + return FALSE; + } + } + return TRUE; + + case WM_COMMAND: + { + int cmd = LOWORD( wParam ); + + if ( ( cmd >= IDC_PHONEME ) && + ( cmd < ( IDC_PHONEME + g_nPhonemeCount ) ) ) + { + ClickedPhoneme( hwndDlg, cmd - IDC_PHONEME ); + if ( !g_Params.m_bMultiplePhoneme ) + { + PhonemePropertiesDialogExit( hwndDlg, 1 ); + } + } + else if ( cmd != IDC_EDIT_PHONEME ) + { + switch ( cmd ) + { + case IDOK: + { + // Retrieve text + char szPhoneme[ 256 ]; + HWND ctrl = GetDlgItem( hwndDlg, IDC_EDIT_PHONEME ); + if ( ctrl ) + { + SendMessage( ctrl, WM_GETTEXT, (WPARAM)sizeof( szPhoneme ), (LPARAM)szPhoneme ); + + ValidatePhonemeString( g_Params.m_bMultiplePhoneme, szPhoneme, g_Params.m_szName ); + } + PhonemePropertiesDialogExit( hwndDlg, 1 ); + } + break; + case IDCANCEL: + PhonemePropertiesDialogExit( hwndDlg, 0 ); + break; + } + } + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int PhonemeProperties( CPhonemeParams *params ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_PHONEMEPROPERTIES ), + (HWND)g_MDLViewer->getHandle(), + (DLGPROC)PhonemePropertiesDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/phonemeproperties.h b/utils/hlfaceposer/phonemeproperties.h new file mode 100644 index 0000000..401343d --- /dev/null +++ b/utils/hlfaceposer/phonemeproperties.h @@ -0,0 +1,31 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef PHONEMEPROPERTIES_H +#define PHONEMEPROPERTIES_H +#ifdef _WIN32 +#pragma once +#endif + +#include "basedialogparams.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +struct CPhonemeParams : public CBaseDialogParams +{ + // i/o phoneme name + char m_szName[ 256 ]; + + // Can enter multiple phonemes, and clicking buttons just appends phonemes to string + bool m_bMultiplePhoneme; +}; + +// Display/create actor info +int PhonemeProperties( CPhonemeParams *params ); + +#endif // PHONEMEPROPERTIES_H diff --git a/utils/hlfaceposer/project.ico b/utils/hlfaceposer/project.ico Binary files differnew file mode 100644 index 0000000..c7d9da9 --- /dev/null +++ b/utils/hlfaceposer/project.ico diff --git a/utils/hlfaceposer/project1.ico b/utils/hlfaceposer/project1.ico Binary files differnew file mode 100644 index 0000000..3e2b5c5 --- /dev/null +++ b/utils/hlfaceposer/project1.ico diff --git a/utils/hlfaceposer/resource.h b/utils/hlfaceposer/resource.h new file mode 100644 index 0000000..84c410c --- /dev/null +++ b/utils/hlfaceposer/resource.h @@ -0,0 +1,149 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by hlfaceposer.rc +// +#define IDI_ICON1 101 +#define IDD_ACTORPROPERTIES 102 +#define IDD_INPUTDIALOG 103 +#define IDD_EXPRESSIONPROPERTIES 104 +#define IDI_WORKSPACE 104 +#define IDD_CHANNELPROPERTIES 105 +#define IDC_MOVETAG 105 +#define IDI_PROJECT 105 +#define IDD_EVENTPROPERTIES 106 +#define IDI_SCENE 106 +#define IDD_PHONEMEPROPERTIES 107 +#define IDI_VCD 107 +#define IDD_GLOBALEVENTPROPERTIES 108 +#define IDI_WAV 108 +#define IDD_FLEXSLIDERS 109 +#define IDI_SPEAK 109 +#define IDI_GRAY 112 +#define IDI_UNCHECKED 113 +#define IDI_CHECKED 114 +#define IDD_CHOICEDIALOG 115 +#define IDI_VCD_CHECKEDOUT 115 +#define IDD_EDITPHRASE 116 +#define IDI_PROJECT_CHECKEDOUT 116 +#define IDI_SPEAK_CHECKEDOUT 117 +#define IDD_WAVELOOKUP 118 +#define IDI_WAV_CHECKEDOUT 118 +#define IDD_ADDSOUNDENTRY 119 +#define IDI_WORKSPACE_CHECKEDOUT 119 +#define IDD_CCLOOKUP 120 +#define IDC_CURSOR1 120 +#define IDD_EVENTPROPERTIES_EXPRESSION 121 +#define IDD_EVENTPROPERTIES_LOOKAT 122 +#define IDD_EVENTPROPERTIES_MOVETO 123 +#define IDD_EVENTPROPERTIES_SPEAK 124 +#define IDD_EVENTPROPERTIES_GESTURE 125 +#define IDD_EVENTPROPERTIES_SEQUENCE 126 +#define IDD_EVENTPROPERTIES_FACE 127 +#define IDD_EVENTPROPERTIES_FIRETRIGGER 128 +#define IDD_EVENTPROPERTIES_FLEXANIMATION 129 +#define IDD_EVENTPROPERTIES_SUBSCENE 130 +#define IDD_EVENTPROPERTIES_INTERRUPT 131 +#define IDD_EVENTPROPERTIES_STOPPOINT 132 +#define IDD_EDGEPROPERTIES 132 +#define IDD_EVENTPROPERTIES_PERMITRESPONSES 133 +#define IDD_EVENTPROPERTIES_GENERIC 134 +#define IDD_PROGRESS 135 +#define IDC_ACTORNAME 1000 +#define IDC_EXPRESSIONDESC 1001 +#define IDC_STATIC_PROMPT 1003 +#define IDC_EXPRESSIONNAME 1004 +#define IDC_CHANNELNAME 1005 +#define IDC_EVENTCHOICES 1007 +#define IDC_TYPENAME 1008 +#define IDC_SELECTWAV 1009 +#define IDC_EVENTCHOICES2 1010 +#define IDC_FILENAME 1011 +#define IDC_TYPENAME2 1011 +#define IDC_EVENTNAME 1012 +#define IDC_ACTORCHOICE 1013 +#define IDC_CHOICES2PROMPT 1013 +#define IDC_STATIC_ACTOR 1014 +#define IDC_CHOICES3PROMPT 1014 +#define IDC_STARTTIME 1015 +#define IDC_ENDTIME 1016 +#define IDC_LOOPTIME 1016 +#define IDC_CHECK_ENDTIME 1017 +#define IDC_CHECK_RESUMECONDITION 1018 +#define IDC_CHECK_AUTOCHECK 1019 +#define IDC_CHECK_LOCKBODYFACING 1019 +#define IDC_CB_AUTOACTION 1020 +#define IDC_DURATION 1021 +#define IDC_INPUTSTRING 1022 +#define IDC_LOOPCOUNT 1023 +#define IDC_PHONEMENAME 1024 +#define IDC_ABSOLUTESTART 1025 +#define IDC_RELATIVESTART 1026 +#define IDC_TAGS 1027 +#define IDC_EDIT_PHONEME 1028 +#define IDC_PHONEMETEXTPROMPT 1029 +#define IDC_STATIC_HELPTEXT 1030 +#define IDC_STATIC_SPLINE 1038 +#define IDC_SLIDERS 1040 +#define IDC_CHOICE 1043 +#define IDC_EVENTCHOICES3 1044 +#define IDC_STATIC_PITCH 1046 +#define IDC_STATIC_YAW 1047 +#define IDC_SLIDER_PITCH 1048 +#define IDC_SLIDER_YAW 1049 +#define IDC_STATIC_PITCHVAL 1050 +#define IDC_STATIC_YAWVAL 1051 +#define IDC_CHECK_LOOKAT 1052 +#define IDC_STATIC_LOOPCOUNT 1054 +#define IDC_STATIC_LOOPTIME 1055 +#define IDC_STATIC_AFTER 1056 +#define IDC_STATIC_SECONDS 1057 +#define IDC_SHOW_ALL_SOUNDS 1058 +#define IDC_SOUNDENTRYLIST 1059 +#define IDC_CAPTION_ATTENUATION 1059 +#define IDC_ADDENTRY 1061 +#define IDC_SOUNDNAME 1061 +#define IDC_SOUNDSCRIPT 1062 +#define IDC_CCTOKEN 1065 +#define IDC_CCTOKENLIST 1066 +#define IDC_SOUNDLIST 1067 +#define IDC_FILTER 1068 +#define IDC_STATIC_WAVEFILENAME 1069 +#define IDC_STATIC_SCRIPTFILE 1070 +#define IDC_PLAY_SOUND 1071 +#define IDC_OPENSOURCE 1072 +#define IDC_STATIC_DISTANCE 1074 +#define IDC_SLIDER_DISTANCE 1075 +#define IDC_STATIC_DISTANCEVAL 1076 +#define IDC_LEFT_CURVETYPE 1077 +#define IDC_RIGHT_CURVETYPE 1078 +#define IDC_LEFT_ACTIVE 1079 +#define IDC_RIGHT_ACTIVE 1080 +#define IDC_LEFT_ZEROVALUE 1081 +#define IDC_RIGHT_ZEROVALUE 1082 +#define IDC_LEFT_RESET 1083 +#define IDC_RIGHT_RESET 1084 +#define IDC_CHECK1 1086 +#define IDC_HOLD_OUT 1086 +#define IDC_CHECK_FORCESHORTMOVEMENT 1087 +#define IDC_CHECK_SYNCTOFOLLOWINGGESTURE 1088 +#define IDC_CHECK_DISABLED 1090 +#define IDC_CHECK_PLAYOVERSCRIPT 1091 +#define IDC_FP_PROGRESS 1092 +#define IDC_FP_PROGRESS_TITLE 1093 +#define IDC_FP_PROGRESS_TEXT 1094 +#define IDC_FP_PROGRESS_CANCEL 1095 +#define IDC_FP_PROGRESS_PERCENT 1095 +#define IDC_FP_PROGRESS_PERCENT2 1096 +#define IDC_FP_PROGRESS_ETA 1096 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 133 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1096 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/utils/hlfaceposer/scene.ico b/utils/hlfaceposer/scene.ico Binary files differnew file mode 100644 index 0000000..ad48cbf --- /dev/null +++ b/utils/hlfaceposer/scene.ico diff --git a/utils/hlfaceposer/snd_audio_source.cpp b/utils/hlfaceposer/snd_audio_source.cpp new file mode 100644 index 0000000..58fd6fa --- /dev/null +++ b/utils/hlfaceposer/snd_audio_source.cpp @@ -0,0 +1,63 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include <stdio.h> +#include "snd_audio_source.h" + + + +extern CAudioSource *Audio_CreateMemoryWave( const char *pName ); + +//----------------------------------------------------------------------------- +// Purpose: Simple wrapper to crack naming convention and create the proper wave source +// Input : *pName - WAVE filename +// Output : CAudioSource +//----------------------------------------------------------------------------- +CAudioSource *AudioSource_Create( const char *pName ) +{ + if ( !pName ) + return NULL; + +// if ( pName[0] == '!' ) // sentence + ; + + // Names that begin with "*" are streaming. + // Skip over the * and create a streamed source + if ( pName[0] == '*' ) + { + + return NULL; + } + + // These are loaded into memory directly + return Audio_CreateMemoryWave( pName ); +} +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#include "hlfaceposer.h" +#include "ifaceposersound.h" + +CAudioSource::~CAudioSource( void ) +{ + CAudioMixer *mixer; + + while ( 1 ) + { + mixer = sound->FindMixer( this ); + if ( !mixer ) + break; + + sound->StopSound( mixer ); + } + + sound->EnsureNoModelReferences( this ); +} + +CAudioSource::CAudioSource( void ) +{ +} diff --git a/utils/hlfaceposer/snd_audio_source.h b/utils/hlfaceposer/snd_audio_source.h new file mode 100644 index 0000000..a60f14b --- /dev/null +++ b/utils/hlfaceposer/snd_audio_source.h @@ -0,0 +1,124 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef SND_AUDIO_SOURCE_H +#define SND_AUDIO_SOURCE_H +#pragma once + + +// fixed point stuff for real-time resampling +#define FIX_BITS 28 +#define FIX_SCALE (1 << FIX_BITS) +#define FIX_MASK ((1 << FIX_BITS)-1) +#define FIX_FLOAT(a) ((int)((a) * FIX_SCALE)) +#define FIX(a) (((int)(a)) << FIX_BITS) +#define FIX_INTPART(a) (((int)(a)) >> FIX_BITS) +#define FIX_FRACTION(a,b) (FIX(a)/(b)) +#define FIX_FRACPART(a) ((a) & FIX_MASK) + +typedef unsigned int fixedint; + +typedef struct channel_s channel_t; + +class CAudioSource; + +class IAudioDevice +{ +public: + // Add a virtual destructor to silence the clang warning. + // This is harmless but not important since the only derived class + // doesn't have a destructor. + virtual ~IAudioDevice() {} + + virtual void MixBegin( void ) = 0; + virtual void Mix8Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, int rateScaleFix, int outCount, int timecompress, bool forward = true ) = 0; + virtual void Mix8Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, int rateScaleFix, int outCount, int timecompress, bool forward = true ) = 0; + virtual void Mix16Mono( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, int rateScaleFix, int outCount, int timecompress, bool forward = true ) = 0; + virtual void Mix16Stereo( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, int rateScaleFix, int outCount, int timecompress, bool forward = true ) = 0; + virtual int MaxSampleCount( void ) = 0; +}; + +//----------------------------------------------------------------------------- +// Purpose: This is an instance of an audio source. +// Mixers are attached to channels and reference an audio source. +// Mixers are specific to the sample format and source format. +// Mixers are never re-used, so they can track instance data like +// sample position, fractional sample, stream cache, faders, etc. +//----------------------------------------------------------------------------- +class CAudioMixer +{ +public: + virtual ~CAudioMixer( void ) {} + + // UNDONE: time compress + virtual bool MixDataToDevice( IAudioDevice *pDevice, channel_t *pChannel, int startSample, int sampleCount, int outputRate, bool forward = true ) = 0; + virtual void IncrementSamples( channel_t *pChannel, int startSample, int sampleCount,int outputRate, bool forward = true ) = 0; + virtual bool SkipSamples( channel_t *pChannel, int startSample, int sampleCount, int outputRate, bool forward = true ) = 0; + + virtual CAudioSource *GetSource( void ) = 0; + + virtual int GetSamplePosition( void ) = 0; + virtual int GetScrubPosition( void ) = 0; + + virtual bool SetSamplePosition( int position, bool scrubbing = false ) = 0; + virtual void SetLoopPosition( int position ) = 0; + virtual int GetStartPosition( void ) = 0; + + virtual bool GetActive( void ) = 0; + virtual void SetActive( bool active ) = 0; + + virtual void SetModelIndex( int index ) = 0; + virtual int GetModelIndex( void ) const = 0; + + virtual void SetDirection( bool forward ) = 0; + virtual bool GetDirection( void ) const = 0; + + virtual void SetAutoDelete( bool autodelete ) = 0; + virtual bool GetAutoDelete( void ) const = 0; + + virtual void SetVolume( float volume ) = 0; + virtual channel_t *GetChannel() = 0; +}; + +//----------------------------------------------------------------------------- +// Purpose: A source is an abstraction for a stream, cached file, or procedural +// source of audio. +//----------------------------------------------------------------------------- +class CSentence; + +class CAudioSource +{ +public: + CAudioSource( void ); + virtual ~CAudioSource( void ); + + // Create an instance (mixer) of this audio source + virtual CAudioMixer *CreateMixer( void ) = 0; + virtual int GetOutputData( void **pData, int samplePosition, int sampleCount, bool forward = true ) = 0; + virtual int SampleRate( void ) = 0; + virtual int SampleSize( void ) = 0; + virtual int SampleCount( void ) = 0; + virtual float TrueSampleSize( void ) = 0; + virtual bool IsLooped( void ) = 0; + virtual bool IsStreaming( void ) = 0; + virtual float GetRunningLength( void ) = 0; + virtual int GetNumChannels() = 0; + virtual bool IsStereoWav( void ) = 0; + + virtual CSentence *GetSentence( void ) { return NULL; }; +}; + + +extern CAudioSource *AudioSource_Create( const char *pName ); + +#endif // SND_AUDIO_SOURCE_H diff --git a/utils/hlfaceposer/snd_wave_mixer.cpp b/utils/hlfaceposer/snd_wave_mixer.cpp new file mode 100644 index 0000000..e90d5cd --- /dev/null +++ b/utils/hlfaceposer/snd_wave_mixer.cpp @@ -0,0 +1,537 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//===========================================================================// +#include <stdio.h> +#include <windows.h> +#include "snd_audio_source.h" +#include "snd_wave_source.h" +#include "snd_wave_mixer_private.h" +#include "snd_wave_mixer_adpcm.h" +#include "ifaceposersound.h" +#include "AudioWaveOutput.h" +#include "tier2/riff.h" + +typedef struct channel_s +{ + int leftvol; + int rightvol; + int rleftvol; + int rrightvol; + float pitch; +} channel_t; + +//----------------------------------------------------------------------------- +// These mixers provide an abstraction layer between the audio device and +// mixing/decoding code. They allow data to be decoded and mixed using +// optimized, format sensitive code by calling back into the device that +// controls them. +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Purpose: maps mixing to 8-bit mono mixer +//----------------------------------------------------------------------------- +class CAudioMixerWave8Mono : public CAudioMixerWave +{ +public: + CAudioMixerWave8Mono( CWaveData *data ) : CAudioMixerWave( data ) {} + virtual void Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress, bool forward = true ) + { + pDevice->Mix8Mono( pChannel, (char *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress, forward ); + } +}; + +//----------------------------------------------------------------------------- +// Purpose: maps mixing to 8-bit stereo mixer +//----------------------------------------------------------------------------- +class CAudioMixerWave8Stereo : public CAudioMixerWave +{ +public: + CAudioMixerWave8Stereo( CWaveData *data ) : CAudioMixerWave( data ) {} + virtual void Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress, bool forward = true ) + { + pDevice->Mix8Stereo( pChannel, (char *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress, forward ); + } +}; + +//----------------------------------------------------------------------------- +// Purpose: maps mixing to 16-bit mono mixer +//----------------------------------------------------------------------------- +class CAudioMixerWave16Mono : public CAudioMixerWave +{ +public: + CAudioMixerWave16Mono( CWaveData *data ) : CAudioMixerWave( data ) {} + virtual void Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress, bool forward = true ) + { + pDevice->Mix16Mono( pChannel, (short *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress, forward ); + } +}; + + +//----------------------------------------------------------------------------- +// Purpose: maps mixing to 16-bit stereo mixer +//----------------------------------------------------------------------------- +class CAudioMixerWave16Stereo : public CAudioMixerWave +{ +public: + CAudioMixerWave16Stereo( CWaveData *data ) : CAudioMixerWave( data ) {} + virtual void Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress, bool forward = true ) + { + pDevice->Mix16Stereo( pChannel, (short *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress, forward ); + } +}; + + +//----------------------------------------------------------------------------- +// Purpose: Create an approprite mixer type given the data format +// Input : *data - data access abstraction +// format - pcm or adpcm (1 or 2 -- RIFF format) +// channels - number of audio channels (1 = mono, 2 = stereo) +// bits - bits per sample +// Output : CAudioMixer * abstract mixer type that maps mixing to appropriate code +//----------------------------------------------------------------------------- +CAudioMixer *CreateWaveMixer( CWaveData *data, int format, int channels, int bits ) +{ + if ( format == WAVE_FORMAT_PCM ) + { + if ( channels > 1 ) + { + if ( bits == 8 ) + return new CAudioMixerWave8Stereo( data ); + else + return new CAudioMixerWave16Stereo( data ); + } + else + { + if ( bits == 8 ) + return new CAudioMixerWave8Mono( data ); + else + return new CAudioMixerWave16Mono( data ); + } + } + else if ( format == WAVE_FORMAT_ADPCM ) + { + return CreateADPCMMixer( data ); + } + return NULL; +} + +#include "hlfaceposer.h" +//----------------------------------------------------------------------------- +// Purpose: Init the base WAVE mixer. +// Input : *data - data access object +//----------------------------------------------------------------------------- +CAudioMixerWave::CAudioMixerWave( CWaveData *data ) : m_pData(data), m_pChannel(NULL) +{ + m_loop = 0; + m_sample = 0; + m_absoluteSample = 0; + m_scrubSample = -1; + m_fracOffset = 0; + m_bActive = false; + m_nModelIndex = -1; + m_bForward = true; + m_bAutoDelete = true; + m_pChannel = new channel_t; + m_pChannel->leftvol = 127; + m_pChannel->rightvol = 127; + m_pChannel->pitch = 1.0; +} + +//----------------------------------------------------------------------------- +// Purpose: Frees the data access object (we own it after construction) +//----------------------------------------------------------------------------- +CAudioMixerWave::~CAudioMixerWave( void ) +{ + delete m_pData; + delete m_pChannel; +} + + +//----------------------------------------------------------------------------- +// Purpose: Decode and read the data +// by default we just pass the request on to the data access object +// other mixers may need to buffer or decode the data for some reason +// +// Input : **pData - dest pointer +// sampleCount - number of samples needed +// Output : number of samples available in this batch +//----------------------------------------------------------------------------- +int CAudioMixerWave::GetOutputData( void **pData, int samplePosition, int sampleCount, bool forward /*= true*/ ) +{ + if ( samplePosition != m_sample ) + { + // Seek + m_sample = samplePosition; + m_absoluteSample = samplePosition; + } + + return m_pData->ReadSourceData( pData, m_sample, sampleCount, forward ); +} + + +//----------------------------------------------------------------------------- +// Purpose: calls through the wavedata to get the audio source +// Output : CAudioSource +//----------------------------------------------------------------------------- +CAudioSource *CAudioMixerWave::GetSource( void ) +{ + if ( m_pData ) + return &m_pData->Source(); + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Gets the current sample location in playback +// Output : int (samples from start of wave) +//----------------------------------------------------------------------------- +int CAudioMixerWave::GetSamplePosition( void ) +{ + return m_sample; +} + + +//----------------------------------------------------------------------------- +// Purpose: Gets the current sample location in playback +// Output : int (samples from start of wave) +//----------------------------------------------------------------------------- +int CAudioMixerWave::GetScrubPosition( void ) +{ + if (m_scrubSample != -1) + { + return m_scrubSample; + } + return m_sample; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : position - +//----------------------------------------------------------------------------- +bool CAudioMixerWave::SetSamplePosition( int position, bool scrubbing ) +{ + position = max( 0, position ); + + m_sample = position; + m_absoluteSample = position; + m_startpos = m_sample; + if (scrubbing) + { + m_scrubSample = position; + } + else + { + m_scrubSample = -1; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : position - +//----------------------------------------------------------------------------- +void CAudioMixerWave::SetLoopPosition( int position ) +{ + m_loop = position; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CAudioMixerWave::GetStartPosition( void ) +{ + return m_startpos; +} + +bool CAudioMixerWave::GetActive( void ) +{ + return m_bActive; +} + +void CAudioMixerWave::SetActive( bool active ) +{ + m_bActive = active; +} + +void CAudioMixerWave::SetModelIndex( int index ) +{ + m_nModelIndex = index; +} + +int CAudioMixerWave::GetModelIndex( void ) const +{ + return m_nModelIndex; +} + +void CAudioMixerWave::SetDirection( bool forward ) +{ + m_bForward = forward; +} + +bool CAudioMixerWave::GetDirection( void ) const +{ + return m_bForward; +} + +void CAudioMixerWave::SetAutoDelete( bool autodelete ) +{ + m_bAutoDelete = autodelete; +} + +bool CAudioMixerWave::GetAutoDelete( void ) const +{ + return m_bAutoDelete; +} + +void CAudioMixerWave::SetVolume( float volume ) +{ + int ivolume = (int)( clamp( volume, 0.0f, 1.0f ) * 127.0f ); + + m_pChannel->leftvol = ivolume; + m_pChannel->rightvol = ivolume; +} + +channel_t *CAudioMixerWave::GetChannel() +{ + Assert( m_pChannel ); + return m_pChannel; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pChannel - +// sampleCount - +// outputRate - +//----------------------------------------------------------------------------- +void CAudioMixerWave::IncrementSamples( channel_t *pChannel, int startSample, int sampleCount,int outputRate, bool forward /*= true*/ ) +{ + int inputSampleRate = (int)(pChannel->pitch * m_pData->Source().SampleRate()); + float rate = (float)inputSampleRate / outputRate; + + int startpos = startSample; + + if ( !forward ) + { + int requestedstart = startSample - (int)( sampleCount * rate ); + if ( requestedstart < 0 ) + return; + + startpos = max( 0, requestedstart ); + SetSamplePosition( startpos ); + } + + while ( sampleCount > 0 ) + { + int inputSampleCount; + int outputSampleCount = sampleCount; + + if ( outputRate != inputSampleRate ) + { + inputSampleCount = (int)(sampleCount * rate); + } + else + { + inputSampleCount = sampleCount; + } + + sampleCount -= outputSampleCount; + if ( forward ) + { + m_sample += inputSampleCount; + m_absoluteSample += inputSampleCount; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: The device calls this to request data. The mixer must provide the +// full amount of samples or have silence in its output stream. +// Input : *pDevice - requesting device +// sampleCount - number of samples at the output rate +// outputRate - sampling rate of the request +// Output : Returns true to keep mixing, false to delete this mixer +//----------------------------------------------------------------------------- +bool CAudioMixerWave::SkipSamples( channel_t *pChannel, int startSample, int sampleCount, int outputRate, bool forward /*= true*/ ) +{ + int offset = 0; + + int inputSampleRate = (int)(pChannel->pitch * m_pData->Source().SampleRate()); + float rate = (float)inputSampleRate / outputRate; + + sampleCount = min( sampleCount, PAINTBUFFER_SIZE ); + + int startpos = startSample; + + if ( !forward ) + { + int requestedstart = startSample - (int)( sampleCount * rate ); + if ( requestedstart < 0 ) + return false; + + startpos = max( 0, requestedstart ); + SetSamplePosition( startpos ); + } + + while ( sampleCount > 0 ) + { + int inputSampleCount; + char *pData = NULL; + int outputSampleCount = sampleCount; + + if ( outputRate != inputSampleRate ) + { + inputSampleCount = (int)(sampleCount * rate); + if ( !forward ) + { + startSample = max( 0, startSample - inputSampleCount ); + } + int availableSamples = GetOutputData( (void **)&pData, startSample, inputSampleCount, forward ); + if ( !availableSamples ) + break; + + if ( availableSamples < inputSampleCount ) + { + outputSampleCount = (int)(availableSamples / rate); + inputSampleCount = availableSamples; + } + + // compute new fraction part of sample index + float flOffset = (m_fracOffset / FIX_SCALE) + (rate * outputSampleCount); + flOffset = flOffset - (float)((int)flOffset); + m_fracOffset = FIX_FLOAT( flOffset ); + } + else + { + if ( !forward ) + { + startSample = max( 0, startSample - sampleCount ); + } + int availableSamples = GetOutputData( (void **)&pData, startSample, sampleCount, forward ); + if ( !availableSamples ) + break; + outputSampleCount = availableSamples; + inputSampleCount = availableSamples; + + } + offset += outputSampleCount; + sampleCount -= outputSampleCount; + if ( forward ) + { + m_sample += inputSampleCount; + m_absoluteSample += inputSampleCount; + } + + if ( m_loop != 0 && m_sample >= m_loop ) + { + SetSamplePosition( m_startpos ); + } + + } + + if ( sampleCount > 0 ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: The device calls this to request data. The mixer must provide the +// full amount of samples or have silence in its output stream. +// Input : *pDevice - requesting device +// sampleCount - number of samples at the output rate +// outputRate - sampling rate of the request +// Output : Returns true to keep mixing, false to delete this mixer +//----------------------------------------------------------------------------- +bool CAudioMixerWave::MixDataToDevice( IAudioDevice *pDevice, channel_t *pChannel, int startSample, int sampleCount, int outputRate, bool forward /*= true*/ ) +{ + int offset = 0; + + int inputSampleRate = (int)(pChannel->pitch * m_pData->Source().SampleRate()); + float rate = (float)inputSampleRate / outputRate; + fixedint fracstep = FIX_FLOAT( rate ); + + sampleCount = min( sampleCount, PAINTBUFFER_SIZE ); + + int startpos = startSample; + + if ( !forward ) + { + int requestedstart = startSample - (int)( sampleCount * rate ); + if ( requestedstart < 0 ) + return false; + + startpos = max( 0, requestedstart ); + SetSamplePosition( startpos ); + } + + while ( sampleCount > 0 ) + { + int inputSampleCount; + char *pData = NULL; + int outputSampleCount = sampleCount; + + + if ( outputRate != inputSampleRate ) + { + inputSampleCount = (int)(sampleCount * rate); + + int availableSamples = GetOutputData( (void **)&pData, startpos, inputSampleCount, forward ); + if ( !availableSamples ) + break; + + if ( availableSamples < inputSampleCount ) + { + outputSampleCount = (int)(availableSamples / rate); + inputSampleCount = availableSamples; + } + + Mix( pDevice, pChannel, pData, offset, m_fracOffset, fracstep, outputSampleCount, 0, forward ); + + // compute new fraction part of sample index + float flOffset = (m_fracOffset / FIX_SCALE) + (rate * outputSampleCount); + flOffset = flOffset - (float)((int)flOffset); + m_fracOffset = FIX_FLOAT( flOffset ); + } + else + { + int availableSamples = GetOutputData( (void **)&pData, startpos, sampleCount, forward ); + if ( !availableSamples ) + break; + + outputSampleCount = availableSamples; + inputSampleCount = availableSamples; + + Mix( pDevice, pChannel, pData, offset, m_fracOffset, FIX(1), outputSampleCount, 0, forward ); + } + offset += outputSampleCount; + sampleCount -= outputSampleCount; + + if ( forward ) + { + m_sample += inputSampleCount; + m_absoluteSample += inputSampleCount; + } + + if ( m_loop != 0 && m_sample >= m_loop ) + { + SetSamplePosition( m_startpos ); + } + + } + + if ( sampleCount > 0 ) + return false; + + return true; +} diff --git a/utils/hlfaceposer/snd_wave_mixer.h b/utils/hlfaceposer/snd_wave_mixer.h new file mode 100644 index 0000000..07e019b --- /dev/null +++ b/utils/hlfaceposer/snd_wave_mixer.h @@ -0,0 +1,24 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef SND_WAVE_MIXER_H +#define SND_WAVE_MIXER_H +#pragma once + +class CWaveData; +class CAudioMixer; + +CAudioMixer *CreateWaveMixer( CWaveData *data, int format, int channels, int bits ); + + +#endif // SND_WAVE_MIXER_H diff --git a/utils/hlfaceposer/snd_wave_mixer_adpcm.cpp b/utils/hlfaceposer/snd_wave_mixer_adpcm.cpp new file mode 100644 index 0000000..a76b66b --- /dev/null +++ b/utils/hlfaceposer/snd_wave_mixer_adpcm.cpp @@ -0,0 +1,516 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#pragma warning( disable: 4201 ) +#include <mmsystem.h> +#pragma warning( default: 4201 ) + +#include <mmreg.h> +#include "snd_wave_source.h" +#include "snd_wave_mixer_adpcm.h" +#include "snd_wave_mixer_private.h" +#include "hlfaceposer.h" + +// max size of ADPCM block in bytes +#define MAX_BLOCK_SIZE 4096 + + +//----------------------------------------------------------------------------- +// Purpose: Mixer for ADPCM encoded audio +//----------------------------------------------------------------------------- +class CAudioMixerWaveADPCM : public CAudioMixerWave +{ +public: + CAudioMixerWaveADPCM( CWaveData *data ); + ~CAudioMixerWaveADPCM( void ); + + virtual void Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress, bool forward = true ); + virtual int GetOutputData( void **pData, int samplePosition, int sampleCount, bool forward = true ); + + virtual bool SetSamplePosition( int position, bool scrubbing = false ); + +private: + bool DecodeBlock( void ); + int NumChannels( void ); + void DecompressBlockMono( short *pOut, const char *pIn, int count ); + void DecompressBlockStereo( short *pOut, const char *pIn, int count ); + + void SetCurrentBlock( int block ); + int GetCurrentBlock( void ) const; + int GetBlockNumberForSample( int samplePosition ); + bool IsSampleInCurrentBlock( int samplePosition ); + int GetFirstSampleForBlock( int blocknum ) const; + + const ADPCMWAVEFORMAT *m_pFormat; + const ADPCMCOEFSET *m_pCoefficients; + + short *m_pSamples; + int m_sampleCount; + int m_samplePosition; + + int m_blockSize; + int m_offset; + + int m_currentBlock; +}; + + +CAudioMixerWaveADPCM::CAudioMixerWaveADPCM( CWaveData *data ) : CAudioMixerWave( data ) +{ + m_currentBlock = -1; + m_pSamples = NULL; + m_sampleCount = 0; + m_samplePosition = 0; + m_offset = 0; + + m_pFormat = (const ADPCMWAVEFORMAT *)m_pData->Source().GetHeader(); + if ( m_pFormat ) + { + m_pCoefficients = (ADPCMCOEFSET *)((char *)m_pFormat + sizeof(WAVEFORMATEX) + 4); + + // create the decode buffer + m_pSamples = new short[m_pFormat->wSamplesPerBlock * m_pFormat->wfx.nChannels]; + + // number of bytes for samples + m_blockSize = ((m_pFormat->wSamplesPerBlock - 2) * m_pFormat->wfx.nChannels ) / 2; + // size of channel header + m_blockSize += 7 * m_pFormat->wfx.nChannels; +// Assert(m_blockSize < MAX_BLOCK_SIZE); + } +} + + +CAudioMixerWaveADPCM::~CAudioMixerWaveADPCM( void ) +{ + delete[] m_pSamples; +} + + +int CAudioMixerWaveADPCM::NumChannels( void ) +{ + if ( m_pFormat ) + { + return m_pFormat->wfx.nChannels; + } + return 0; +} + +void CAudioMixerWaveADPCM::Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress, bool forward /*= true*/ ) +{ + if ( NumChannels() == 1 ) + pDevice->Mix16Mono( pChannel, (short *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress, forward ); + else + pDevice->Mix16Stereo( pChannel, (short *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress, forward ); +} + + +static int error_sign_lut[] = { 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1 }; +static int error_coefficients_lut[] = { 230, 230, 230, 230, 307, 409, 512, 614, + 768, 614, 512, 409, 307, 230, 230, 230 }; + +//----------------------------------------------------------------------------- +// Purpose: ADPCM decompress a single block of 1-channel audio +// Input : *pOut - output buffer 16-bit +// *pIn - input block +// count - number of samples to decode (to support partial blocks) +//----------------------------------------------------------------------------- +void CAudioMixerWaveADPCM::DecompressBlockMono( short *pOut, const char *pIn, int count ) +{ + + int pred = *pIn++; + int co1 = m_pCoefficients[pred].iCoef1; + int co2 = m_pCoefficients[pred].iCoef2; + + // read initial delta + int delta = *((short *)pIn); + pIn += 2; + + // read initial samples for prediction + int samp1 = *((short *)pIn); + pIn += 2; + + int samp2 = *((short *)pIn); + pIn += 2; + + // write out the initial samples (stored in reverse order) + *pOut++ = (short)samp2; + *pOut++ = (short)samp1; + + // subtract the 2 samples in the header + count -= 2; + + // this is a toggle to read nibbles, first nibble is high + int high = 1; + + int error = 0, sample = 0; + + // now process the block + while ( count ) + { + // read the error nibble from the input stream + if ( high ) + { + sample = (unsigned char) (*pIn++); + // high nibble + error = sample >> 4; + // cache low nibble for next read + sample = sample & 0xf; + // Next read is from cache, not stream + high = 0; + } + else + { + // stored in previous read (low nibble) + error = sample; + // next read is from stream + high = 1; + } + // convert to signed with LUT + int errorSign = error_sign_lut[error]; + + // interpolate the new sample + int predSample = (samp1 * co1) + (samp2 * co2); + // coefficients are fixed point 8-bit, so shift back to 16-bit integer + predSample >>= 8; + + // Add in current error estimate + predSample += (errorSign * delta); + + // Correct error estimate + delta = (delta * error_coefficients_lut[error]) >> 8; + // Clamp error estimate + if ( delta < 16 ) + delta = 16; + + // clamp + if ( predSample > 32767L ) + predSample = 32767L; + else if ( predSample < -32768L ) + predSample = -32768L; + + // output + *pOut++ = (short)predSample; + // move samples over + samp2 = samp1; + samp1 = predSample; + + count--; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Decode a single block of stereo ADPCM audio +// Input : *pOut - 16-bit output buffer +// *pIn - ADPCM encoded block data +// count - number of sample pairs to decode +//----------------------------------------------------------------------------- +void CAudioMixerWaveADPCM::DecompressBlockStereo( short *pOut, const char *pIn, int count ) +{ + int pred[2], co1[2], co2[2]; + int i; + + for ( i = 0; i < 2; i++ ) + { + pred[i] = *pIn++; + co1[i] = m_pCoefficients[pred[i]].iCoef1; + co2[i] = m_pCoefficients[pred[i]].iCoef2; + } + + int delta[2], samp1[2], samp2[2]; + + for ( i = 0; i < 2; i++, pIn += 2 ) + { + // read initial delta + delta[i] = *((short *)pIn); + } + + // read initial samples for prediction + for ( i = 0; i < 2; i++, pIn += 2 ) + { + samp1[i] = *((short *)pIn); + } + for ( i = 0; i < 2; i++, pIn += 2 ) + { + samp2[i] = *((short *)pIn); + } + + // write out the initial samples (stored in reverse order) + *pOut++ = (short)samp2[0]; // left + *pOut++ = (short)samp2[1]; // right + *pOut++ = (short)samp1[0]; // left + *pOut++ = (short)samp1[1]; // right + + // subtract the 2 samples in the header + count -= 2; + + // this is a toggle to read nibbles, first nibble is high + int high = 1; + + int error, sample = 0; + + // now process the block + while ( count ) + { + for ( i = 0; i < 2; i++ ) + { + // read the error nibble from the input stream + if ( high ) + { + sample = (unsigned char) (*pIn++); + // high nibble + error = sample >> 4; + // cache low nibble for next read + sample = sample & 0xf; + // Next read is from cache, not stream + high = 0; + } + else + { + // stored in previous read (low nibble) + error = sample; + // next read is from stream + high = 1; + } + // convert to signed with LUT + int errorSign = error_sign_lut[error]; + + // interpolate the new sample + int predSample = (samp1[i] * co1[i]) + (samp2[i] * co2[i]); + // coefficients are fixed point 8-bit, so shift back to 16-bit integer + predSample >>= 8; + + // Add in current error estimate + predSample += (errorSign * delta[i]); + + // Correct error estimate + delta[i] = (delta[i] * error_coefficients_lut[error]) >> 8; + // Clamp error estimate + if ( delta[i] < 16 ) + delta[i] = 16; + + // clamp + if ( predSample > 32767L ) + predSample = 32767L; + else if ( predSample < -32768L ) + predSample = -32768L; + + // output + *pOut++ = (short)predSample; + // move samples over + samp2[i] = samp1[i]; + samp1[i] = predSample; + } + count--; + } +} + + +bool CAudioMixerWaveADPCM::DecodeBlock( void ) +{ + char tmpBlock[MAX_BLOCK_SIZE]; + char *pData; + + int available = m_pData->ReadSourceData( (void **) (&pData), m_offset, m_blockSize ); + if ( available < m_blockSize ) + { + int total = 0; + while ( available && total < m_blockSize ) + { + memcpy( tmpBlock + total, pData, available ); + total += available; + available = m_pData->ReadSourceData( (void **) (&pData), m_offset + total, m_blockSize - total ); + } + pData = tmpBlock; + available = total; + } + + Assert( m_blockSize > 0 ); + + // Current block number is based on starting offset + int blockNumber = m_offset / m_blockSize; + SetCurrentBlock( blockNumber ); + + if ( !available ) + { + return false; + } + + // advance the file pointer + m_offset += available; + + int channelCount = NumChannels(); + + // this is sample pairs for stereo, samples for mono + m_sampleCount = m_pFormat->wSamplesPerBlock; + + // short block?, fixup sample count (2 samples per byte, divided by number of channels per sample set) + m_sampleCount -= ((m_blockSize - available) * 2) / channelCount; + + // new block, start at the first sample + m_samplePosition = 0; + + // no need to subclass for different channel counts... + if ( channelCount == 1 ) + { + DecompressBlockMono( m_pSamples, pData, m_sampleCount ); + } + else + { + DecompressBlockStereo( m_pSamples, pData, m_sampleCount ); + } + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : block - +//----------------------------------------------------------------------------- +void CAudioMixerWaveADPCM::SetCurrentBlock( int block ) +{ + m_currentBlock = block; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CAudioMixerWaveADPCM::GetCurrentBlock( void ) const +{ + return m_currentBlock; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : samplePosition - +// Output : int +//----------------------------------------------------------------------------- +int CAudioMixerWaveADPCM::GetBlockNumberForSample( int samplePosition ) +{ + int blockNum = samplePosition / m_pFormat->wSamplesPerBlock; + return blockNum; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : samplePosition - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAudioMixerWaveADPCM::IsSampleInCurrentBlock( int samplePosition ) +{ + int currentBlock = GetCurrentBlock(); + + int startSample = currentBlock * m_pFormat->wSamplesPerBlock; + int endSample = startSample + m_pFormat->wSamplesPerBlock - 1; + + if ( samplePosition >= startSample && + samplePosition <= endSample ) + { + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : blocknum - +// Output : int +//----------------------------------------------------------------------------- +int CAudioMixerWaveADPCM::GetFirstSampleForBlock( int blocknum ) const +{ + return m_pFormat->wSamplesPerBlock * blocknum; +} + +//----------------------------------------------------------------------------- +// Purpose: Read existing buffer or decompress a new block when necessary +// Input : **pData - output data pointer +// sampleCount - number of samples (or pairs) +// Output : int - available samples (zero to stop decoding) +//----------------------------------------------------------------------------- +int CAudioMixerWaveADPCM::GetOutputData( void **pData, int samplePosition, int sampleCount, bool forward /*= true*/ ) +{ + int requestedBlock = GetBlockNumberForSample( samplePosition ); + if ( requestedBlock != GetCurrentBlock() ) + { + // Ran out of data!!! + if ( !SetSamplePosition( samplePosition ) ) + return 0; + } + + Assert( requestedBlock == GetCurrentBlock() ); + + if ( m_samplePosition >= m_sampleCount ) + { + if ( !DecodeBlock() ) + return 0; + } + + if ( m_samplePosition < m_sampleCount ) + { + *pData = (void *)(m_pSamples + m_samplePosition * NumChannels()); + int available = m_sampleCount - m_samplePosition; + if ( available > sampleCount ) + available = sampleCount; + + m_samplePosition += available; + return available; + } + + return 0; +} + + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : position - +//----------------------------------------------------------------------------- +bool CAudioMixerWaveADPCM::SetSamplePosition( int position, bool scrubbing ) +{ + position = max( 0, position ); + + CAudioMixerWave::SetSamplePosition( position, scrubbing ); + + int requestedBlock = GetBlockNumberForSample( position ); + int firstSample = GetFirstSampleForBlock( requestedBlock ); + + if ( firstSample >= m_pData->Source().SampleCount() ) + { + // Read past end of file!!! + return false; + } + + int currentSample = ( position - firstSample ); + + if ( requestedBlock != GetCurrentBlock() ) + { + // Rewind file to beginning of block + m_offset = requestedBlock * m_blockSize; + if ( !DecodeBlock() ) + { + return false; + } + } + + m_samplePosition = currentSample; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Abstract factory function for ADPCM mixers +// Input : *data - wave data access object +// channels - +// Output : CAudioMixer +//----------------------------------------------------------------------------- +CAudioMixer *CreateADPCMMixer( CWaveData *data ) +{ + return new CAudioMixerWaveADPCM( data ); +} diff --git a/utils/hlfaceposer/snd_wave_mixer_adpcm.h b/utils/hlfaceposer/snd_wave_mixer_adpcm.h new file mode 100644 index 0000000..467ab5f --- /dev/null +++ b/utils/hlfaceposer/snd_wave_mixer_adpcm.h @@ -0,0 +1,24 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef SND_WAVE_MIXER_ADPCM_H +#define SND_WAVE_MIXER_ADPCM_H +#pragma once + + +class CAudioMixer; +class CWaveData; + +CAudioMixer *CreateADPCMMixer( CWaveData *data ); + +#endif // SND_WAVE_MIXER_ADPCM_H diff --git a/utils/hlfaceposer/snd_wave_mixer_private.h b/utils/hlfaceposer/snd_wave_mixer_private.h new file mode 100644 index 0000000..2155b87 --- /dev/null +++ b/utils/hlfaceposer/snd_wave_mixer_private.h @@ -0,0 +1,102 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef SND_WAVE_MIXER_PRIVATE_H +#define SND_WAVE_MIXER_PRIVATE_H +#pragma once + +#include "snd_audio_source.h" +#include "snd_wave_mixer.h" + + +//----------------------------------------------------------------------------- +// Purpose: Linear iterator over source data. +// Keeps track of position in source, and maintains necessary buffers +//----------------------------------------------------------------------------- +class CWaveData +{ +public: + virtual ~CWaveData( void ) {} + virtual CAudioSourceWave &Source( void ) = 0; + virtual int ReadSourceData( void **pData, int sampleIndex, int sampleCount, bool forward = true ) = 0; +}; + +class CAudioMixerWave : public CAudioMixer +{ +public: + CAudioMixerWave( CWaveData *data ); + virtual ~CAudioMixerWave( void ); + + virtual bool MixDataToDevice( IAudioDevice *pDevice, channel_t *pChannel, int startSample, int sampleCount, int outputRate, bool forward = true ); + virtual void IncrementSamples( channel_t *pChannel, int startSample, int sampleCount,int outputRate, bool forward = true ); + virtual bool SkipSamples( channel_t *pChannel, int startSample, int sampleCount,int outputRate, bool forward = true ); + virtual void Mix( IAudioDevice *pDevice, + channel_t *pChannel, + void *pData, + int outputOffset, + int inputOffset, + fixedint fracRate, + int outCount, + int timecompress, + bool forward = true ) = 0; + + virtual int GetOutputData( void **pData, int samplePosition, int sampleCount, bool forward = true ); + + virtual CAudioSource *GetSource( void ); + + virtual int GetSamplePosition( void ); + virtual int GetScrubPosition( void ); + + virtual bool SetSamplePosition( int position, bool scrubbing = false ); + virtual void SetLoopPosition( int position ); + virtual int GetStartPosition( void ); + + virtual bool GetActive( void ); + virtual void SetActive( bool active ); + + virtual void SetModelIndex( int index ); + virtual int GetModelIndex( void ) const; + + virtual void SetDirection( bool forward ); + virtual bool GetDirection( void ) const; + + virtual void SetAutoDelete( bool autodelete ); + virtual bool GetAutoDelete( void ) const; + + virtual void SetVolume( float volume ); + virtual channel_t *GetChannel(); + +protected: + int m_sample; + int m_absoluteSample; + int m_scrubSample; + int m_startpos; + int m_loop; + int m_fracOffset; + CWaveData *m_pData; + + int m_absoluteStartPos; + + bool m_bActive; + // Associated playback model in faceposer + int m_nModelIndex; + + bool m_bForward; + + bool m_bAutoDelete; + + channel_t *m_pChannel; +}; + + +#endif // SND_WAVE_MIXER_PRIVATE_H diff --git a/utils/hlfaceposer/snd_wave_source.cpp b/utils/hlfaceposer/snd_wave_source.cpp new file mode 100644 index 0000000..61f4b60 --- /dev/null +++ b/utils/hlfaceposer/snd_wave_source.cpp @@ -0,0 +1,605 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <windows.h> +#include "tier2/riff.h" +#include "snd_wave_source.h" +#include "snd_wave_mixer_private.h" +#include "snd_audio_source.h" +#include <mmsystem.h> // wave format +#include <mmreg.h> // adpcm format +#include "hlfaceposer.h" +#include "filesystem.h" +#include "utlbuffer.h" +#include "phonemeconverter.h" + +//----------------------------------------------------------------------------- +// Purpose: Implements the RIFF i/o interface on stdio +//----------------------------------------------------------------------------- +class StdIOReadBinary : public IFileReadBinary +{ +public: + int open( const char *pFileName ) + { + return (int)filesystem->Open( pFileName, "rb" ); + } + + int read( void *pOutput, int size, int file ) + { + if ( !file ) + return 0; + + return filesystem->Read( pOutput, size, (FileHandle_t)file ); + } + + void seek( int file, int pos ) + { + if ( !file ) + return; + + filesystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD ); + } + + unsigned int tell( int file ) + { + if ( !file ) + return 0; + + return filesystem->Tell( (FileHandle_t)file ); + } + + unsigned int size( int file ) + { + if ( !file ) + return 0; + + return filesystem->Size( (FileHandle_t)file ); + } + + void close( int file ) + { + if ( !file ) + return; + + filesystem->Close( (FileHandle_t)file ); + } +}; + +static StdIOReadBinary io; + +#define RIFF_WAVE MAKEID('W','A','V','E') +#define WAVE_FMT MAKEID('f','m','t',' ') +#define WAVE_DATA MAKEID('d','a','t','a') +#define WAVE_FACT MAKEID('f','a','c','t') +#define WAVE_CUE MAKEID('c','u','e',' ') + +void ChunkError( unsigned int id ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Init to empty wave +//----------------------------------------------------------------------------- +CAudioSourceWave::CAudioSourceWave( void ) +{ + m_format = 0; + m_pHeader = NULL; + // no looping + m_loopStart = -1; + m_sampleSize = 1; + m_sampleCount = 0; +} + + +CAudioSourceWave::~CAudioSourceWave( void ) +{ + // for non-standard waves, we store a copy of the header in RAM + delete[] m_pHeader; + // m_pWords points into m_pWordBuffer, no need to delete +} + +//----------------------------------------------------------------------------- +// Purpose: Init the wave data. +// Input : *pHeaderBuffer - the RIFF fmt chunk +// headerSize - size of that chunk +//----------------------------------------------------------------------------- +void CAudioSourceWave::Init( const char *pHeaderBuffer, int headerSize ) +{ + const WAVEFORMATEX *pHeader = (const WAVEFORMATEX *)pHeaderBuffer; + + // copy the relevant header data + m_format = pHeader->wFormatTag; + m_bits = pHeader->wBitsPerSample; + m_rate = pHeader->nSamplesPerSec; + m_channels = pHeader->nChannels; + + m_sampleSize = (m_bits * m_channels) / 8; + + // this can never be zero -- other functions divide by this. + // This should never happen, but avoid crashing + if ( m_sampleSize <= 0 ) + m_sampleSize = 1; + + // For non-standard waves (like ADPCM) store the header, it has some useful data + if ( m_format != WAVE_FORMAT_PCM ) + { + m_pHeader = new char[headerSize]; + memcpy( m_pHeader, pHeader, headerSize ); + if ( m_format == WAVE_FORMAT_ADPCM ) + { + // treat ADPCM sources as a file of bytes. They are decoded by the mixer + m_sampleSize = 1; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CAudioSourceWave::TrueSampleSize( void ) +{ + if ( m_format == WAVE_FORMAT_ADPCM ) + { + return 0.5f; + } + return (float)m_sampleSize; +} + +//----------------------------------------------------------------------------- +// Purpose: Total number of samples in this source +// Output : int +//----------------------------------------------------------------------------- +int CAudioSourceWave::SampleCount( void ) +{ + if ( m_format == WAVE_FORMAT_ADPCM ) + { + ADPCMWAVEFORMAT *pFormat = (ADPCMWAVEFORMAT *)m_pHeader; + int blockSize = ((pFormat->wSamplesPerBlock - 2) * pFormat->wfx.nChannels ) / 2; + blockSize += 7 * pFormat->wfx.nChannels; + + int blockCount = m_sampleCount / blockSize; + int blockRem = m_sampleCount % blockSize; + + // total samples in complete blocks + int sampleCount = blockCount * pFormat->wSamplesPerBlock; + + // add remaining in a short block + if ( blockRem ) + { + sampleCount += pFormat->wSamplesPerBlock - (((blockSize - blockRem) * 2) / m_channels); + } + return sampleCount; + } + return m_sampleCount; +} + +//----------------------------------------------------------------------------- +// Purpose: Do any sample conversion +// For 8 bit PCM, convert to signed because the mixing routine assumes this +// Input : *pData - pointer to sample data +// sampleCount - number of samples +//----------------------------------------------------------------------------- +void CAudioSourceWave::ConvertSamples( char *pData, int sampleCount ) +{ + if ( m_format == WAVE_FORMAT_PCM ) + { + if ( m_bits == 8 ) + { + for ( int i = 0; i < sampleCount; i++ ) + { + for ( int j = 0; j < m_channels; j++ ) + { + *pData = (unsigned char)((int)((unsigned)*pData) - 128); + pData++; + } + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &walk - +//----------------------------------------------------------------------------- +void CAudioSourceWave::ParseSentence( IterateRIFF &walk ) +{ + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + + buf.EnsureCapacity( walk.ChunkSize() ); + walk.ChunkRead( buf.Base() ); + buf.SeekPut( CUtlBuffer::SEEK_HEAD, walk.ChunkSize() ); + + m_Sentence.InitFromDataChunk( buf.Base(), buf.TellPut() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Parse base chunks +// Input : &walk - riff file to parse +// : chunkName - name of the chunk to parse +//----------------------------------------------------------------------------- +// UNDONE: Move parsing loop here and drop each chunk into a virtual function +// instead of this being virtual. +void CAudioSourceWave::ParseChunk( IterateRIFF &walk, int chunkName ) +{ + switch( chunkName ) + { + case WAVE_CUE: + { + m_loopStart = ParseCueChunk( walk ); + } + break; + case WAVE_VALVEDATA: + { + ParseSentence( walk ); + } + break; + // unknown/don't care + default: + { + ChunkError( walk.ChunkName() ); + } + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CSentence +//----------------------------------------------------------------------------- +CSentence *CAudioSourceWave::GetSentence( void ) +{ + return &m_Sentence; +} + +//----------------------------------------------------------------------------- +// Purpose: Bastardized construction routine. This is just to avoid complex +// constructor functions so code can be shared more easily by sub-classes +// Input : *pFormatBuffer - RIFF header +// formatSize - header size +// &walk - RIFF file +//----------------------------------------------------------------------------- +void CAudioSourceWave::Setup( const char *pFormatBuffer, int formatSize, IterateRIFF &walk ) +{ + Init( pFormatBuffer, formatSize ); + + while ( walk.ChunkAvailable() ) + { + ParseChunk( walk, walk.ChunkName() ); + walk.ChunkNext(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Wave file that is completely in memory +// UNDONE: Implement Lock/Unlock and caching +//----------------------------------------------------------------------------- +class CAudioSourceMemWave : public CAudioSourceWave +{ +public: + CAudioSourceMemWave( void ); + ~CAudioSourceMemWave( void ); + + // Create an instance (mixer) of this audio source + virtual CAudioMixer *CreateMixer( void ); + + virtual void ParseChunk( IterateRIFF &walk, int chunkName ); + void ParseDataChunk( IterateRIFF &walk ); + + virtual int GetOutputData( void **pData, int samplePosition, int sampleCount, bool forward = true ); + virtual float GetRunningLength( void ) { return CAudioSourceWave::GetRunningLength(); }; + + virtual int GetNumChannels(); + +private: + char *m_pData; // wave data +}; + + +//----------------------------------------------------------------------------- +// Purpose: Iterator for wave data (this is to abstract streaming/buffering) +//----------------------------------------------------------------------------- +class CWaveDataMemory : public CWaveData +{ +public: + CWaveDataMemory( CAudioSourceWave &source ) : m_source(source) {} + ~CWaveDataMemory( void ) {} + CAudioSourceWave &Source( void ) { return m_source; } + + // this file is in memory, simply pass along the data request to the source + virtual int ReadSourceData( void **pData, int sampleIndex, int sampleCount, bool forward /*= true*/ ) + { + return m_source.GetOutputData( pData, sampleIndex, sampleCount, forward ); + } +private: + CAudioSourceWave &m_source; // pointer to source +}; + + +//----------------------------------------------------------------------------- +// Purpose: NULL the wave data pointer (we haven't loaded yet) +//----------------------------------------------------------------------------- +CAudioSourceMemWave::CAudioSourceMemWave( void ) +{ + m_pData = NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Free any wave data we've allocated +//----------------------------------------------------------------------------- +CAudioSourceMemWave::~CAudioSourceMemWave( void ) +{ + delete[] m_pData; +} + +//----------------------------------------------------------------------------- +// Purpose: Creates a mixer and initializes it with an appropriate mixer +//----------------------------------------------------------------------------- +CAudioMixer *CAudioSourceMemWave::CreateMixer( void ) +{ + return CreateWaveMixer( new CWaveDataMemory(*this), m_format, m_channels, m_bits ); +} + +//----------------------------------------------------------------------------- +// Purpose: parse chunks with unique processing to in-memory waves +// Input : &walk - RIFF file +//----------------------------------------------------------------------------- +void CAudioSourceMemWave::ParseChunk( IterateRIFF &walk, int chunkName ) +{ + switch( chunkName ) + { + // this is the audio data + case WAVE_DATA: + { + ParseDataChunk( walk ); + } + return; + } + + CAudioSourceWave::ParseChunk( walk, chunkName ); +} + + +//----------------------------------------------------------------------------- +// Purpose: reads the actual sample data and parses it +// Input : &walk - RIFF file +//----------------------------------------------------------------------------- +void CAudioSourceMemWave::ParseDataChunk( IterateRIFF &walk ) +{ + int size = walk.ChunkSize(); + + // create a buffer for the samples + m_pData = new char[size]; + + // load them into memory + walk.ChunkRead( m_pData ); + + if ( m_format == WAVE_FORMAT_PCM ) + { + // number of samples loaded + m_sampleCount = size / m_sampleSize; + + // some samples need to be converted + ConvertSamples( m_pData, m_sampleCount ); + } + else if ( m_format == WAVE_FORMAT_ADPCM ) + { + // The ADPCM mixers treat the wave source as a flat file of bytes. + m_sampleSize = 1; + // Since each "sample" is a byte (this is a flat file), the number of samples is the file size + m_sampleCount = size; + + // file says 4, output is 16 + m_bits = 16; + } +} + +int CAudioSourceMemWave::GetNumChannels() +{ + return m_channels; +} + + + +//----------------------------------------------------------------------------- +// Purpose: parses loop information from a cue chunk +// Input : &walk - RIFF iterator +// Output : int loop start position +//----------------------------------------------------------------------------- +int CAudioSourceWave::ParseCueChunk( IterateRIFF &walk ) +{ + // Cue chunk as specified by RIFF format + // see $/research/jay/sound/riffnew.htm + struct + { + unsigned int dwName; + unsigned int dwPosition; + unsigned int fccChunk; + unsigned int dwChunkStart; + unsigned int dwBlockStart; + unsigned int dwSampleOffset; + } cue_chunk; + + int cueCount; + + // assume that the cue chunk stored in the wave is the start of the loop + // assume only one cue chunk, UNDONE: Test this assumption here? + cueCount = walk.ChunkReadInt(); + + walk.ChunkReadPartial( &cue_chunk, sizeof(cue_chunk) ); + return cue_chunk.dwSampleOffset; +} + +//----------------------------------------------------------------------------- +// Purpose: get the wave header +//----------------------------------------------------------------------------- +void *CAudioSourceWave::GetHeader( void ) +{ + return m_pHeader; +} + + +//----------------------------------------------------------------------------- +// Purpose: wrap the position wrt looping +// Input : samplePosition - absolute position +// Output : int - looped position +//----------------------------------------------------------------------------- +int CAudioSourceWave::ConvertLoopedPosition( int samplePosition ) +{ + // if the wave is looping and we're past the end of the sample + // convert to a position within the loop + // At the end of the loop, we return a short buffer, and subsequent call + // will loop back and get the rest of the buffer + if ( m_loopStart >= 0 ) + { + if ( samplePosition >= m_sampleCount ) + { + // size of loop + int loopSize = m_sampleCount - m_loopStart; + // subtract off starting bit of the wave + samplePosition -= m_loopStart; + + if ( loopSize ) + { + // "real" position in memory (mod off extra loops) + samplePosition = m_loopStart + (samplePosition % loopSize); + } + // ERROR? if no loopSize + } + } + + return samplePosition; +} + +bool CAudioSourceWave::IsStereoWav( void ) +{ + return (m_channels == 2) ? true : false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : **pData - output pointer to samples +// samplePosition - position (in samples not bytes) +// sampleCount - number of samples (not bytes) +// Output : int - number of samples available +//----------------------------------------------------------------------------- +int CAudioSourceMemWave::GetOutputData( void **pData, int samplePosition, int sampleCount, bool forward /*= true*/ ) +{ + // handle position looping + samplePosition = ConvertLoopedPosition( samplePosition ); + + // how many samples are available (linearly not counting looping) + int availableSampleCount = m_sampleCount - samplePosition; + if ( !forward ) + { + if ( samplePosition >= m_sampleCount ) + { + availableSampleCount = 0; + } + else + { + availableSampleCount = samplePosition; + } + } + + // may be asking for a sample out of range, clip at zero + if ( availableSampleCount < 0 ) + availableSampleCount = 0; + + // clip max output samples to max available + if ( sampleCount > availableSampleCount ) + sampleCount = availableSampleCount; + + // byte offset in sample database + samplePosition *= m_sampleSize; + + // if we are returning some samples, store the pointer + if ( sampleCount ) + { + *pData = m_pData + samplePosition; + } + + return sampleCount; +} + +//----------------------------------------------------------------------------- +// Purpose: Create a wave audio source (streaming or in memory) +// Input : *pName - file name +// streaming - if true, don't load, stream each instance +// Output : CAudioSource * - a new source +//----------------------------------------------------------------------------- +// UNDONE : Pool these and check for duplicates? +CAudioSource *CreateWave( const char *pName ) +{ + char formatBuffer[1024]; + InFileRIFF riff( pName, io ); + + // UNDONE: Don't use printf to handle errors + if ( riff.RIFFName() != RIFF_WAVE ) + { + printf("Bad RIFF file type %s\n", pName ); + return NULL; + } + + // set up the iterator for the whole file (root RIFF is a chunk) + IterateRIFF walk( riff, riff.RIFFSize() ); + + int format = 0; + int formatSize = 0; + + // This chunk must be first as it contains the wave's format + // break out when we've parsed it + while ( walk.ChunkAvailable() && format == 0 ) + { + switch( walk.ChunkName() ) + { + case WAVE_FMT: + { + if ( walk.ChunkSize() <= 1024 ) + { + walk.ChunkRead( formatBuffer ); + formatSize = walk.ChunkSize(); + format = ((WAVEFORMATEX *)formatBuffer)->wFormatTag; + } + } + break; + default: + { + ChunkError( walk.ChunkName() ); + } + break; + } + walk.ChunkNext(); + } + + // Not really a WAVE file or no format chunk, bail + if ( !format ) + return NULL; + + CAudioSourceWave *pWave; + + // create the source from this file + pWave = new CAudioSourceMemWave(); + + // init the wave source + pWave->Setup( formatBuffer, formatSize, walk ); + + return pWave; +} + +//----------------------------------------------------------------------------- +// Purpose: Wrapper for CreateWave() +//----------------------------------------------------------------------------- +CAudioSource *Audio_CreateMemoryWave( const char *pName ) +{ + return CreateWave( pName ); +} diff --git a/utils/hlfaceposer/snd_wave_source.h b/utils/hlfaceposer/snd_wave_source.h new file mode 100644 index 0000000..f403203 --- /dev/null +++ b/utils/hlfaceposer/snd_wave_source.h @@ -0,0 +1,78 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef SND_WAVE_SOURCE_H +#define SND_WAVE_SOURCE_H +#pragma once + +#include "snd_audio_source.h" +#include "sentence.h" + +class IterateRIFF; + +class CAudioSourceWave : public CAudioSource +{ +public: + CAudioSourceWave( void ); + ~CAudioSourceWave( void ); + + void Setup( const char *pFormat, int formatSize, IterateRIFF &walk ); + + virtual int SampleRate( void ) { return m_rate; } + inline int SampleSize( void ) { return m_sampleSize; } + virtual float TrueSampleSize( void ); + + void *GetHeader( void ); + + // Legacy + virtual void ParseChunk( IterateRIFF &walk, int chunkName ); + + virtual void ParseSentence( IterateRIFF &walk ); + + void ConvertSamples( char *pData, int sampleCount ); + bool IsLooped( void ) { return (m_loopStart >= 0) ? true : false; } + bool IsStreaming( void ) { return false; } + bool IsStereoWav( void ); + int ConvertLoopedPosition( int samplePosition ); + + int SampleCount( void ); + + virtual float GetRunningLength( void ) + { + if ( m_rate > 0.0 ) + { + return (float)SampleCount() / m_rate; + } + return 0.0f; } + + CSentence *GetSentence( void ); + +protected: + // returns the loop start from a cue chunk + int ParseCueChunk( IterateRIFF &walk ); + void Init( const char *pHeaderBuffer, int headerSize ); + + int m_bits; + int m_rate; + int m_channels; + int m_format; + int m_sampleSize; + int m_loopStart; + int m_sampleCount; + +private: + char *m_pHeader; + CSentence m_Sentence; +}; + +#endif // SND_WAVE_SOURCE_H diff --git a/utils/hlfaceposer/sound.cpp b/utils/hlfaceposer/sound.cpp new file mode 100644 index 0000000..3d9f6e5 --- /dev/null +++ b/utils/hlfaceposer/sound.cpp @@ -0,0 +1,1800 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#pragma warning( disable : 4201 ) +#include <mmsystem.h> +#include <stdio.h> +#include <math.h> +#include "snd_audio_source.h" +#include "AudioWaveOutput.h" +#include "ifaceposersound.h" +#include "StudioModel.h" +#include "hlfaceposer.h" +#include "expressions.h" +#include "expclass.h" +#include "PhonemeConverter.h" +#include "utlvector.h" +#include "filesystem.h" +#include "sentence.h" +#include "faceposer_models.h" +#include "iclosecaptionmanager.h" +#include "phonemeeditor.h" +#include "wavebrowser.h" +#include "choreoscene.h" +#include "choreoview.h" +#include "KeyValues.h" + +extern ISoundEmitterSystemBase *soundemitter; + +typedef struct channel_s +{ + int leftvol; + int rightvol; + int rleftvol; + int rrightvol; + float pitch; +} channel_t; + +#define INPUT_BUFFER_COUNT 32 + +class CAudioWaveInput : public CAudioInput +{ +public: + CAudioWaveInput( void ); + ~CAudioWaveInput( void ); + + // Returns the current count of available samples + int SampleCount( void ); + + // returns the size of each sample in bytes + int SampleSize( void ) { return m_sampleSize; } + + // returns the sampling rate of the data + int SampleRate( void ) { return m_sampleRate; } + + // returns a pointer to the actual data + void *SampleData( void ); + + // release the available data (mark as done) + void SampleRelease( void ); + + // returns the mono/stereo status of this device (true if stereo) + bool IsStereo( void ) { return m_isStereo; } + + // begin sampling + void Start( void ); + + // stop sampling + void Stop( void ); + + void WaveMessage( HWAVEIN hdevice, UINT uMsg, DWORD dwParam1, DWORD dwParam2 ); + +private: + void OpenDevice( void ); + bool ValidDevice( void ) { return m_deviceId != 0xFFFFFFFF; } + void ClearDevice( void ) { m_deviceId = 0xFFFFFFFF; } + + // returns true if the new format is better + bool BetterFormat( DWORD dwNewFormat, DWORD dwOldFormat ); + + void InitReadyList( void ); + void AddToReadyList( WAVEHDR *pBuffer ); + void PopReadyList( void ); + + WAVEHDR *m_pReadyList; + + int m_sampleSize; + int m_sampleRate; + bool m_isStereo; + + UINT m_deviceId; + HWAVEIN m_deviceHandle; + + WAVEHDR *m_buffers[ INPUT_BUFFER_COUNT ]; +}; + +extern "C" void CALLBACK WaveData( HWAVEIN hwi, UINT uMsg, CAudioWaveInput *pAudio, DWORD dwParam1, DWORD dwParam2 ); + +CAudioWaveInput::CAudioWaveInput( void ) +{ + memset( m_buffers, 0, sizeof( m_buffers ) ); + int deviceCount = (int)waveInGetNumDevs(); + UINT deviceId = 0; + DWORD deviceFormat = 0; + + int i; + for ( i = 0; i < deviceCount; i++ ) + { + WAVEINCAPS waveCaps; + MMRESULT errorCode = waveInGetDevCaps( (UINT)i, &waveCaps, sizeof(waveCaps) ); + if ( errorCode == MMSYSERR_NOERROR ) + { + // valid device + if ( BetterFormat( waveCaps.dwFormats, deviceFormat ) ) + { + deviceId = i; + deviceFormat = waveCaps.dwFormats; + } + } + } + + if ( !deviceFormat ) + { + m_deviceId = 0xFFFFFFFF; + m_sampleSize = 0; + m_sampleRate = 0; + m_isStereo = false; + } + else + { + m_deviceId = deviceId; + m_sampleRate = 44100; + m_isStereo = false; + if ( deviceFormat & WAVE_FORMAT_4M16 ) + { + m_sampleSize = 2; + } + else if ( deviceFormat & WAVE_FORMAT_4M08 ) + { + m_sampleSize = 1; + } + else + { + // ERROR! + } + + OpenDevice(); + } + + InitReadyList(); +} + +CAudioWaveInput::~CAudioWaveInput( void ) +{ + if ( ValidDevice() ) + { + Stop(); + waveInReset( m_deviceHandle ); + waveInClose( m_deviceHandle ); + for ( int i = 0; i < INPUT_BUFFER_COUNT; i++ ) + { + if ( m_buffers[i] ) + { + waveInUnprepareHeader( m_deviceHandle, m_buffers[i], sizeof( *m_buffers[i] ) ); + delete[] m_buffers[i]->lpData; + delete m_buffers[i]; + } + m_buffers[i] = NULL; + } + ClearDevice(); + } +} + +void CALLBACK WaveData( HWAVEIN hwi, UINT uMsg, CAudioWaveInput *pAudio, DWORD dwParam1, DWORD dwParam2 ) +{ + if ( pAudio ) + { + pAudio->WaveMessage( hwi, uMsg, dwParam1, dwParam2 ); + } +} + +void CAudioWaveInput::WaveMessage( HWAVEIN hdevice, UINT uMsg, DWORD dwParam1, DWORD dwParam2 ) +{ + if ( hdevice != m_deviceHandle ) + return; + switch( uMsg ) + { + case WIM_DATA: + WAVEHDR *pHeader = (WAVEHDR *)dwParam1; + AddToReadyList( pHeader ); + break; + } +} + +void CAudioWaveInput::OpenDevice( void ) +{ + if ( !ValidDevice() ) + return; + + WAVEFORMATEX format; + + memset( &format, 0, sizeof(format) ); + format.nAvgBytesPerSec = m_sampleRate * m_sampleSize; + format.nChannels = 1; + format.wBitsPerSample = m_sampleSize * 8; + format.nSamplesPerSec = m_sampleRate; + format.wFormatTag = WAVE_FORMAT_PCM; + format.nBlockAlign = m_sampleSize; + + MMRESULT errorCode = waveInOpen( &m_deviceHandle, m_deviceId, &format, (DWORD)WaveData, (DWORD)this, CALLBACK_FUNCTION ); + if ( errorCode == MMSYSERR_NOERROR ) + { + // valid device opened + int bufferSize = m_sampleSize * m_sampleRate / INPUT_BUFFER_COUNT; // total of one second of data + + // allocate buffers + for ( int i = 0; i < INPUT_BUFFER_COUNT; i++ ) + { + m_buffers[i] = new WAVEHDR; + m_buffers[i]->lpData = new char[ bufferSize ]; + m_buffers[i]->dwBufferLength = bufferSize; + m_buffers[i]->dwUser = 0; + m_buffers[i]->dwFlags = 0; + + waveInPrepareHeader( m_deviceHandle, m_buffers[i], sizeof( *m_buffers[i] ) ); + waveInAddBuffer( m_deviceHandle, m_buffers[i], sizeof( *m_buffers[i] ) ); + } + } + else + { + ClearDevice(); + } +} + +void CAudioWaveInput::Start( void ) +{ + if ( !ValidDevice() ) + return; + + waveInStart( m_deviceHandle ); +} + +void CAudioWaveInput::Stop( void ) +{ + if ( !ValidDevice() ) + return; + + waveInStop( m_deviceHandle ); +} + +void CAudioWaveInput::InitReadyList( void ) +{ + m_pReadyList = NULL; +} + +void CAudioWaveInput::AddToReadyList( WAVEHDR *pBuffer ) +{ + WAVEHDR **pList = &m_pReadyList; + + waveInUnprepareHeader( m_deviceHandle, pBuffer, sizeof(*pBuffer) ); + // insert at the tail of the list + while ( *pList ) + { + pList = reinterpret_cast<WAVEHDR **>(&((*pList)->dwUser)); + } + pBuffer->dwUser = NULL; + *pList = pBuffer; +} + + +void CAudioWaveInput::PopReadyList( void ) +{ + if ( m_pReadyList ) + { + WAVEHDR *pBuffer = m_pReadyList; + m_pReadyList = reinterpret_cast<WAVEHDR *>(m_pReadyList->dwUser); + waveInPrepareHeader( m_deviceHandle, pBuffer, sizeof(*pBuffer) ); + waveInAddBuffer( m_deviceHandle, pBuffer, sizeof(*pBuffer) ); + } +} + + + +#define WAVE_FORMAT_STEREO (WAVE_FORMAT_1S08|WAVE_FORMAT_1S16|WAVE_FORMAT_2S08|WAVE_FORMAT_2S16|WAVE_FORMAT_4S08|WAVE_FORMAT_4S16) +#define WAVE_FORMATS_UNDERSTOOD (0xFFF) +#define WAVE_FORMAT_11K (WAVE_FORMAT_1M08|WAVE_FORMAT_1M16) +#define WAVE_FORMAT_22K (WAVE_FORMAT_2M08|WAVE_FORMAT_2M16) +#define WAVE_FORMAT_44K (WAVE_FORMAT_4M08|WAVE_FORMAT_4M16) + +static int HighestBit( DWORD dwFlags ) +{ + int i = 31; + while ( i ) + { + if ( dwFlags & (1<<i) ) + return i; + i--; + } + + return 0; +} + +bool CAudioWaveInput::BetterFormat( DWORD dwNewFormat, DWORD dwOldFormat ) +{ + dwNewFormat &= WAVE_FORMATS_UNDERSTOOD & (~WAVE_FORMAT_STEREO); + dwOldFormat &= WAVE_FORMATS_UNDERSTOOD & (~WAVE_FORMAT_STEREO); + + // our target format is 44.1KHz, mono, 16-bit + if ( HighestBit(dwOldFormat) >= HighestBit(dwNewFormat) ) + return false; + + return true; +} + + +int CAudioWaveInput::SampleCount( void ) +{ + if ( !ValidDevice() ) + return 0; + + if ( m_pReadyList ) + { + switch( SampleSize() ) + { + case 2: + return m_pReadyList->dwBytesRecorded >> 1; + case 1: + return m_pReadyList->dwBytesRecorded; + default: + break; + } + } + return 0; +} + +void *CAudioWaveInput::SampleData( void ) +{ + if ( !ValidDevice() ) + return NULL; + + if ( m_pReadyList ) + { + return m_pReadyList->lpData; + } + + return NULL; +} + + +// release the available data (mark as done) +void CAudioWaveInput::SampleRelease( void ) +{ + PopReadyList(); +} + + +// factory to create a suitable audio input for this system +CAudioInput *CAudioInput::Create( void ) +{ + // sound source is a singleton for now + static CAudioInput *pSource = NULL; + + if ( !pSource ) + { + pSource = new CAudioWaveInput; + } + + return pSource; +} + +void CAudioDeviceSWMix::Mix8Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, int rateScaleFix, int outCount, int timecompress, bool forward ) +{ + int sampleIndex = 0; + fixedint sampleFrac = inputOffset; + + int fixup = 0; + int fixupstep = 1; + + if ( !forward ) + { + fixup = outCount - 1; + fixupstep = -1; + } + + for ( int i = 0; i < outCount; i++, fixup += fixupstep ) + { + int dest = max( outputOffset + fixup, 0 ); + + m_paintbuffer[ dest ].left += pChannel->leftvol * pData[sampleIndex]; + m_paintbuffer[ dest ].right += pChannel->rightvol * pData[sampleIndex]; + sampleFrac += rateScaleFix; + sampleIndex += FIX_INTPART(sampleFrac); + sampleFrac = FIX_FRACPART(sampleFrac); + } +} + + +void CAudioDeviceSWMix::Mix8Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, int rateScaleFix, int outCount, int timecompress, bool forward ) +{ + int sampleIndex = 0; + fixedint sampleFrac = inputOffset; + + int fixup = 0; + int fixupstep = 1; + + if ( !forward ) + { + fixup = outCount - 1; + fixupstep = -1; + } + + for ( int i = 0; i < outCount; i++, fixup += fixupstep ) + { + int dest = max( outputOffset + fixup, 0 ); + + m_paintbuffer[ dest ].left += pChannel->leftvol * pData[sampleIndex]; + m_paintbuffer[ dest ].right += pChannel->rightvol * pData[sampleIndex+1]; + sampleFrac += rateScaleFix; + sampleIndex += FIX_INTPART(sampleFrac)<<1; + sampleFrac = FIX_FRACPART(sampleFrac); + } +} + + +void CAudioDeviceSWMix::Mix16Mono( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, int rateScaleFix, int outCount, int timecompress, bool forward ) +{ + int sampleIndex = 0; + fixedint sampleFrac = inputOffset; + + int fixup = 0; + int fixupstep = 1; + + if ( !forward ) + { + fixup = outCount - 1; + fixupstep = -1; + } + + for ( int i = 0; i < outCount; i++, fixup += fixupstep ) + { + int dest = max( outputOffset + fixup, 0 ); + + m_paintbuffer[ dest ].left += (pChannel->leftvol * pData[sampleIndex])>>8; + m_paintbuffer[ dest ].right += (pChannel->rightvol * pData[sampleIndex])>>8; + sampleFrac += rateScaleFix; + sampleIndex += FIX_INTPART(sampleFrac); + sampleFrac = FIX_FRACPART(sampleFrac); + } +} + + +void CAudioDeviceSWMix::Mix16Stereo( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, int rateScaleFix, int outCount, int timecompress, bool forward ) +{ + int sampleIndex = 0; + fixedint sampleFrac = inputOffset; + + int fixup = 0; + int fixupstep = 1; + + if ( !forward ) + { + fixup = outCount - 1; + fixupstep = -1; + } + + for ( int i = 0; i < outCount; i++, fixup += fixupstep ) + { + int dest = max( outputOffset + fixup, 0 ); + + m_paintbuffer[ dest ].left += (pChannel->leftvol * pData[sampleIndex])>>8; + m_paintbuffer[ dest ].right += (pChannel->rightvol * pData[sampleIndex+1])>>8; + + sampleFrac += rateScaleFix; + sampleIndex += FIX_INTPART(sampleFrac)<<1; + sampleFrac = FIX_FRACPART(sampleFrac); + } +} + + +int CAudioDeviceSWMix::MaxSampleCount( void ) +{ + return PAINTBUFFER_SIZE; +} + +void CAudioDeviceSWMix::MixBegin( void ) +{ + memset( m_paintbuffer, 0, sizeof(m_paintbuffer) ); +} + +void CAudioDeviceSWMix::TransferBufferStereo16( short *pOutput, int sampleCount ) +{ + for ( int i = 0; i < sampleCount; i++ ) + { + if ( m_paintbuffer[i].left > 32767 ) + m_paintbuffer[i].left = 32767; + else if ( m_paintbuffer[i].left < -32768 ) + m_paintbuffer[i].left = -32768; + + if ( m_paintbuffer[i].right > 32767 ) + m_paintbuffer[i].right = 32767; + else if ( m_paintbuffer[i].right < -32768 ) + m_paintbuffer[i].right = -32768; + + *pOutput++ = (short)m_paintbuffer[i].left; + *pOutput++ = (short)m_paintbuffer[i].right; + } +} + +CAudioWaveOutput::CAudioWaveOutput( void ) +{ + for ( int i = 0; i < OUTPUT_BUFFER_COUNT; i++ ) + { + CAudioBuffer *buffer = &m_buffers[ i ]; + Assert( buffer ); + buffer->hdr = NULL; + buffer->submitted = false; + buffer->submit_sample_count = false; + } + + ClearDevice(); + OpenDevice(); + + m_mixTime = -1; + m_sampleIndex = 0; + memset( m_sourceList, 0, sizeof(m_sourceList) ); + + m_nEstimatedSamplesAhead = (int)( ( float ) OUTPUT_SAMPLE_RATE / 10.0f ); +} + +void CAudioWaveOutput::RemoveMixerChannelReferences( CAudioMixer *mixer ) +{ + for ( int i = 0; i < OUTPUT_BUFFER_COUNT; i++ ) + { + RemoveFromReferencedList( mixer, &m_buffers[ i ] ); + } +} + +void CAudioWaveOutput::AddToReferencedList( CAudioMixer *mixer, CAudioBuffer *buffer ) +{ + // Already in list + for ( int i = 0; i < buffer->m_Referenced.Size(); i++ ) + { + if ( buffer->m_Referenced[ i ].mixer == mixer ) + { + return; + } + } + + // Just remove it + int idx = buffer->m_Referenced.AddToTail(); + + CAudioMixerState *state = &buffer->m_Referenced[ idx ]; + state->mixer = mixer; + state->submit_mixer_sample = mixer->GetSamplePosition(); + +} + +void CAudioWaveOutput::RemoveFromReferencedList( CAudioMixer *mixer, CAudioBuffer *buffer ) +{ + for ( int i = 0; i < buffer->m_Referenced.Size(); i++ ) + { + if ( buffer->m_Referenced[ i ].mixer == mixer ) + { + buffer->m_Referenced.Remove( i ); + break; + } + } +} + +bool CAudioWaveOutput::IsSoundInReferencedList( CAudioMixer *mixer, CAudioBuffer *buffer ) +{ + for ( int i = 0; i < buffer->m_Referenced.Size(); i++ ) + { + if ( buffer->m_Referenced[ i ].mixer == mixer ) + { + return true; + } + } + return false; +} + +bool CAudioWaveOutput::IsSourceReferencedByActiveBuffer( CAudioMixer *mixer ) +{ + if ( !ValidDevice() ) + return false; + + CAudioBuffer *buffer; + for ( int i = 0; i < OUTPUT_BUFFER_COUNT; i++ ) + { + buffer = &m_buffers[ i ]; + if ( !buffer->submitted ) + continue; + + if ( buffer->hdr->dwFlags & WHDR_DONE ) + continue; + + // See if it's referenced + if ( IsSoundInReferencedList( mixer, buffer ) ) + return true; + } + + return false; +} + +CAudioWaveOutput::~CAudioWaveOutput( void ) +{ + if ( ValidDevice() ) + { + waveOutReset( m_deviceHandle ); + for ( int i = 0; i < OUTPUT_BUFFER_COUNT; i++ ) + { + if ( m_buffers[i].hdr ) + { + waveOutUnprepareHeader( m_deviceHandle, m_buffers[i].hdr, sizeof(*m_buffers[i].hdr) ); + delete[] m_buffers[i].hdr->lpData; + delete m_buffers[i].hdr; + } + m_buffers[i].hdr = NULL; + m_buffers[i].submitted = false; + m_buffers[i].submit_sample_count = 0; + m_buffers[i].m_Referenced.Purge(); + } + waveOutClose( m_deviceHandle ); + ClearDevice(); + } +} + + + +CAudioBuffer *CAudioWaveOutput::GetEmptyBuffer( void ) +{ + CAudioBuffer *pOutput = NULL; + if ( ValidDevice() ) + { + for ( int i = 0; i < OUTPUT_BUFFER_COUNT; i++ ) + { + if ( !(m_buffers[ i ].submitted ) || + m_buffers[i].hdr->dwFlags & WHDR_DONE ) + { + pOutput = &m_buffers[i]; + pOutput->submitted = true; + pOutput->m_Referenced.Purge(); + break; + } + } + } + + return pOutput; +} + +void CAudioWaveOutput::SilenceBuffer( short *pSamples, int sampleCount ) +{ + int i; + + for ( i = 0; i < sampleCount; i++ ) + { + // left + *pSamples++ = 0; + // right + *pSamples++ = 0; + } +} + +void CAudioWaveOutput::Flush( void ) +{ + waveOutReset( m_deviceHandle ); +} + +// mix a buffer up to time (time is absolute) +void CAudioWaveOutput::Update( float time ) +{ + if ( !ValidDevice() ) + return; + + // reset the system + if ( m_mixTime < 0 || time < m_baseTime ) + { + m_baseTime = time; + m_mixTime = 0; + } + + // put time in our coordinate frame + time -= m_baseTime; + + if ( time > m_mixTime ) + { + CAudioBuffer *pBuffer = GetEmptyBuffer(); + + // no free buffers, mixing is ahead of the playback! + if ( !pBuffer || !pBuffer->hdr ) + { + //Con_Printf( "out of buffers\n" ); + return; + } + + // UNDONE: These numbers are constants + // calc number of samples (2 channels * 2 bytes per sample) + int sampleCount = pBuffer->hdr->dwBufferLength >> 2; + m_mixTime += sampleCount * (1.0f / OUTPUT_SAMPLE_RATE); + + short *pSamples = reinterpret_cast<short *>(pBuffer->hdr->lpData); + + SilenceBuffer( pSamples, sampleCount ); + + int tempCount = sampleCount; + + while ( tempCount > 0 ) + { + if ( tempCount > m_audioDevice.MaxSampleCount() ) + sampleCount = m_audioDevice.MaxSampleCount(); + else + sampleCount = tempCount; + + m_audioDevice.MixBegin(); + for ( int i = 0; i < MAX_CHANNELS; i++ ) + { + CAudioMixer *pSource = m_sourceList[i]; + if ( !pSource ) + continue; + + StudioModel *model = NULL; + + int modelindex = pSource->GetModelIndex(); + if ( modelindex >= 0 ) + { + model = models->GetStudioModel( modelindex ); + } + else + { + if ( g_pPhonemeEditor->IsActiveTool() || g_pWaveBrowser->IsActiveTool() ) + { + model = models->GetActiveStudioModel(); + + } + } + + if ( model && !model->m_mouth.IsSourceReferenced( pSource->GetSource() ) ) + { + CChoreoScene *pScene = g_pChoreoView->GetScene(); + bool bIgnorePhonemes = pScene ? pScene->ShouldIgnorePhonemes() : false; + model->m_mouth.AddSource( pSource->GetSource(), bIgnorePhonemes ); + if ( modelindex < 0 ) + { + pSource->SetModelIndex( models->GetIndexForStudioModel( model ) ); + } + } + + int currentsample = pSource->GetSamplePosition(); + bool forward = pSource->GetDirection(); + + if ( pSource->GetActive() ) + { + if ( !pSource->MixDataToDevice( &m_audioDevice, pSource->GetChannel(), currentsample, sampleCount, SampleRate(), forward ) ) + { + // Source becomes inactive when last submitted sample is finally + // submitted. But it lingers until it's no longer referenced + pSource->SetActive( false ); + } + else + { + AddToReferencedList( pSource, pBuffer ); + } + } + else + { + if ( !IsSourceReferencedByActiveBuffer( pSource ) ) + { + if ( !pSource->GetAutoDelete() ) + { + FreeChannel( i ); + } + } + else + { + pSource->IncrementSamples( pSource->GetChannel(), currentsample, sampleCount, SampleRate(), forward ); + } + } + + } + + m_audioDevice.TransferBufferStereo16( pSamples, sampleCount ); + + m_sampleIndex += sampleCount; + tempCount -= sampleCount; + pSamples += sampleCount * 2; + } + // if the buffers aren't aligned on sample boundaries, this will hard-lock the machine! + + pBuffer->submit_sample_count = GetOutputPosition(); + + waveOutWrite( m_deviceHandle, pBuffer->hdr, sizeof(*(pBuffer->hdr)) ); + } +} + +int CAudioWaveOutput::GetNumberofSamplesAhead( void ) +{ + ComputeSampleAheadAmount(); + return m_nEstimatedSamplesAhead; +} + +float CAudioWaveOutput::GetAmountofTimeAhead( void ) +{ + ComputeSampleAheadAmount(); + return ( (float)m_nEstimatedSamplesAhead / (float)OUTPUT_SAMPLE_RATE ); +} + +// Find the most recent submitted sample that isn't flagged as whdr_done +void CAudioWaveOutput::ComputeSampleAheadAmount( void ) +{ + m_nEstimatedSamplesAhead = 0; + + int newest_sample_index = -1; + int newest_sample_count = 0; + + CAudioBuffer *buffer; + + if ( ValidDevice() ) + { + + for ( int i = 0; i < OUTPUT_BUFFER_COUNT; i++ ) + { + buffer = &m_buffers[ i ]; + if ( !buffer->submitted ) + continue; + + if ( buffer->hdr->dwFlags & WHDR_DONE ) + continue; + + if ( buffer->submit_sample_count > newest_sample_count ) + { + newest_sample_index = i; + newest_sample_count = buffer->submit_sample_count; + } + } + } + + if ( newest_sample_index == -1 ) + return; + + + buffer = &m_buffers[ newest_sample_index ]; + int currentPos = GetOutputPosition() ; + m_nEstimatedSamplesAhead = currentPos - buffer->submit_sample_count; +} + +int CAudioWaveOutput::FindSourceIndex( CAudioMixer *pSource ) +{ + for ( int i = 0; i < MAX_CHANNELS; i++ ) + { + if ( pSource == m_sourceList[i] ) + { + return i; + } + } + return -1; +} + +CAudioMixer *CAudioWaveOutput::GetMixerForSource( CAudioSource *source ) +{ + for ( int i = 0; i < MAX_CHANNELS; i++ ) + { + if ( !m_sourceList[i] ) + continue; + + if ( source == m_sourceList[i]->GetSource() ) + { + return m_sourceList[i]; + } + } + return NULL; +} + +void CAudioWaveOutput::AddSource( CAudioMixer *pSource ) +{ + int slot = 0; + for ( int i = 0; i < MAX_CHANNELS; i++ ) + { + if ( !m_sourceList[i] ) + { + slot = i; + break; + } + } + + if ( m_sourceList[slot] ) + { + FreeChannel( slot ); + } + SetChannel( slot, pSource ); + + pSource->SetActive( true ); +} + + +void CAudioWaveOutput::StopSounds( void ) +{ + for ( int i = 0; i < MAX_CHANNELS; i++ ) + { + if ( m_sourceList[i] ) + { + FreeChannel( i ); + } + } +} + + +void CAudioWaveOutput::SetChannel( int channelIndex, CAudioMixer *pSource ) +{ + if ( channelIndex < 0 || channelIndex >= MAX_CHANNELS ) + return; + + m_sourceList[channelIndex] = pSource; +} + +void CAudioWaveOutput::FreeChannel( int channelIndex ) +{ + if ( channelIndex < 0 || channelIndex >= MAX_CHANNELS ) + return; + + if ( m_sourceList[channelIndex] ) + { + StudioModel *model = NULL; + int modelindex = m_sourceList[channelIndex]->GetModelIndex(); + if ( modelindex >= 0) + { + model = models->GetStudioModel( modelindex ); + } + + if ( model ) + { + model->m_mouth.RemoveSource( m_sourceList[channelIndex]->GetSource() ); + } + + RemoveMixerChannelReferences( m_sourceList[channelIndex] ); + + delete m_sourceList[channelIndex]; + m_sourceList[channelIndex] = NULL; + } +} + +int CAudioWaveOutput::GetOutputPosition( void ) +{ + if ( !m_deviceHandle ) + return 0; + + MMTIME mmtime; + mmtime.wType = TIME_SAMPLES; + waveOutGetPosition( m_deviceHandle, &mmtime, sizeof( MMTIME ) ); + + // Convert time to sample count + return ( mmtime.u.sample ); +} + +void CAudioWaveOutput::OpenDevice( void ) +{ + WAVEFORMATEX waveFormat; + + memset( &waveFormat, 0, sizeof(waveFormat) ); + // Select a PCM, 16-bit stereo playback device + waveFormat.cbSize = sizeof(waveFormat); + waveFormat.nAvgBytesPerSec = OUTPUT_SAMPLE_RATE * 2 * 2; + waveFormat.nBlockAlign = 2 * 2; // channels * sample size + waveFormat.nChannels = 2; // stereo + waveFormat.nSamplesPerSec = OUTPUT_SAMPLE_RATE; + waveFormat.wBitsPerSample = 16; + waveFormat.wFormatTag = WAVE_FORMAT_PCM; + + MMRESULT errorCode = waveOutOpen( &m_deviceHandle, WAVE_MAPPER, &waveFormat, 0, 0, CALLBACK_NULL ); + if ( errorCode == MMSYSERR_NOERROR ) + { + int bufferSize = 4 * ( OUTPUT_SAMPLE_RATE / OUTPUT_BUFFER_COUNT ); // total of 1 second of data + + // Got one! + for ( int i = 0; i < OUTPUT_BUFFER_COUNT; i++ ) + { + m_buffers[i].hdr = new WAVEHDR; + m_buffers[i].hdr->lpData = new char[ bufferSize ]; + long align = (long)m_buffers[i].hdr->lpData; + if ( align & 3 ) + { + m_buffers[i].hdr->lpData = (char *) ( (align+3) &~3 ); + } + m_buffers[i].hdr->dwBufferLength = bufferSize - (align&3); + m_buffers[i].hdr->dwFlags = 0; + + if ( waveOutPrepareHeader( m_deviceHandle, m_buffers[i].hdr, sizeof(*m_buffers[i].hdr) ) != MMSYSERR_NOERROR ) + { + ClearDevice(); + return; + } + } + } + else + { + ClearDevice(); + } +} + +// factory to create a suitable audio output for this system +CAudioOutput *CAudioOutput::Create( void ) +{ + // sound device is a singleton for now + static CAudioOutput *pWaveOut = NULL; + + if ( !pWaveOut ) + { + pWaveOut = new CAudioWaveOutput; + } + + return pWaveOut; +} + +struct CSoundFile +{ + char filename[ 512 ]; + CAudioSource *source; + long filetime; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CFacePoserSound : public IFacePoserSound +{ +public: + ~CFacePoserSound( void ); + + void Init( void ); + void Shutdown( void ); + void Update( float dt ); + void Flush( void ); + + CAudioSource *LoadSound( const char *wavfile ); + void PlaySound( StudioModel *source, float volume, const char *wavfile, CAudioMixer **ppMixer ); + void PlaySound( CAudioSource *source, float volume, CAudioMixer **ppMixer ); + void PlayPartialSound( StudioModel *model, float volume, const char *wavfile, CAudioMixer **ppMixer, int startSample, int endSample ); + + bool IsSoundPlaying( CAudioMixer *pMixer ); + CAudioMixer *FindMixer( CAudioSource *source ); + + void StopAll( void ); + void StopSound( CAudioMixer *mixer ); + + void RenderWavToDC( HDC dc, RECT& outrect, COLORREF clr, float starttime, float endtime, + CAudioSource *pWave, bool selected = false, int selectionstart = 0, int selectionend = 0 ); + + // void InstallPhonemecallback( IPhonemeTag *pTagInterface ); + float GetAmountofTimeAhead( void ); + + int GetNumberofSamplesAhead( void ); + + CAudioOuput *GetAudioOutput( void ); + + virtual void EnsureNoModelReferences( CAudioSource *source ); + +private: + void AddViseme( float intensity, StudioModel *model, int phoneme, float scale ); + void ProcessCloseCaptionData( StudioModel *model, float curtime, CSentence* sentence ); + void SetupWeights( void ); + + CAudioSource *FindOrAddSound( const char *filename ); + + CAudioOutput *m_pAudio; + + float m_flElapsedTime; + + CUtlVector < CSoundFile > m_ActiveSounds; +}; + +static CFacePoserSound g_FacePoserSound; + +IFacePoserSound *sound = ( IFacePoserSound * )&g_FacePoserSound; + +CFacePoserSound::~CFacePoserSound( void ) +{ + OutputDebugString( va( "Removing %i sounds\n", m_ActiveSounds.Size() ) ); + for ( int i = 0 ; i < m_ActiveSounds.Size(); i++ ) + { + CSoundFile *p = &m_ActiveSounds[ i ]; + OutputDebugString( va( "Removing sound: %s\n", p->filename ) ); + delete p->source; + } + + m_ActiveSounds.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CAudioOuput *CFacePoserSound::GetAudioOutput( void ) +{ + return (CAudioOuput *)m_pAudio; +} + +CAudioSource *CFacePoserSound::FindOrAddSound( const char *filename ) +{ + CSoundFile *s; + + int i; + for ( i = 0; i < m_ActiveSounds.Size(); i++ ) + { + s = &m_ActiveSounds[ i ]; + Assert( s ); + if ( !stricmp( s->filename, filename ) ) + { + long filetime = filesystem->GetFileTime( filename ); + if ( filetime != s->filetime ) + { + Con_Printf( "Reloading sound %s\n", filename ); + delete s->source; + s->source = LoadSound( filename ); + s->filetime = filetime; + } + return s->source; + } + } + + i = m_ActiveSounds.AddToTail(); + s = &m_ActiveSounds[ i ]; + strcpy( s->filename, filename ); + s->source = LoadSound( filename ); + s->filetime = filesystem->GetFileTime( filename ); + + return s->source; +} + +void CFacePoserSound::Init( void ) +{ + m_flElapsedTime = 0.0f; + m_pAudio = CAudioOutput::Create(); + + // Load SoundOverrides for Faceposer + + KeyValues *manifest = new KeyValues( "scripts/game_sounds_manifest.txt" ); + if ( filesystem->LoadKeyValues( *manifest, IFileSystem::TYPE_SOUNDEMITTER, "scripts/game_sounds_manifest.txt", "GAME" ) ) + { + for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() ) + { + if ( !Q_stricmp( sub->GetName(), "faceposer_file" ) ) + { + soundemitter->AddSoundOverrides( sub->GetString() ); + continue; + } + } + } + manifest->deleteThis(); +} + +void CFacePoserSound::Shutdown( void ) +{ +} + +float CFacePoserSound::GetAmountofTimeAhead( void ) +{ + if ( !m_pAudio ) + return 0.0f; + + return m_pAudio->GetAmountofTimeAhead(); +} + +int CFacePoserSound::GetNumberofSamplesAhead( void ) +{ + if ( !m_pAudio ) + return 0; + + return m_pAudio->GetNumberofSamplesAhead(); +} + + +CAudioSource *CFacePoserSound::LoadSound( const char *wavfile ) +{ + if ( !m_pAudio ) + return NULL; + + CAudioSource *wave = AudioSource_Create( wavfile ); + return wave; +} + +void CFacePoserSound::PlaySound( StudioModel *model, float volume, const char *wavfile, CAudioMixer **ppMixer ) +{ + if ( m_pAudio ) + { + CAudioSource *wave = FindOrAddSound( wavfile ); + if ( !wave ) + return; + + CAudioMixer *pMixer = wave->CreateMixer(); + if ( ppMixer ) + { + *ppMixer = pMixer; + } + pMixer->SetVolume( volume ); + m_pAudio->AddSource( pMixer ); + if ( model ) + { + pMixer->SetModelIndex( models->GetIndexForStudioModel( model ) ); + } + } +} + +void CFacePoserSound::PlayPartialSound( StudioModel *model, float volume, const char *wavfile, CAudioMixer **ppMixer, int startSample, int endSample ) +{ + if ( !m_pAudio ) + return; + + StopAll(); + CAudioSource *wave = FindOrAddSound( wavfile ); + if ( !wave ) + return; + + CAudioMixer *mixer = wave->CreateMixer(); + if ( ppMixer ) + { + *ppMixer = mixer; + } + + mixer->SetSamplePosition( startSample ); + mixer->SetLoopPosition( endSample ); + mixer->SetVolume( volume ); + m_pAudio->AddSource( mixer ); +} + +void CFacePoserSound::PlaySound( CAudioSource *source, float volume, CAudioMixer **ppMixer ) +{ + if ( ppMixer ) + { + *ppMixer = NULL; + } + + if ( m_pAudio ) + { + CAudioMixer *mixer = source->CreateMixer(); + if ( ppMixer ) + { + *ppMixer = mixer; + } + mixer->SetVolume( volume ); + m_pAudio->AddSource( mixer ); + } +} + +enum +{ + PHONEME_CLASS_WEAK = 0, + PHONEME_CLASS_NORMAL, + PHONEME_CLASS_STRONG, + + NUM_PHONEME_CLASSES +}; + +struct Emphasized_Phoneme +{ + char *classname; + bool required; + bool valid; + CExpClass *cl; + CExpression *exp; + float *settings; + float amount; +}; + +static Emphasized_Phoneme g_PhonemeClasses[ NUM_PHONEME_CLASSES ] = +{ + { "phonemes_weak", false }, + { "phonemes", true }, + { "phonemes_strong", false }, +}; + +#define STRONG_CROSSFADE_START 0.60f +#define WEAK_CROSSFADE_START 0.40f + +void ComputeBlendedSetting( Emphasized_Phoneme *classes, float emphasis_intensity ) +{ + // Here's the formula + // 0.5 is neutral 100 % of the default setting + + // Crossfade starts at STRONG_CROSSFADE_START and is full at STRONG_CROSSFADE_END + // If there isn't a strong then the intensity of the underlying phoneme is fixed at 2 x STRONG_CROSSFADE_START + // so we don't get huge numbers + + bool has_weak = classes[ PHONEME_CLASS_WEAK ].valid; + bool has_strong = classes[ PHONEME_CLASS_STRONG ].valid; + + Assert( classes[ PHONEME_CLASS_NORMAL ].valid ); + + if ( emphasis_intensity > STRONG_CROSSFADE_START ) + { + if ( has_strong ) + { + // Blend in some of strong + float dist_remaining = 1.0f - emphasis_intensity; + float frac = dist_remaining / ( 1.0f - STRONG_CROSSFADE_START ); + + classes[ PHONEME_CLASS_NORMAL ].amount = (frac) * 2.0f * STRONG_CROSSFADE_START; + classes[ PHONEME_CLASS_STRONG ].amount = 1.0f - frac; + } + else + { + emphasis_intensity = min( emphasis_intensity, STRONG_CROSSFADE_START ); + classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity; + } + } + else if ( emphasis_intensity < WEAK_CROSSFADE_START ) + { + if ( has_weak ) + { + // Blend in some weak + float dist_remaining = WEAK_CROSSFADE_START - emphasis_intensity; + float frac = dist_remaining / ( WEAK_CROSSFADE_START ); + + classes[ PHONEME_CLASS_NORMAL ].amount = (1.0f - frac) * 2.0f * WEAK_CROSSFADE_START; + classes[ PHONEME_CLASS_WEAK ].amount = frac; + } + else + { + emphasis_intensity = max( emphasis_intensity, WEAK_CROSSFADE_START ); + classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity; + } + } + else + { + classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity; + } +} + +void CFacePoserSound::AddViseme( float intensity, StudioModel *model, int phoneme, float scale ) +{ + int i; + + Assert( model ); + CStudioHdr *hdr = model->GetStudioHdr(); + Assert( hdr ); + if ( !hdr ) + return; + + for ( i = 0; i < NUM_PHONEME_CLASSES; i++ ) + { + Emphasized_Phoneme *info = &g_PhonemeClasses[ i ]; + + info->valid = false; + info->exp = NULL; + info->settings = NULL; + info->amount = 0.0f; + + info->cl = expressions->FindClass( info->classname, true ); + if ( info->cl ) + { + info->exp = info->cl->FindExpression( ConvertPhoneme( phoneme ) ); + } + + if ( info->required && ( !info->cl || !info->exp ) ) + { + return; + } + + if ( info->exp ) + { + info->valid = true; + + info->settings = info->exp->GetSettings(); + Assert( info->settings ); + } + } + + ComputeBlendedSetting( g_PhonemeClasses, intensity ); + + // Look up the phoneme + for ( LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++) + { + int j = hdr->pFlexcontroller( i )->localToGlobal; + + float add = 0.0f; + + for ( int k = 0 ; k < NUM_PHONEME_CLASSES; k++ ) + { + Emphasized_Phoneme *info = &g_PhonemeClasses[ k ]; + if ( !info->valid || !info->amount ) + continue; + + add += info->amount * info->settings[ j ]; + } + + if ( add == 0.0f ) + continue; + + float curvalue = model->GetFlexController( i ); + curvalue += add * scale; + model->SetFlexController( i, curvalue ); + } +} + + +#define PHONEME_FILTER 0.08f +#define PHONEME_DELAY 0.0f + +void CFacePoserSound::SetupWeights( void ) +{ + StudioModel *model; + int c = models->Count(); + for ( int i = 0; i < c; i++ ) + { + model = models->GetStudioModel( i ); + if ( !model ) + continue; + + // Reset flexes + CStudioHdr *hdr = model->GetStudioHdr(); + if ( !hdr ) + continue; + + for ( int s = 0; s < model->m_mouth.GetNumVoiceSources(); s++ ) + { + CVoiceData *vd = model->m_mouth.GetVoiceSource( s ); + if ( !vd || vd->ShouldIgnorePhonemes() ) + continue; + + CAudioSource *source = vd->GetSource(); + // check for phoneme flexes + if ( !source ) + continue; + + CAudioMixer *mixer = FindMixer( source ); + if ( !mixer ) + continue; + + CSentence *sentence = source->GetSentence(); + if ( !sentence ) + continue; + + // Zero faces if needed + models->CheckResetFlexes(); + + float pos = (float)mixer->GetScrubPosition(); + + // Con_Printf( "pos %f for mixer %p\n", pos, mixer ); + + float soundtime = pos / source->SampleRate(); + + float t = soundtime - PHONEME_DELAY; + float dt = PHONEME_FILTER; + + float sentence_duration = source->GetRunningLength(); + float emphasis_intensity = sentence->GetIntensity( t, sentence_duration ); + + if ( t > 0.0f ) + { + for ( int w = 0 ; w < sentence->m_Words.Size(); w++ ) + { + CWordTag *word = sentence->m_Words[ w ]; + if ( !word ) + continue; + + for ( int k = 0; k < word->m_Phonemes.Size(); k++) + { + CPhonemeTag *phoneme = word->m_Phonemes[ k ]; + if ( !phoneme ) + continue; + + // if the filter starts within this phoneme, make sure the filter size is + // at least least as long as the current phoneme, or until the end of the next phoneme, + // whichever is smaller + if (t > phoneme->GetStartTime() && t < phoneme->GetEndTime()) + { + CPhonemeTag *next = NULL; + // try next phoneme, or first phoneme of next word + if (k < word->m_Phonemes.Size()-1) + { + next = word->m_Phonemes[ k+1 ]; + } + else if ( w < sentence->m_Words.Size() - 1 && sentence->m_Words[ w+1 ]->m_Phonemes.Size() ) + { + next = sentence->m_Words[ w+1 ]->m_Phonemes[ 0 ]; + } + + // if I have a neighbor + if (next) + { + // and they're touching + if (next->GetStartTime() == phoneme->GetEndTime()) + { + // no gap, so increase the blend length to the end of the next phoneme, as long as it's not longer than the current phoneme + dt = max( dt, min( next->GetEndTime() - t, phoneme->GetEndTime() - phoneme->GetStartTime() ) ); + } + else + { + // dead space, so increase the blend length to the start of the next phoneme, as long as it's not longer than the current phoneme + dt = max( dt, min( next->GetStartTime() - t, phoneme->GetEndTime() - phoneme->GetStartTime() ) ); + } + } + else + { + // last phoneme in list, increase the blend length to the length of the current phoneme + dt = max( dt, phoneme->GetEndTime() - phoneme->GetStartTime() ); + } + } + + float t1 = ( phoneme->GetStartTime() - t) / dt; + float t2 = ( phoneme->GetEndTime() - t) / dt; + + if (t1 < 1.0 && t2 > 0) + { + float scale; + + // clamp + if (t2 > 1) + t2 = 1; + if (t1 < 0) + t1 = 0; + + // FIXME: simple box filter. Should use something fancier + scale = (t2 - t1); + + AddViseme( emphasis_intensity, model, phoneme->GetPhonemeCode(), scale ); + } + } + } + ProcessCloseCaptionData( model, t, sentence ); + } + } + } +} + +static int g_nSoundFrameCount = 0; + +void CFacePoserSound::ProcessCloseCaptionData( StudioModel *model, float curtime, CSentence* sentence ) +{ +// closecaptionmanager->Process( g_nSoundFrameCount, model, curtime, sentence, GetCloseCaptionLanguageId() ); +} + +void CFacePoserSound::Update( float dt ) +{ +// closecaptionmanager->PreProcess( g_nSoundFrameCount ); + + if ( m_pAudio ) + { + SetupWeights(); + m_pAudio->Update( m_flElapsedTime ); + } + +// closecaptionmanager->PostProcess( g_nSoundFrameCount, dt ); + + m_flElapsedTime += dt; + g_nSoundFrameCount++; +} + +void CFacePoserSound::Flush( void ) +{ + if ( m_pAudio ) + { + m_pAudio->Flush(); + } +} + +void CFacePoserSound::StopAll( void ) +{ + int c = models->Count(); + for ( int i = 0; i < c; i++ ) + { + StudioModel *model = models->GetStudioModel( i ); + if ( model ) + { + model->m_mouth.ClearVoiceSources(); + } + } + + if ( m_pAudio ) + { + m_pAudio->StopSounds(); + } +} + +void CFacePoserSound::StopSound( CAudioMixer *mixer ) +{ + int idx = m_pAudio->FindSourceIndex( mixer ); + if ( idx != -1 ) + { + m_pAudio->FreeChannel( idx ); + } +} + +void CFacePoserSound::RenderWavToDC( HDC dc, RECT& outrect, COLORREF clr, + float starttime, float endtime, CAudioSource *pWave, + bool selected /*= false*/, int selectionstart /*= 0*/, int selectionend /*= 0*/ ) +{ + channel_t channel; + + channel.leftvol = 127; + channel.rightvol = 127; + channel.pitch = 1.0; + + if ( !pWave ) + return; + + CAudioWaveOutput *pWaveOutput = ( CAudioWaveOutput * )m_pAudio; + + CAudioMixer *pMixer = pWave->CreateMixer(); + + float timeperpixel = ( endtime - starttime ) / (float)( outrect.right - outrect.left ); + + float samplesperpixel = timeperpixel * pWave->SampleRate(); + + samplesperpixel = min( samplesperpixel, (float)PAINTBUFFER_SIZE ); + + int intsamplesperpixel = (int)samplesperpixel; + + // Determine start/stop positions + int totalsamples = (int)( pWave->GetRunningLength() * pWave->SampleRate() ); + + if ( totalsamples <= 0 ) + return; + + float selectionstarttime = pWave->GetRunningLength() * ( float )selectionstart / ( float )totalsamples; + float selectionendtime = pWave->GetRunningLength() * ( float )selectionend / ( float )totalsamples; + + + HPEN oldPen, pen, pen2, pen3, pen4; + + pen = CreatePen( PS_SOLID, 1, RGB( 175, 175, 250 ) ); + pen2 = CreatePen( PS_SOLID, 1, clr ); + pen3 = CreatePen( PS_SOLID, 1, RGB( 127, 200, 249 ) ); + pen4 = CreatePen( PS_SOLID, 2, RGB( 0, 0, 200 ) ); + + oldPen = (HPEN)SelectObject( dc, pen ); + + MoveToEx( dc, outrect.left, ( outrect.bottom + outrect.top ) / 2, NULL ); + LineTo( dc, outrect.right, ( outrect.bottom + outrect.top ) / 2 ); + + SelectObject( dc, pen2 ); + + // Now iterate the samples + float currenttime = 0.0f; + int pixel = 0; + int height = ( outrect.bottom - outrect.top ) / 2; + int midy = ( outrect.bottom + outrect.top ) / 2; + int bufferlen = ( intsamplesperpixel + 3 ) & ~3; + short *samples = new short[ 2 * bufferlen ]; + bool drawingselection = false; + int maxsamples = max( 32, intsamplesperpixel / 16 ); + int currentsample = 0; + + while ( currenttime < endtime ) + { + + pWaveOutput->m_audioDevice.MixBegin(); + + int samplecount = min( maxsamples, intsamplesperpixel ); + + if ( !pMixer->MixDataToDevice( &pWaveOutput->m_audioDevice, &channel, currentsample, samplecount, pWave->SampleRate(), true ) ) + break; + + currentsample = pMixer->GetSamplePosition(); + + // Jump ahead by diff + int diff = intsamplesperpixel - samplecount; + if ( diff > 0 ) + { + if ( !pMixer->SkipSamples( &channel, currentsample, diff, pWave->SampleRate(), true ) ) + break; + } + + currentsample = pMixer->GetSamplePosition(); + + pWaveOutput->m_audioDevice.TransferBufferStereo16( samples, samplecount ); + + if ( currenttime >= starttime ) + { + if ( selected ) + { + bool boundary = false; + bool inselection = ( currenttime >= selectionstarttime && + currenttime <= selectionendtime ); + + if ( inselection ) + { + if ( !drawingselection ) + { + drawingselection = true; + boundary = true; + } + } + else if ( drawingselection ) + { + boundary = true; + drawingselection = false; + } + + if ( inselection || boundary ) + { + int top, bottom; + + bottom = outrect.bottom; + + HPEN *usePen; + if ( boundary ) + { + usePen = &pen4; + top = outrect.top; + } + else + { + usePen = &pen3; + top = outrect.bottom - 19; + } + + HPEN old = (HPEN)SelectObject( dc, *usePen ); + + MoveToEx( dc, outrect.left + pixel, top, NULL ); + LineTo( dc, outrect.left + pixel, bottom-1 ); + + SelectObject( dc, old ); + } + } + + + int maxvalue = -65536; + int minvalue = 65536; + + short *pData = samples; + + // only take fix samples + int step = 2; + int count = 2 * samplecount; + + for ( int i = 0; i < count; i+=step ) + { + int val = (int)( pData[i] + pData[i+1] ) / 2; + + if ( val > maxvalue ) + { + maxvalue = val; + } + + if ( val < minvalue ) + { + minvalue = val; + } + } + + float maxv = (float)( maxvalue ) / 32768.0f; + float minv = (float)( minvalue ) / 32768.0f; + + MoveToEx( dc, outrect.left + pixel, midy + ( int ) ( maxv * height ), NULL ); + LineTo( dc, outrect.left + pixel, midy + ( int ) ( minv * height ) ); + + pixel++; + } + currenttime += timeperpixel; + } + + delete[] samples; + + SelectObject( dc, oldPen ); + DeleteObject( pen ); + DeleteObject( pen2 ); + DeleteObject( pen3 ); + + delete pMixer; +} + +bool CFacePoserSound::IsSoundPlaying( CAudioMixer *pMixer ) +{ + if ( !m_pAudio || !pMixer ) + { + return false; + } + + // + int index = m_pAudio->FindSourceIndex( pMixer ); + if ( index != -1 ) + return true; + + return false; +} + +CAudioMixer *CFacePoserSound::FindMixer( CAudioSource *source ) +{ + if ( !m_pAudio ) + return NULL; + + return m_pAudio->GetMixerForSource( source ); +} + + +void CFacePoserSound::EnsureNoModelReferences( CAudioSource *source ) +{ + int c = models->Count(); + for ( int i = 0; i < c; i++ ) + { + StudioModel *model = models->GetStudioModel( i ); + if ( model->m_mouth.IsSourceReferenced( source ) ) + { + model->m_mouth.RemoveSource( source ); + } + } +}
\ No newline at end of file diff --git a/utils/hlfaceposer/sound.h b/utils/hlfaceposer/sound.h new file mode 100644 index 0000000..0199701 --- /dev/null +++ b/utils/hlfaceposer/sound.h @@ -0,0 +1,97 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#ifndef SOUND_H +#define SOUND_H +#pragma once + +class CAudioMixer; + +class CAudioInput +{ +public: + // factory to create a suitable audio input for this system + static CAudioInput *Create( void ); + + // base class needs virtual destructor + virtual ~CAudioInput( void ) {} + + // ------------------- interface ------------------------ + + // Returns the current count of available samples + virtual int SampleCount( void ) = 0; + + // returns the size of each sample in bytes + virtual int SampleSize( void ) = 0; + + // returns the sampling rate of the data + virtual int SampleRate( void ) = 0; + + // returns a pointer to the available data + virtual void *SampleData( void ) = 0; + + // release the available data (mark as done) + virtual void SampleRelease( void ) = 0; + + // returns the mono/stereo status of this device (true if stereo) + virtual bool IsStereo( void ) = 0; + + // begin sampling + virtual void Start( void ) = 0; + + // stop sampling + virtual void Stop( void ) = 0; +}; + +class CAudioSource; + +class CAudioOutput +{ +public: + // factory to create a suitable audio output for this system + static CAudioOutput *Create( void ); + + // base class needs virtual destructor + virtual ~CAudioOutput( void ) {} + + // ------------------- interface ------------------------ + + // returns the size of each sample in bytes + virtual int SampleSize( void ) = 0; + + // returns the sampling rate of the data + virtual int SampleRate( void ) = 0; + + // returns the mono/stereo status of this device (true if stereo) + virtual bool IsStereo( void ) = 0; + + // move up to time (time is absolute) + virtual void Update( float dt ) = 0; + + virtual void Flush( void ) = 0; + + // Hook up a filter to the input channel + virtual void AddSource( CAudioMixer *pSource ) = 0; + + virtual void StopSounds( void ) = 0; + + virtual void FreeChannel( int channel ) = 0; + + virtual int FindSourceIndex( CAudioMixer *pSource ) = 0; + + virtual float GetAmountofTimeAhead( void ) = 0; + + virtual int GetNumberofSamplesAhead( void ) = 0; + + virtual CAudioMixer *GetMixerForSource( CAudioSource *pDevice ) = 0; +}; + + +int AudioResample( void *pInput, int inCount, int inSize, bool inStereo, int inRate, + void *pOutput, int outCount, int outSize, bool outStereo, int outRate ); + +#endif // SOUND_H diff --git a/utils/hlfaceposer/soundlookup.cpp b/utils/hlfaceposer/soundlookup.cpp new file mode 100644 index 0000000..128e340 --- /dev/null +++ b/utils/hlfaceposer/soundlookup.cpp @@ -0,0 +1,140 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "mxtk/mx.h" +#include "resource.h" +#include "SoundLookup.h" +#include "mdlviewer.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "addsoundentry.h" + +static CSoundLookupParams g_Params; + +static void PopulateSoundEntryList( HWND wnd, CSoundLookupParams *params ) +{ + HWND control = GetDlgItem( wnd, IDC_SOUNDENTRYLIST ); + if ( !control ) + return; + + SendMessage( control, LB_RESETCONTENT, 0, 0 ); + SendMessage( control, WM_SETFONT, (WPARAM) (HFONT) GetStockObject (ANSI_FIXED_FONT), MAKELPARAM (TRUE, 0) ); + + int c = params->entryList->Count(); + for ( int i = 0; i < c; i++ ) + { + int soundindex = (*params->entryList)[ i ]; + char text[ 128 ]; + text[ 0 ] = 0; + Q_strncpy( text, soundemitter->GetSoundName( soundindex ), sizeof( text ) ); + if ( text[0] ) + { + char const *script = soundemitter->GetSourceFileForSound( soundindex ); + + int idx = SendMessage( control, LB_ADDSTRING, 0, (LPARAM)va( "%20s: '%s'", script, text ) ); + SendMessage( control, LB_SETITEMDATA, idx, (LPARAM)soundindex ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : hwndDlg - +// uMsg - +// wParam - +// lParam - +// Output : static BOOL CALLBACK +//----------------------------------------------------------------------------- +static BOOL CALLBACK SoundLookupDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + switch(uMsg) + { + case WM_INITDIALOG: + // Insert code here to put the string (to find and replace with) + // into the edit controls. + // ... + { + g_Params.PositionSelf( hwndDlg ); + + PopulateSoundEntryList( hwndDlg, &g_Params ); + + SetDlgItemText( hwndDlg, IDC_STATIC_PROMPT, g_Params.m_szPrompt ); + + SetWindowText( hwndDlg, g_Params.m_szDialogTitle ); + + SetFocus( GetDlgItem( hwndDlg, IDC_SOUNDENTRYLIST ) ); + } + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + int selindex = SendMessage( GetDlgItem( hwndDlg, IDC_SOUNDENTRYLIST ), LB_GETCURSEL, 0, 0 ); + if ( selindex == LB_ERR ) + { + mxMessageBox( NULL, "You must select an entry from the list", g_appTitle, MB_OK ); + return TRUE; + } + + int soundindex = SendMessage( GetDlgItem( hwndDlg, IDC_SOUNDENTRYLIST ), LB_GETITEMDATA, selindex, 0 ); + + Assert( soundindex != LB_ERR ); + + Q_strncpy( g_Params.m_szSoundName, soundemitter->GetSoundName( soundindex ), sizeof ( g_Params.m_szSoundName ) ); + EndDialog( hwndDlg, 1 ); + } + break; + case IDCANCEL: + EndDialog( hwndDlg, 0 ); + break; + case IDC_ADDENTRY: + { + // Create a new sound entry for this sound + CAddSoundParams params; + Q_memset( ¶ms, 0, sizeof( params ) ); + Q_strcpy( params.m_szDialogTitle, "Add Sound Entry" ); + Q_strcpy( params.m_szWaveFile, g_Params.m_szWaveFile ); + + if ( AddSound( ¶ms, hwndDlg ) ) + { + // Add it to soundemitter and check out script files + if ( params.m_szSoundName[ 0 ] && + params.m_szScriptName[ 0 ] ) + { + Q_strcpy( g_Params.m_szSoundName, params.m_szSoundName ); + // Press the OK button for the user... + EndDialog( hwndDlg, 1 ); + } + } + } + break; + } + return TRUE; + } + return FALSE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *view - +// *actor - +// Output : int +//----------------------------------------------------------------------------- +int SoundLookup( CSoundLookupParams *params, HWND parent ) +{ + g_Params = *params; + + int retval = DialogBox( (HINSTANCE)GetModuleHandle( 0 ), + MAKEINTRESOURCE( IDD_WAVELOOKUP ), + parent, + (DLGPROC)SoundLookupDialogProc ); + + *params = g_Params; + + return retval; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/soundlookup.h b/utils/hlfaceposer/soundlookup.h new file mode 100644 index 0000000..d6a2de2 --- /dev/null +++ b/utils/hlfaceposer/soundlookup.h @@ -0,0 +1,33 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef SOUNDLOOKUP_H +#define SOUNDLOOKUP_H +#ifdef _WIN32 +#pragma once +#endif + +#include "basedialogparams.h" +#include "utlvector.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +struct CSoundLookupParams : public CBaseDialogParams +{ + char m_szWaveFile[ 256 ]; + + char m_szPrompt[ 256 ]; + + CUtlVector< int > *entryList; + + char m_szSoundName[ 256 ]; +}; + +// Display/create dialog +int SoundLookup( CSoundLookupParams *params, HWND parent ); + +#endif // SOUNDLOOKUP_H diff --git a/utils/hlfaceposer/tabwindow.cpp b/utils/hlfaceposer/tabwindow.cpp new file mode 100644 index 0000000..cdd07ba --- /dev/null +++ b/utils/hlfaceposer/tabwindow.cpp @@ -0,0 +1,480 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "tabwindow.h" +#include "ChoreoWidgetDrawHelper.h" +#include "hlfaceposer.h" + +//----------------------------------------------------------------------------- +// Purpose: Constructor +// Input : *parent - +// x - +// y - +// w - +// h - +// id - +// style - +//----------------------------------------------------------------------------- +CTabWindow::CTabWindow( mxWindow *parent, int x, int y, int w, int h, int id /*= 0*/, int style /*=0*/ ) +: mxWindow( parent, x, y, w, h, "", style ) +{ + setId( id ); + + m_nSelected = -1; + + m_nRowHeight = 20; + m_nRowsRequired = 1; + + m_nTabWidth = 80; + m_nPixelDelta = 3; + m_bInverted = false; + m_bRightJustify = false; + SetColor( COLOR_BG, GetSysColor( COLOR_BTNFACE ) ); + SetColor( COLOR_FG, GetSysColor( COLOR_INACTIVECAPTION ) ); + SetColor( COLOR_FG_SELECTED, GetSysColor( COLOR_ACTIVECAPTION ) ); + SetColor( COLOR_HILITE, GetSysColor( COLOR_3DSHADOW ) ); + SetColor( COLOR_HILITE_SELECTED, GetSysColor( COLOR_3DHILIGHT ) ); + SetColor( COLOR_TEXT, GetSysColor( COLOR_CAPTIONTEXT ) ); + SetColor( COLOR_TEXT_SELECTED, GetSysColor( COLOR_INACTIVECAPTIONTEXT ) ); + + FacePoser_AddWindowStyle( this, WS_CLIPCHILDREN | WS_CLIPSIBLINGS ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CTabWindow::~CTabWindow +//----------------------------------------------------------------------------- +CTabWindow::~CTabWindow ( void ) +{ + removeAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : index - +// clr - +//----------------------------------------------------------------------------- +void CTabWindow::SetColor( int index, COLORREF clr ) +{ + if ( index < 0 || index >= NUM_COLORS ) + return; + + m_Colors[ index ] = clr; +} + +void CTabWindow::SetInverted( bool invert ) +{ + m_bInverted = invert; + RecomputeLayout( w2() ); +} + +void CTabWindow::SetRightJustify( bool rightjustify ) +{ + m_bRightJustify = true; + RecomputeLayout( w2() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Tabs are sized to string content +// Input : rcClient - +// tabRect - +// tabNum - +//----------------------------------------------------------------------------- +void CTabWindow::GetTabRect( const RECT& rcClient, RECT& tabRect, int tabNum ) +{ + tabRect = m_Items[ tabNum ].rect; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +// rcClient - +// tabnum - +// selected - +//----------------------------------------------------------------------------- +void CTabWindow::DrawTab( CChoreoWidgetDrawHelper& drawHelper, RECT& rcClient, int tabnum, bool selected ) +{ + RECT rcTab; + + if ( tabnum < 0 || tabnum >= m_Items.Size() ) + return; + + GetTabRect( rcClient, rcTab, tabnum ); + + COLORREF fgcolor = m_Colors[ selected ? COLOR_FG_SELECTED : COLOR_FG ]; + COLORREF hilightcolor = m_Colors[ selected ? COLOR_HILITE_SELECTED : COLOR_HILITE ]; + COLORREF text = m_Colors[ selected ? COLOR_TEXT_SELECTED : COLOR_TEXT ]; + + // Create a trapezoid/paralleogram + POINT region[4]; + int cPoints = 4; + + OffsetRect( &rcTab, 0, m_bInverted ? 1 : -1 ); + + if ( m_bInverted ) + { + region[ 0 ].x = rcTab.left - m_nPixelDelta; + region[ 0 ].y = rcTab.top; + + region[ 1 ].x = rcTab.right + m_nPixelDelta; + region[ 1 ].y = rcTab.top; + + region[ 2 ].x = rcTab.right - m_nPixelDelta; + region[ 2 ].y = rcTab.bottom; + + region[ 3 ].x = rcTab.left + m_nPixelDelta; + region[ 3 ].y = rcTab.bottom; + } + else + { + region[ 0 ].x = rcTab.left + m_nPixelDelta; + region[ 0 ].y = rcTab.top; + + region[ 1 ].x = rcTab.right - m_nPixelDelta; + region[ 1 ].y = rcTab.top; + + region[ 2 ].x = rcTab.right + m_nPixelDelta; + region[ 2 ].y = rcTab.bottom; + + region[ 3 ].x = rcTab.left - m_nPixelDelta; + region[ 3 ].y = rcTab.bottom; + } + + HDC dc = drawHelper.GrabDC(); + + HRGN rgn = CreatePolygonRgn( region, cPoints, ALTERNATE ); + + int oldPF = SetPolyFillMode( dc, ALTERNATE ); + + HBRUSH brBg = CreateSolidBrush( fgcolor ); + HBRUSH brBorder = CreateSolidBrush( hilightcolor ); + //HBRUSH brInset = CreateSolidBrush( fgcolor ); + + FillRgn( dc, rgn, brBg ); + FrameRgn( dc, rgn, brBorder, 1, 1 ); + + SetPolyFillMode( dc, oldPF ); + + DeleteObject( rgn ); + + DeleteObject( brBg ); + DeleteObject( brBorder ); + //DeleteObject( brInset ); + + // Position label + InflateRect( &rcTab, -5, 0 ); + OffsetRect( &rcTab, 2, 0 ); + + // Draw label + drawHelper.DrawColoredText( "Arial", 9, FW_NORMAL, text, rcTab, "%s%s", getPrefix( tabnum ), getLabel( tabnum ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTabWindow::redraw( void ) +{ + CChoreoWidgetDrawHelper drawHelper( this, m_Colors[ COLOR_BG ] ); + + int liney = m_bInverted ? 1 : h2() - 2; + + drawHelper.DrawColoredLine( m_Colors[ COLOR_HILITE ], PS_SOLID, 1, 0, liney, w(), liney ); + RECT rc; + drawHelper.GetClientRect( rc ); + + // Draw non-selected first + for ( int i = 0 ; i < m_Items.Size(); i++ ) + { + if ( i == m_nSelected ) + continue; + + DrawTab( drawHelper, rc, i ); + } + + // Draw selected last, so that it appears to pop to top of z order + if ( m_nSelected >= 0 && m_nSelected < m_Items.Size() ) + { + DrawTab( drawHelper, rc, m_nSelected, true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// my - +// Output : int +//----------------------------------------------------------------------------- +int CTabWindow::GetItemUnderMouse( int mx, int my ) +{ + RECT rcClient; + GetClientRect( (HWND)getHandle(), &rcClient ); + + for ( int i = 0; i < m_Items.Size() ; i++ ) + { + RECT rcTab; + GetTabRect( rcClient, rcTab, i ); + + if ( mx < rcTab.left || + mx > rcTab.right || + my < rcTab.top || + my > rcTab.bottom ) + { + continue; + } + return i; + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +// Output : int CTabWindow::handleEvent +//----------------------------------------------------------------------------- +int CTabWindow::handleEvent (mxEvent *event) +{ + int iret = 0; + + switch ( event->event ) + { + case mxEvent::MouseDown: + { + int item = GetItemUnderMouse( (short)event->x, (short)event->y ); + if ( item != -1 ) + { + m_nSelected = item; + redraw(); + + // Send CBN_SELCHANGE WM_COMMAND message to parent + HWND parent = (HWND)( getParent() ? getParent()->getHandle() : NULL ); + if ( parent ) + { + LPARAM lp; + WPARAM wp; + + wp = MAKEWPARAM( getId(), CBN_SELCHANGE ); + lp = (long)getHandle(); + + PostMessage( parent, WM_COMMAND, wp, lp ); + } + iret = 1; + } + + if ( event->buttons & mxEvent::MouseRightButton ) + { + ShowRightClickMenu( (short)event->x, (short)event->y ); + iret = 1; + } + } + break; + case mxEvent::Size: + { + RecomputeLayout( w2() ); + } + break; + } + return iret; +} + +//----------------------------------------------------------------------------- +// Purpose: Add string to table +// Input : *item - +//----------------------------------------------------------------------------- +void CTabWindow::add( const char *item ) +{ + m_Items.AddToTail(); + CETItem *p = &m_Items[ m_Items.Size() - 1 ]; + Assert( p ); + + Q_memset( &p->rect, 0, sizeof( p->rect) ); + + strcpy( p->m_szString, item ); + p->m_szPrefix[ 0 ] = 0; + m_nSelected = min( m_nSelected, m_Items.Size() - 1 ); + m_nSelected = max( m_nSelected, 0 ); + + RecomputeLayout( w2() ); + + redraw(); +} + +void CTabWindow::setPrefix( int item, char const *prefix ) +{ + if ( item < 0 || item >= m_Items.Size() ) + return; + + Q_strncpy( m_Items[ item ].m_szPrefix, prefix, sizeof( m_Items[ item ].m_szPrefix ) ); +// RecomputeLayout( w2() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Change selected item +// Input : index - +//----------------------------------------------------------------------------- +void CTabWindow::select( int index ) +{ + if ( index < 0 || index >= m_Items.Size() ) + return; + + m_nSelected = index; + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: Remove a string +// Input : index - +//----------------------------------------------------------------------------- +void CTabWindow::remove( int index ) +{ + if ( index < 0 || index >= m_Items.Size() ) + return; + + m_Items.Remove( index ); + + m_nSelected = min( m_nSelected, m_Items.Size() - 1 ); + m_nSelected = max( m_nSelected, 0 ); + + RecomputeLayout( w2() ); + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: Clear out everything +//----------------------------------------------------------------------------- +void CTabWindow::removeAll() +{ + m_nSelected = -1; + m_Items.RemoveAll(); + + RecomputeLayout( w2() ); + + redraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CTabWindow::getItemCount () const +{ + return m_Items.Size(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CTabWindow::getSelectedIndex () const +{ + // Convert based on override index + return m_nSelected; +} + +char const *CTabWindow::getLabel( int item ) +{ + if ( item < 0 || item >= m_Items.Count() ) + return ""; + + return m_Items[ item ].m_szString; +} + +char const *CTabWindow::getPrefix( int item ) +{ + if ( item < 0 || item >= m_Items.Count() ) + return ""; + + return m_Items[ item ].m_szPrefix; +} + +void CTabWindow::SetRowHeight( int rowheight ) +{ + m_nRowHeight = rowheight; + RecomputeLayout( w2() ); + redraw(); +} + +int CTabWindow::GetBestHeight( int width ) +{ + return RecomputeLayout( width, false ) * m_nRowHeight; +} + + +int CTabWindow::RecomputeLayout( int windowWidth, bool dolayout /*=true*/ ) +{ + // Draw non-selected first + int curedge = m_nPixelDelta + 1; + int curtop = 0; + + if ( m_bRightJustify ) + { + curedge = windowWidth - ( m_nPixelDelta + 1 ) - 5; + } + + int startedge = curedge; + + int currentrow = 0; + + for ( int i = 0 ; i < m_Items.Size(); i++ ) + { + CETItem *p = &m_Items[ i ]; + + RECT rc; + + int textwidth = CChoreoWidgetDrawHelper::CalcTextWidth( "Arial", 9, FW_NORMAL, "%s%s", p->m_szPrefix, p->m_szString ) + 15; + + if ( !m_bRightJustify ) + { + // Starting column + if ( curedge + textwidth > windowWidth ) + { + curedge = startedge; + curtop += m_nRowHeight; + currentrow++; + } + + rc.left = curedge; + rc.right = curedge + textwidth; + rc.top = curtop + 2; + rc.bottom = curtop + m_nRowHeight; + + curedge += textwidth; + + p->rect = rc; + } + else + { + // Starting column + if ( curedge - textwidth < 0 ) + { + curedge = startedge; + curtop += m_nRowHeight; + currentrow++; + } + + rc.left = curedge - textwidth; + rc.right = curedge; + rc.top = curtop; + rc.bottom = curtop + m_nRowHeight - 2; + + curedge -= textwidth; + } + + if ( dolayout ) + { + p->rect = rc; + } + } + + if ( dolayout ) + { + m_nRowsRequired = currentrow + 1; + } + + return currentrow + 1; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/tabwindow.h b/utils/hlfaceposer/tabwindow.h new file mode 100644 index 0000000..28a0447 --- /dev/null +++ b/utils/hlfaceposer/tabwindow.h @@ -0,0 +1,102 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TABWINDOW_H +#define TABWINDOW_H +#ifdef _WIN32 +#pragma once +#endif + +#include <mxtk/mx.h> +#include "utlvector.h" + +class CChoreoWidgetDrawHelper; + +//----------------------------------------------------------------------------- +// Purpose: A custom tab control for handling expression class strings +//----------------------------------------------------------------------------- +class CTabWindow : public mxWindow +{ +public: + enum + { + COLOR_BG = 0, + COLOR_FG, + COLOR_FG_SELECTED, + COLOR_HILITE, + COLOR_HILITE_SELECTED, + COLOR_TEXT, + COLOR_TEXT_SELECTED, + + NUM_COLORS + }; + + CTabWindow( mxWindow *parent, int x, int y, int w, int h, int id = 0, int style = 0 ); + virtual ~CTabWindow ( void ); + + virtual void redraw( void ); + virtual int handleEvent (mxEvent *event); + + // MANIPULATORS + virtual void add (const char *item); + virtual void select (int index); + virtual void remove (int index); + virtual void removeAll (); + virtual void setPrefix( int item, char const *prefix ); + + // ACCESSORS + virtual int getItemCount () const; + virtual int getSelectedIndex () const; + + virtual char const *getLabel( int item ); + virtual char const *getPrefix( int item ); + virtual void ShowRightClickMenu( int mx, int my ) = 0; + + void SetColor( int index, COLORREF clr ); + + void SetInverted( bool invert ); + void SetRightJustify( bool rightjustify ); + + int GetBestHeight( int width ); + void SetRowHeight( int rowheight ); + +protected: + void GetTabRect( const RECT& rcClient, RECT& tabRect, int tabNum ); + virtual void DrawTab( CChoreoWidgetDrawHelper& drawHelper, RECT& rcClient, int tabnum, bool selected = false ); + + int RecomputeLayout( int windowWidth, bool dolayout = true ); + + class CETItem + { + public: + enum + { + MAX_ET_STRING_LENGTH = 64 + }; + + char m_szString[ MAX_ET_STRING_LENGTH ]; + char m_szPrefix[ MAX_ET_STRING_LENGTH ]; + RECT rect; + }; + + int GetItemUnderMouse( int mx, int my ); + + CUtlVector <CETItem> m_Items; + int m_nRowsRequired; + + int m_nSelected; + + int m_nTabWidth; + int m_nPixelDelta; + bool m_bInverted; + bool m_bRightJustify; + + COLORREF m_Colors[ NUM_COLORS ]; + + int m_nRowHeight; +}; +#endif // TABWINDOW_H diff --git a/utils/hlfaceposer/timelineitem.cpp b/utils/hlfaceposer/timelineitem.cpp new file mode 100644 index 0000000..50a4b86 --- /dev/null +++ b/utils/hlfaceposer/timelineitem.cpp @@ -0,0 +1,1763 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "hlfaceposer.h" +#include <stdio.h> +#include "TimelineItem.h" +#include "choreowidgetdrawhelper.h" +#include "mathlib/mathlib.h" +#include "expressions.h" +#include "StudioModel.h" +#include "expclass.h" +#include "mathlib/mathlib.h" +#include "ExpressionTool.h" +#include "choreoevent.h" +#include "choreoscene.h" +#include "choreoactor.h" +#include "choreochannel.h" +#include "ChoreoView.h" +#include "ControlPanel.h" +#include "faceposer_models.h" +#include "MatSysWin.h" +#include "choreoviewcolors.h" +#include "ifaceposersound.h" +#include "curveeditorhelpers.h" + +extern double realtime; + +#define DOUBLE_CLICK_TIME 0.2 + +#define GROW_HANDLE_WIDTH 66 +#define GROW_HANDLE_HEIGHT 8 +#define GROW_HANDLE_INSETPIXELS 8 + +#define TIMELINEITEM_DEFAULT_HEIGHT 100 + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +TimelineItem::TimelineItem( mxWindow *workspace ) +{ + m_pHelper = new CCurveEditorHelper< TimelineItem >( this ); + + m_pWorkspace = workspace; + + m_nDragging = DRAGTYPE_NONE; + m_nLastX = 0; + m_nLastY = 0; + m_nStartX = 0; + m_nStartY = 0; + + SetExpressionInfo( NULL, 0 ); + + m_nNumSelected = 0; + + m_nEditType = 0; + + SetCollapsed( false ); + SetActive( false ); + + m_nUndoSetup = 0; + m_rcBounds.top = 0; + m_rcBounds.bottom = 0; + m_rcBounds.left = 0; + m_rcBounds.right = 0; + m_bVisible = false; + m_flLastClickTime = -1; + + m_nCurrentHeight = TIMELINEITEM_DEFAULT_HEIGHT; +} + +TimelineItem::~TimelineItem( void ) +{ + delete m_pHelper; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TimelineItem::ResetHeight() +{ + m_nCurrentHeight = TIMELINEITEM_DEFAULT_HEIGHT; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mx - +// Output : float +//----------------------------------------------------------------------------- +float TimelineItem::GetTimeForMouse( int mx, bool clip /*= false*/ ) +{ + float start, end; + g_pExpressionTool->GetStartAndEndTime( start, end ); + + if ( clip ) + { + if ( mx < m_rcBounds.left ) + { + return start; + } + else if ( mx >= m_rcBounds.right ) + { + return end; + } + } + + float frac = (float)( mx - m_rcBounds.left ) / (float)( m_rcBounds.right - m_rcBounds.left ); + float t = start + frac * ( end - start ); + return t; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : t - +// Output : int +//----------------------------------------------------------------------------- +int TimelineItem::GetMouseForTime( float t, bool *clipped /*= NULL*/ ) +{ + float start, end; + g_pExpressionTool->GetStartAndEndTime( start, end ); + + float frac = ( t - start ) / ( end - start ); + + if ( frac < 0.0 || frac > 1.0 ) + { + if ( clipped ) + { + *clipped = true; + } + } + + int mx = m_rcBounds.left + ( int ) ( frac * (float)( m_rcBounds.right - m_rcBounds.left ) ); + + return mx; +} + +int TimelineItem::NumSamples() +{ + CFlexAnimationTrack *track = GetSafeTrack(); + if ( !track ) + return 0; + + // Aggregate both types of tracks together + return track->GetNumSamples( 0 ) + track->GetNumSamples( 1 ); + // return track->GetNumSamples( m_nEditType ); +} + +CExpressionSample *TimelineItem::GetSample( int idx ) +{ + CFlexAnimationTrack *track = GetSafeTrack(); + if ( !track ) + return NULL; + + if ( idx >= track->GetNumSamples( 0 ) ) + { + // Rebase and look at left/right track instead + idx -= track->GetNumSamples( 0 ); + return track->GetSample( idx, 1 ); + } + return track->GetSample( idx, 0 ); +} + +CExpressionSample *TimelineItem::GetSampleUnderMouse( int mx, int my, float tolerance /*= FP_TL_SELECTION_TOLERANCE*/ ) +{ + CFlexAnimationTrack *track = GetSafeTrack(); + if ( !track ) + return NULL; + + CChoreoEvent *e = track->GetEvent(); + if ( !e ) + return NULL; + + float closest_dist = 9999999.f; + CExpressionSample *bestsample = NULL; + + // Add a sample point + int height = m_rcBounds.bottom - m_rcBounds.top; + + mx += m_rcBounds.left; + + for ( int i = 0; i < track->GetNumSamples( m_nEditType ); i++ ) + { + CExpressionSample *sample = track->GetSample( i, m_nEditType ); + + bool clipped = false; + int px = GetMouseForTime( sample->time, &clipped ); + int py = height * ( 1.0f - sample->value ); + + int dx = px - mx; + int dy = py - my; + + float dist = sqrt( (float)(dx * dx + dy * dy) ); + if ( dist < closest_dist ) + { + bestsample = sample; + closest_dist = dist; + } + } + + // Not close to any of them!!! + if ( ( tolerance != 0.0f ) && + ( closest_dist > tolerance ) ) + return NULL; + + return bestsample; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TimelineItem::DeselectAll( void ) +{ + g_pExpressionTool->DeselectAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TimelineItem::SelectAll( void ) +{ + CFlexAnimationTrack *track = GetSafeTrack(); + if ( !track ) + return; + + for ( int t = 0; t < 2; t++ ) + { + for ( int i = 0; i < track->GetNumSamples( t ); i++ ) + { + CExpressionSample *sample = track->GetSample( i, t ); + sample->selected = true; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TimelineItem::Delete( void ) +{ + g_pExpressionTool->DeleteSelectedSamples(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : sample - +//----------------------------------------------------------------------------- +void TimelineItem::AddSample( CExpressionSample const& sample ) +{ + CFlexAnimationTrack *track = GetSafeTrack(); + if ( !track ) + return; + + PreDataChanged( "Add sample point" ); + + track->AddSample( sample.time, sample.value, m_nEditType ); + track->Resort( m_nEditType ); + + SetActive( true ); + + PostDataChanged( "Add sample point" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int TimelineItem::CountSelected( void ) +{ + m_nNumSelected = m_pHelper->CountSelected( false ); + return m_nNumSelected; +} + +void TimelineItem::SetMousePositionForEvent( mxEvent *event ) +{ + POINT pt; + GetCursorPos( &pt ); + ScreenToClient( (HWND)m_pWorkspace->getHandle(), &pt ); + + pt.x -= m_rcBounds.left; + pt.y -= m_rcBounds.top; + + event->x = pt.x; + event->y = pt.y; +} + +int TimelineItem::handleEvent( mxEvent *event ) +{ + int iret = 0; + + // Give helper a shot at the event + if ( m_pHelper->HelperHandleEvent( event ) ) + { + return 1; + } + + switch ( event->event ) + { + case mxEvent::KeyDown: + { + switch ( event->key ) + { + default: + iret = g_pChoreoView->HandleZoomKey( g_pExpressionTool, event->key ); + break; + case VK_ESCAPE: + DeselectAll(); + DrawSelf(); + break; + case VK_DELETE: + Delete(); + DrawSelf(); + break; + case 'C': + Copy(); + DrawSelf(); + break; + case 'V': + Paste(); + DrawSelf(); + break; + case 'J': + { + g_pExpressionTool->OnCopyToFlex( g_pExpressionTool->GetScrubberSceneTime(), true ); + } + break; + case 'K': + { + g_pExpressionTool->OnCopyFromFlex( g_pExpressionTool->GetScrubberSceneTime(), false ); + } + break; + case 188: // VK_OEM_COMMA: + { + g_pExpressionTool->SetScrubTargetTime( 0.0f ); + } + break; + case 190: // VK_OEM_PERIOD: + { + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( scene ) + { + g_pExpressionTool->SetScrubTargetTime( scene->FindStopTime() ); + } + } + break; + case VK_LEFT: + { + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( scene && scene->GetSceneFPS() > 0 ) + { + float curscrub = g_pExpressionTool->GetScrub(); + curscrub -= ( 1.0f / (float)scene->GetSceneFPS() ); + curscrub = max( curscrub, 0.0f ); + g_pExpressionTool->SetScrubTargetTime( curscrub ); + } + } + break; + case VK_RIGHT: + { + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( scene && scene->GetSceneFPS() > 0 ) + { + float curscrub = g_pExpressionTool->GetScrub(); + curscrub += ( 1.0f / (float)scene->GetSceneFPS() ); + curscrub = min( curscrub, scene->FindStopTime() ); + g_pExpressionTool->SetScrubTargetTime( curscrub ); + } + } + break; + case 191: + { + if ( g_pChoreoView->IsPlayingScene() ) + { + g_pChoreoView->StopScene(); + } + } + break; + } + iret = 1; + } + break; + case mxEvent::KeyUp: + { + switch ( event->key ) + { + case VK_SPACE: + { + CFlexAnimationTrack *track = GetSafeTrack(); + if ( track && track->IsComboType() ) + { + SetEditType( m_nEditType == 0 ? 1 : 0 ); + DrawSelf(); + } + } + break; + } + iret = 1; + } + break; + case mxEvent::MouseDown: + { + sound->Flush(); + + SetFocus( (HWND)g_pExpressionTool->getHandle() ); + + int height = m_rcBounds.bottom - m_rcBounds.top; + + bool rightbutton = ( event->buttons & mxEvent::MouseRightButton ) ? true : false; + + if ( m_nDragging == DRAGTYPE_NONE ) + { + bool ctrlDown = ( event->modifiers & mxEvent::KeyCtrl ) ? true : false; + + CExpressionSample *sample = GetSampleUnderMouse( event->x, event->y, ctrlDown ? FP_TL_ADDSAMPLE_TOLERANCE : FP_TL_SELECTION_TOLERANCE ); + + if ( IsMouseOverGrowHandle( (short)event->x, (short)event->y ) ) + { + m_nDragging = DRAGTYPE_GROW; + m_nLastX = (short)event->x; + m_nLastY = (short)event->y; + + m_nStartX = m_nLastX; + m_nStartY = m_nLastY; + + MouseDrag( (short)event->x, (short)event->y, event->modifiers ); + + DrawGrowRect(); + } + else if ( sample ) + { + if ( event->modifiers & mxEvent::KeyShift ) + { + sample->selected = !sample->selected; + DrawSelf(); + } + else if ( sample->selected ) + { + m_nDragging = rightbutton ? DRAGTYPE_MOVEPOINTS_TIME : DRAGTYPE_MOVEPOINTS_VALUE; + m_nLastX = (short)event->x; + m_nLastY = (short)event->y; + + m_nStartX = m_nLastX; + m_nStartY = m_nLastY; + + PreDataChanged( "Move sample point(s)" ); + + MouseDrag( (short)event->x, (short)event->y, event->modifiers ); + + DrawSelf(); + } + else + { + if ( !( event->modifiers & mxEvent::KeyShift ) ) + { + DeselectAll(); + DrawSelf(); + } + + m_nDragging = DRAGTYPE_SELECTION; + m_nLastX = (short)event->x; + m_nLastY = (short)event->y; + + m_nStartX = m_nLastX; + m_nStartY = m_nLastY; + + MouseDrag( (short)event->x, (short)event->y, event->modifiers ); + + DrawFocusRect(); + } + } + else if ( event->modifiers & mxEvent::KeyCtrl ) + { + CChoreoEvent *e = g_pExpressionTool->GetSafeEvent(); + if ( e ) + { + // Add a sample point + float t = GetTimeForMouse( (short)event->x + m_rcBounds.left ); + + CExpressionSample sample; + sample.time = FacePoser_SnapTime( t ); + sample.value = 1.0f - (float)( (short)( event->y ) ) / (float)height; + sample.selected = false; + + AddSample( sample ); + + DrawSelf(); + } + } + else + { + if ( rightbutton ) + { + POINT pt; + pt.x = event->x; + pt.y = event->y; + ClientToScreen( (HWND)m_pWorkspace->getHandle(), &pt ); + ScreenToClient( (HWND)g_pExpressionTool->getHandle(), &pt ); + event->x = pt.x; + event->y = pt.y; + g_pExpressionTool->ShowContextMenu( event, true ); + return iret; + } + + if ( !( event->modifiers & mxEvent::KeyShift ) ) + { + DeselectAll(); + DrawSelf(); + } + + m_nDragging = DRAGTYPE_SELECTION; + m_nLastX = (short)event->x; + m_nLastY = (short)event->y; + + m_nStartX = m_nLastX; + m_nStartY = m_nLastY; + + MouseDrag( (short)event->x, (short)event->y, event->modifiers ); + + DrawFocusRect(); + } + } + iret = 1; + } + break; + case mxEvent::MouseDrag: + case mxEvent::MouseMove: + { + if ( m_nDragging != DRAGTYPE_NONE ) + { + if ( m_nDragging == DRAGTYPE_SELECTION ) + { + DrawFocusRect(); + } + else if ( m_nDragging == DRAGTYPE_GROW ) + { + DrawGrowRect(); + } + + MouseDrag( (short)event->x, (short)event->y, event->modifiers ); + + if ( m_nDragging == DRAGTYPE_SELECTION ) + { + DrawFocusRect(); + } + else if ( m_nDragging == DRAGTYPE_GROW ) + { + DrawGrowRect(); + } + + if ( m_nDragging == DRAGTYPE_MOVEPOINTS_TIME || + m_nDragging == DRAGTYPE_MOVEPOINTS_VALUE ) + { + DrawSelf(); + } + } + else + { + // See if anything is selected + CountSelected(); + if ( m_nNumSelected <= 0 && g_pExpressionTool->IsFocusItem( this ) ) + { + // Nothing selected + // Draw auto highlight + DrawAutoHighlight( event ); + } + } + iret = 1; + } + break; + case mxEvent::MouseUp: + { + bool overgrow = IsMouseOverGrowHandle( (short)event->x, (short)event->y ); + + if ( m_nDragging != DRAGTYPE_NONE ) + { + if ( m_nDragging == DRAGTYPE_SELECTION ) + { + DrawFocusRect(); + } + else if ( m_nDragging == DRAGTYPE_GROW ) + { + DrawGrowRect(); + } + + MouseDrag( (short)event->x, (short)event->y, event->modifiers, true ); + + if ( m_nDragging == DRAGTYPE_GROW ) + { + // Finish grow by resizing control + int desiredheight = m_nCurrentHeight + event->y - m_nStartY; + if ( desiredheight >= 10 ) + { + m_nCurrentHeight = desiredheight; + g_pExpressionTool->LayoutItems( true ); + } + } + else if ( m_nDragging != DRAGTYPE_MOVEPOINTS_VALUE && + m_nDragging != DRAGTYPE_MOVEPOINTS_TIME ) + { + SelectPoints(); + } + else + { + PostDataChanged( "Move sample point(s)" ); + } + + m_nDragging = DRAGTYPE_NONE; + + DrawSelf(); + } + + bool rightbutton = ( event->buttons & mxEvent::MouseRightButton ) ? true : false; + bool shift = ( event->modifiers & mxEvent::KeyShift ) ? true : false; + bool ctrl = ( event->modifiers & mxEvent::KeyCtrl ) ? true : false; + + if ( !rightbutton && !shift && !ctrl ) + { + if ( realtime - m_flLastClickTime < DOUBLE_CLICK_TIME ) + { + if ( overgrow || IsCollapsed() ) + { + OnDoubleClicked(); + } + } + + m_flLastClickTime = realtime; + } + + iret = 1; + } + break; + } + + return iret; +} + +void TimelineItem::MouseDrag( int x, int y, int modifiers, bool snap /*=false*/ ) +{ + if ( m_nDragging == DRAGTYPE_NONE ) + return; + + int width = m_rcBounds.right - m_rcBounds.left; + int height = m_rcBounds.bottom - m_rcBounds.top; + + if ( m_nDragging == DRAGTYPE_MOVEPOINTS_TIME || + m_nDragging == DRAGTYPE_MOVEPOINTS_VALUE ) + { + int dx = x - m_nLastX; + int dy = y - m_nLastY; + + if ( !( modifiers & mxEvent::KeyCtrl ) ) + { + // Zero out motion on other axis + if ( m_nDragging == DRAGTYPE_MOVEPOINTS_VALUE ) + { + dx = 0; + x = m_nLastX; + } + else + { + dy = 0; + y = m_nLastY; + } + } + + float dfdx = (float)dx / g_pExpressionTool->GetPixelsPerSecond(); + float dfdy = (float)dy / (float)height; + + g_pExpressionTool->MoveSelectedSamples( dfdx, dfdy, snap ); + + // Update the scrubber + if ( (float)width > 0 ) + { + float t = GetTimeForMouse( x + m_rcBounds.left ); + g_pExpressionTool->ForceScrubPosition( t ); + + g_pMatSysWindow->Frame(); + } + } + + m_nLastX = x; + m_nLastY = y; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TimelineItem::DrawFocusRect( void ) +{ + RECT rcFocus; + + rcFocus.left = m_nStartX < m_nLastX ? m_nStartX : m_nLastX; + rcFocus.right = m_nStartX < m_nLastX ? m_nLastX : m_nStartX; + + rcFocus.top = m_nStartY < m_nLastY ? m_nStartY : m_nLastY; + rcFocus.bottom = m_nStartY < m_nLastY ? m_nLastY : m_nStartY; + + POINT offset; + offset.x = m_rcBounds.left; + offset.y = m_rcBounds.top; + ClientToScreen( (HWND)m_pWorkspace->getHandle(), &offset ); + OffsetRect( &rcFocus, offset.x, offset.y ); + + HDC dc = GetDC( NULL ); + + ::DrawFocusRect( dc, &rcFocus ); + + ReleaseDC( NULL, dc ); +} + +void TimelineItem::DrawSelf( void ) +{ + CChoreoWidgetDrawHelper drawHelper( m_pWorkspace, m_rcBounds ); + Draw( drawHelper ); +} + +void TimelineItem::Draw( CChoreoWidgetDrawHelper& drawHelper ) +{ + CFlexAnimationTrack *track = GetSafeTrack(); + if ( !track ) + return; + + CChoreoEvent *e = track->GetEvent(); + if ( !e ) + return; + + Assert( e->HasEndTime() ); + + bool active = track && ( IsValid() || IsActive() ); + + float starttime; + float endtime; + + g_pExpressionTool->GetStartAndEndTime( starttime, endtime ); + + CountSelected(); + int scount = GetNumSelected(); + + COLORREF bgColor = RGB( 230, 230, 200 ); + if ( IsCollapsed() && active ) + { + bgColor = RGB( 200, 230, 200 ); + } + + RECT rcClient = m_rcBounds; + + drawHelper.DrawFilledRect( bgColor, rcClient ); + + COLORREF gray = RGB( 200, 200, 200 ); + + DrawEventEnd( drawHelper ); + + DrawRelativeTags( drawHelper ); + if ( !IsCollapsed() && track ) + { + if ( m_nEditType == 1 ) + { + float zero = track->GetZeroValue( m_nEditType, true ); + + drawHelper.DrawColoredLine( RGB( 180, 200, 220 ), PS_SOLID, 1, + rcClient.left, ( rcClient.top * zero + rcClient.bottom * (1 - zero)) , + rcClient.right, ( rcClient.top * zero + rcClient.bottom * (1 - zero)) ); + } + + drawHelper.DrawOutlinedRect( RGB( 100, 150, 200 ), PS_SOLID, 1, rcClient ); + + // Draw grow handle into background... + if ( CanHaveGrowHandle() ) + { + RECT handleRect; + GetGrowHandleRect( handleRect ); + DrawGrowHandle( drawHelper, handleRect ); + } + + // Draw left/right underneath amount so go backbard + for ( int type = ( track->IsComboType() ? 1 : 0 ); type >= 0; type-- ) + { + COLORREF lineColor = ( type == m_nEditType ) ? RGB( 0, 0, 255 ) : gray; + COLORREF shadowColor = ( type == m_nEditType ) ? RGB( 150, 150, 250 ) : gray; + COLORREF dotColor = ( type == m_nEditType ) ? RGB( 0, 0, 255 ) : gray; + COLORREF dotColorSelected = ( type == m_nEditType ) ? RGB( 240, 80, 20 ) : gray; + + int height = rcClient.bottom - rcClient.top; + int bottom = rcClient.bottom; + + // Fixme, could look at 1st derivative and do more sampling at high rate of change? + // or near actual sample points! + float linelength = g_pExpressionTool->IsFocusItem( this ) ? 2.0f : 8.0f; + + float timestepperpixel = linelength / g_pExpressionTool->GetPixelsPerSecond(); + + float stoptime = min( endtime, e->GetDuration() ); + + float prev_t = starttime; + float prev_value = track->GetFracIntensity( prev_t, type ); + + CUtlVector< POINT > segments; + + /* + if (type == m_nEditType) + { + // draw hermite version of time step + float i0, i1, i2; + float time10hz = starttime; + + i0 = track->GetFracIntensity( time10hz, type ); + i1 = i0; + time10hz = starttime + 0.1; + i2 = track->GetFracIntensity( time10hz, type );; + + for ( float t = starttime; t <= stoptime; t += timestepperpixel ) + { + while (t >= time10hz) + { + time10hz += 0.1; + i0 = i1; + i1 = i2; + i2 = track->GetFracIntensity( time10hz, type );; + } + + float value = Hermite_Spline( i0, i1, i2, (t - time10hz + 0.1) / 0.1 ); + + int prevx, x; + + bool clipped1, clipped2; + x = GetMouseForTime( t, &clipped1 ); + prevx = GetMouseForTime( prev_t, &clipped2 ); + + //if ( !clipped1 && !clipped2 ) + { + // Draw segment + //drawHelper.DrawColoredLine( lineColor, PS_SOLID, 1, + // prevx, bottom - prev_value * height, + // x, bottom - value * height ); + + POINT pt; + + if ( segments.Count() == 0 ) + { + pt.x = prevx; + pt.y = bottom - prev_value * height; + + segments.AddToTail( pt ); + } + + pt.x = x; + pt.y = bottom - value * height; + + segments.AddToTail( pt ); + } + + prev_t = t; + prev_value = value; + } + + if ( segments.Count() >= 2 ) + { + drawHelper.DrawColoredPolyLine( shadowColor, PS_SOLID, 1, segments ); + } + + segments.RemoveAll(); + } + */ + for ( float t = starttime; t <= stoptime; t += timestepperpixel ) + { + float value = track->GetFracIntensity( t, type ); + + int prevx, x; + + bool clipped1, clipped2; + x = GetMouseForTime( t, &clipped1 ); + prevx = GetMouseForTime( prev_t, &clipped2 ); + + //if ( !clipped1 && !clipped2 ) + { + // Draw segment + //drawHelper.DrawColoredLine( lineColor, PS_SOLID, 1, + // prevx, bottom - prev_value * height, + // x, bottom - value * height ); + + POINT pt; + + if ( segments.Count() == 0 ) + { + pt.x = prevx; + pt.y = bottom - prev_value * height; + + segments.AddToTail( pt ); + } + + pt.x = x; + pt.y = bottom - value * height; + + segments.AddToTail( pt ); + } + + prev_t = t; + prev_value = value; + } + + if ( segments.Count() >= 2 ) + { + drawHelper.DrawColoredPolyLine( lineColor, PS_SOLID, 1, segments ); + } + + for ( int sample = 0; sample < track->GetNumSamples( type ); sample++ ) + { + bool dummy; + CExpressionSample *start = track->GetBoundedSample( sample, dummy, type ); + + /* + int pixel = (int)( ( start->time / event_time ) * width + 0.5f); + int x = m_rcBounds.left + pixel; + float roundedfrac = (float)pixel / (float)width; + */ + float value = start->value; // track->GetFracIntensity( start->time, type ); + bool clipped = false; + int x = GetMouseForTime( start->time, &clipped ); + if ( clipped ) + continue; + int y = bottom - value * height; + + int dotsize = 6; + int dotSizeSelected = 6; + + COLORREF clr = dotColor; + COLORREF clrSelected = dotColorSelected; + + drawHelper.DrawCircle( + start->selected ? clrSelected : clr, + x, y, + start->selected ? dotSizeSelected : dotsize, + true ); + + + if ( !start->selected ) + continue; + + if ( start->GetCurveType() == CURVE_DEFAULT ) + continue; + + // Draw curve type indicator... + char sz[ 128 ]; + Q_snprintf( sz, sizeof( sz ), "%s", Interpolator_NameForCurveType( start->GetCurveType(), true ) ); + RECT rc; + int fontSize = 9; + rc.top = clamp( y + 5, rcClient.top + 2, rcClient.bottom - 2 - fontSize ); + rc.bottom = rc.top + fontSize + 1; + rc.left = x - 75; + rc.right = x + 175; + drawHelper.DrawColoredText( "Arial", fontSize, 500, shadowColor, rc, sz ); + } + } + } + + if ( track && track->IsComboType() && !IsCollapsed() ) + { + RECT title = rcClient; + title.left += 10; + title.top += 14; + title.bottom = title.top + 9; + + char sz[ 128 ]; + + if ( m_nEditType == 1 ) + { + sprintf( sz, "left" ); + + drawHelper.DrawColoredText( "Arial", 9, 500, RGB( 0, 0, 255 ), title, sz ); + + sprintf( sz, "right" ); + + title.top = rcClient.bottom - 22; + title.bottom = rcClient.bottom; + + drawHelper.DrawColoredText( "Arial", 9, 500, RGB( 0, 0, 255 ), title, sz ); + } + + int mid = ( rcClient.top + rcClient.bottom ) / 2; + + title.top = mid - 10; + title.bottom = mid; + + sprintf( sz, "editmode: <%s>", m_nEditType == 0 ? "amount" : "left/right" ); + + drawHelper.DrawColoredText( "Arial", 9, 500, RGB( 0, 0, 255 ), title, sz ); + } + + if ( track ) + { + RECT title = rcClient; + title.left += 2; + title.top += 2; + title.bottom = title.top + 9; + + char const *name = track->GetFlexControllerName(); + char sz[ 128 ]; + + if ( scount > 0 ) + { + sprintf( sz, "{%i} - ", scount ); + + int len = drawHelper.CalcTextWidth( "Arial", 9, 500, sz ); + drawHelper.DrawColoredText( "Arial", 9, 500, + RGB( 120, 120, 0 ), title, sz ); + + title.left += len + 2; + } + + sprintf( sz, "%s -", name ); + + int len = drawHelper.CalcTextWidth( "Arial", 9, 500, sz ); + + drawHelper.DrawColoredText( "Arial", 9, 500, + active ? RGB( 0, 150, 100 ) : RGB( 100, 100, 100 ), + title, sz ); + + sprintf( sz, "%s", IsActive() ? "enabled" : "disabled" ); + + title.left += len + 2; + + len = drawHelper.CalcTextWidth( "Arial", 9, 500, sz ); + drawHelper.DrawColoredText( "Arial", 9, 500, + active ? RGB( 0, 150, 100 ) : RGB( 100, 100, 100 ), + title, sz ); + + if ( active ) + { + title.left += len + 2; + + sprintf( sz, " <%i>", track->GetNumSamples( 0 ) ); + + len = drawHelper.CalcTextWidth( "Arial", 9, 500, sz ); + drawHelper.DrawColoredText( "Arial", 9, 500, RGB( 220, 0, 00 ), title, sz ); + } + } +} + +void TimelineItem::DrawAutoHighlight( mxEvent *event ) +{ + if ( IsCollapsed() ) + return; + + CFlexAnimationTrack *track = GetSafeTrack(); + if ( !track ) + return; + + CExpressionSample *hover = GetSampleUnderMouse( event->x, event->y, 0.0f ); + CChoreoWidgetDrawHelper drawHelper( m_pWorkspace, m_rcBounds, true ); + + RECT rcClient = m_rcBounds; + + // Draw left/right underneath amount so go backbard + int type = m_nEditType; + + COLORREF dotColor = RGB( 0, 0, 255 ); + COLORREF dotColorSelected = RGB( 240, 80, 20 ); + COLORREF clrHighlighted = RGB( 0, 200, 0 ); + + int height = rcClient.bottom - rcClient.top; + int bottom = rcClient.bottom; + + int dotsize = 6; + int dotSizeSelected = 6; + int dotSizeHighlighted = 6; + + COLORREF clr = dotColor; + COLORREF clrSelected = dotColorSelected; + COLORREF bgColor = RGB( 230, 230, 200 ); + + // Fixme, could look at 1st derivative and do more sampling at high rate of change? + // or near actual sample points! + for ( int sample = 0; sample < track->GetNumSamples( type ); sample++ ) + { + bool dummy; + CExpressionSample *start = track->GetBoundedSample( sample, dummy, type ); + + float value = start->value; + bool clipped = false; + int x = GetMouseForTime( start->time, &clipped ); + if ( clipped ) + continue; + int y = bottom - value * height; + + if ( hover == start ) + { + drawHelper.DrawCircle( + bgColor, + x, y, + dotSizeHighlighted, + true ); + + drawHelper.DrawCircle( + clrHighlighted, + x, y, + dotSizeHighlighted, + false ); + + + } + else + { + drawHelper.DrawCircle( + start->selected ? clrSelected : clr, + x, y, + start->selected ? dotSizeSelected : dotsize, + true ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : drawHelper - +//----------------------------------------------------------------------------- +void TimelineItem::DrawRelativeTags( CChoreoWidgetDrawHelper& drawHelper ) +{ + CFlexAnimationTrack *track = GetSafeTrack(); + if ( !track ) + return; + + CChoreoEvent *event = track->GetEvent(); + if ( !event ) + return; + + float duration = event->GetDuration(); + + if ( duration <= 0.0f ) + return; + + CChoreoScene *scene = g_pChoreoView->GetScene(); + if ( !scene ) + return; + + RECT rcClient = m_rcBounds;; + //drawHelper.GetClientRect( rcClient ); + + // Iterate relative tags + for ( int i = 0; i < scene->GetNumActors(); i++ ) + { + CChoreoActor *a = scene->GetActor( i ); + if ( !a ) + continue; + + for ( int j = 0; j < a->GetNumChannels(); j++ ) + { + CChoreoChannel *c = a->GetChannel( j ); + if ( !c ) + continue; + + for ( int k = 0 ; k < c->GetNumEvents(); k++ ) + { + CChoreoEvent *e = c->GetEvent( k ); + if ( !e ) + continue; + + // add each tag to combo box + for ( int t = 0; t < e->GetNumRelativeTags(); t++ ) + { + CEventRelativeTag *tag = e->GetRelativeTag( t ); + if ( !tag ) + continue; + + //SendMessage( control, CB_ADDSTRING, 0, (LPARAM)va( "\"%s\" \"%s\"", tag->GetName(), e->GetParameters() ) ); + bool clipped = false; + int tagx = GetMouseForTime( tag->GetStartTime() - event->GetStartTime(), &clipped ); + if ( clipped ) + continue; + + drawHelper.DrawColoredLine( RGB( 180, 180, 220 ), PS_SOLID, 1, tagx, rcClient.top, tagx, rcClient.bottom ); + } + } + } + } + + for ( int t = 0; t < event->GetNumTimingTags(); t++ ) + { + CFlexTimingTag *tag = event->GetTimingTag( t ); + if ( !tag ) + continue; + + bool clipped = false; + int tagx = GetMouseForTime( tag->GetStartTime() - event->GetStartTime(), &clipped ); + if ( clipped ) + continue; + + // Draw relative tag marker + drawHelper.DrawColoredLine( RGB( 220, 180, 180 ), PS_SOLID, 1, tagx, rcClient.top, tagx, rcClient.bottom ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *exp - +// flexnum - +//----------------------------------------------------------------------------- +void TimelineItem::SetExpressionInfo( CFlexAnimationTrack *track, int flexnum ) +{ + m_szTrackName[ 0 ] = 0; + if ( track ) + { + V_strcpy_safe( m_szTrackName, track->GetFlexControllerName() ); + SetActive( track->IsTrackActive() ); + } + + m_nFlexNum = flexnum; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TimelineItem::Copy( void ) +{ + if ( !g_pExpressionTool ) + return; + + CFlexAnimationTrack *track = GetSafeTrack(); + if ( !track ) + return; + + g_pExpressionTool->Copy( track ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TimelineItem::Paste( void ) +{ + if ( !g_pExpressionTool ) + return; + + if ( !g_pExpressionTool->HasCopyData() ) + return; + + CFlexAnimationTrack *track = GetSafeTrack(); + if ( !track ) + return; + + g_pExpressionTool->Paste( track ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TimelineItem::Clear( bool preserveundo ) +{ + CFlexAnimationTrack *track = GetSafeTrack(); + if ( !track ) + return; + + if ( preserveundo ) + { + PreDataChanged( "Clear" ); + } + + track->Clear(); + + if ( preserveundo ) + { + PostDataChanged( "Clear" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : state - +//----------------------------------------------------------------------------- +void TimelineItem::SetCollapsed( bool state ) +{ + m_bCollapsed = state; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool TimelineItem::IsCollapsed( void ) const +{ + return m_bCollapsed; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int TimelineItem::GetHeight( void ) +{ + return ( IsCollapsed() ? 12 : m_nCurrentHeight ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : state - +//----------------------------------------------------------------------------- +void TimelineItem::SetActive( bool state ) +{ + CFlexAnimationTrack *track = GetSafeTrack(); + if ( !track ) + return; + + track->SetTrackActive( state ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool TimelineItem::IsActive( void ) +{ + CFlexAnimationTrack *track = GetSafeTrack(); + if ( !track ) + return false; + + return track->IsTrackActive(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool TimelineItem::IsValid( void ) +{ + CFlexAnimationTrack *track = GetSafeTrack(); + if ( !track ) + return false; + + if ( track->GetNumSamples( 0 ) > 0 ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CFlexAnimationTrack +//----------------------------------------------------------------------------- +CFlexAnimationTrack *TimelineItem::GetSafeTrack( void ) +{ + if ( !g_pExpressionTool ) + return NULL; + + CChoreoEvent *ev = g_pExpressionTool->GetSafeEvent(); + if ( !ev ) + return NULL; + + // Find track by name + for ( int i = 0; i < ev->GetNumFlexAnimationTracks() ; i++ ) + { + CFlexAnimationTrack *track = ev->GetFlexAnimationTrack( i ); + if ( track && !stricmp( track->GetFlexControllerName(), m_szTrackName ) ) + { + return track; + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - +//----------------------------------------------------------------------------- +void TimelineItem::SetEditType( int type ) +{ + Assert( type == 0 || type == 1 ); + + CFlexAnimationTrack *track = GetSafeTrack(); + if ( !track || !track->IsComboType() ) + { + type = 0; + } + + m_nEditType = type; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int TimelineItem::GetEditType( void ) +{ + return m_nEditType; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TimelineItem::SelectPoints( void ) +{ + RECT rcSelection; + + rcSelection.left = m_nStartX < m_nLastX ? m_nStartX : m_nLastX; + rcSelection.right = m_nStartX < m_nLastX ? m_nLastX : m_nStartX; + + rcSelection.top = m_nStartY < m_nLastY ? m_nStartY : m_nLastY; + rcSelection.bottom = m_nStartY < m_nLastY ? m_nLastY : m_nStartY; + + int selW = rcSelection.right - rcSelection.left; + int selH = rcSelection.bottom - rcSelection.top; + + float tolerance = FP_TL_SELECTION_RECTANGLE_TOLERANCE; + // If they are just clicking and releasing in one spot, capture any items w/in a larger tolerance + if ( selW <= 2 && selH <= 2 ) + { + tolerance = FP_TL_SELECTION_TOLERANCE; + + CExpressionSample *sample = GetSampleUnderMouse( rcSelection.left + selW * 0.5f, rcSelection.top + selH * 0.5f ); + if ( sample ) + { + sample->selected = true; + return; + } + } + else + { + InflateRect( &rcSelection, 3, 3 ); + } + + int width = m_rcBounds.right - m_rcBounds.left; + int height = m_rcBounds.bottom - m_rcBounds.top; + + CFlexAnimationTrack *track = GetSafeTrack(); + if ( !track || !width || !height ) + return; + + CChoreoEvent *e = track->GetEvent(); + Assert( e ); + if ( !e ) + return; + + float duration = e->GetDuration(); + + float fleft = (float)GetTimeForMouse( rcSelection.left + m_rcBounds.left ); + float fright = (float)GetTimeForMouse( rcSelection.right + m_rcBounds.left ); + + //fleft *= duration; + //fright *= duration; + + float ftop = (float)rcSelection.top / (float)height; + float fbottom = (float)rcSelection.bottom / (float)height; + + fleft = clamp( fleft, 0.0f, duration ); + fright = clamp( fright, 0.0f, duration ); + ftop = clamp( ftop, 0.0f, 1.0f ); + fbottom = clamp( fbottom, 0.0f, 1.0f ); + + float timestepperpixel = 1.0f / g_pExpressionTool->GetPixelsPerSecond(); + float yfracstepperpixel = 1.0f / (float)height; + + float epsx = tolerance*timestepperpixel; + float epsy = tolerance*yfracstepperpixel; + + for ( int i = 0; i < track->GetNumSamples( m_nEditType ); i++ ) + { + CExpressionSample *sample = track->GetSample( i, m_nEditType ); + + if ( sample->time + epsx < fleft ) + continue; + + if ( sample->time - epsx > fright ) + continue; + + if ( (1.0f - sample->value ) + epsy < ftop ) + continue; + + if ( (1.0f - sample->value ) - epsy > fbottom ) + continue; + + sample->selected = true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *undodescription - +//----------------------------------------------------------------------------- +void TimelineItem::PreDataChanged( char const *undodescription ) +{ + if ( m_nUndoSetup == 0 ) + { + g_pChoreoView->SetDirty( true ); + g_pChoreoView->PushUndo( undodescription ); + } + ++m_nUndoSetup; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *redodescription - +//----------------------------------------------------------------------------- +void TimelineItem::PostDataChanged( char const *redodescription ) +{ + --m_nUndoSetup; + if ( m_nUndoSetup == 0 ) + { + g_pChoreoView->PushRedo( redodescription ); + g_pExpressionTool->InvalidateLayout(); + } +} + +void TimelineItem::SetBounds( const RECT& rect ) +{ + m_rcBounds = rect; +} + +void TimelineItem::GetBounds( RECT& rect ) +{ + rect = m_rcBounds; +} + +void TimelineItem::SetVisible( bool vis ) +{ + m_bVisible = vis; +} + +bool TimelineItem::GetVisible( void ) const +{ + return m_bVisible; +} + +int TimelineItem::GetNumSelected( void ) +{ + return m_nNumSelected; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TimelineItem::SnapAll() +{ + CFlexAnimationTrack *track = GetSafeTrack(); + if ( !track ) + return; + + for ( int t = 0; t < 2; t++ ) + { + for ( int i = 0; i < track->GetNumSamples( t ); i++ ) + { + CExpressionSample *sample = track->GetSample( i, t ); + sample->time = FacePoser_SnapTime( sample->time ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TimelineItem::SnapSelected() +{ + CFlexAnimationTrack *track = GetSafeTrack(); + if ( !track ) + return; + + for ( int t = 0; t < 2; t++ ) + { + for ( int i = 0; i < track->GetNumSamples( t ); i++ ) + { + CExpressionSample *sample = track->GetSample( i, t ); + if ( !sample->selected ) + continue; + + sample->time = FacePoser_SnapTime( sample->time ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : start - +// end - +//----------------------------------------------------------------------------- +void TimelineItem::DeletePoints( float start, float end ) +{ + CFlexAnimationTrack *track = GetSafeTrack(); + if ( !track ) + return; + + for ( int t = 0; t < 2; t++ ) + { + int num = track->GetNumSamples( t ); + for ( int i = num - 1; i >= 0 ; i-- ) + { + CExpressionSample *sample = track->GetSample( i, t ); + if ( sample->time < start || sample->time > end ) + continue; + + track->RemoveSample( i, t ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TimelineItem::OnDoubleClicked() +{ + // Disabled for now by request of BillF + CFlexAnimationTrack *track = GetSafeTrack(); + if ( !track ) + return; + + SetCollapsed( !IsCollapsed() ); + g_pExpressionTool->LayoutItems( true ); +} + +void TimelineItem::DrawEventEnd( CChoreoWidgetDrawHelper& drawHelper ) +{ + CFlexAnimationTrack *track = GetSafeTrack(); + if ( !track ) + return; + + CChoreoEvent *e = track->GetEvent(); + if ( !e ) + return; + + float duration = e->GetDuration(); + if ( !duration ) + return; + + int leftx = GetMouseForTime( duration ); + if ( leftx > m_rcBounds.right ) + return; + + drawHelper.DrawColoredLine( + COLOR_CHOREO_ENDTIME, PS_SOLID, 1, + leftx, m_rcBounds.top, leftx, m_rcBounds.bottom ); + +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : helper - +// handleRect - +//----------------------------------------------------------------------------- +void TimelineItem::DrawGrowHandle( CChoreoWidgetDrawHelper& helper, RECT& handleRect ) +{ + Assert(CanHaveGrowHandle()); + + RECT useRect = handleRect; + helper.OffsetSubRect( useRect ); + + POINT region[4]; + int cPoints = 4; + + region[ 0 ].x = useRect.left + GROW_HANDLE_INSETPIXELS; + region[ 0 ].y = useRect.top; + + region[ 1 ].x = useRect.right - GROW_HANDLE_INSETPIXELS; + region[ 1 ].y = useRect.top; + + region[ 2 ].x = useRect.right; + region[ 2 ].y = useRect.bottom; + + region[ 3 ].x = useRect.left; + region[ 3 ].y = useRect.bottom; + + HDC dc = helper.GrabDC(); + + HRGN rgn = CreatePolygonRgn( region, cPoints, ALTERNATE ); + + int oldPF = SetPolyFillMode( dc, ALTERNATE ); + + HBRUSH brBg = CreateSolidBrush( RGB( 150, 150, 150 ) ); + HBRUSH brBorder = CreateSolidBrush( RGB( 200, 200, 200 ) ); + + FillRgn( dc, rgn, brBg ); + FrameRgn( dc, rgn, brBorder, 1, 1 ); + + SetPolyFillMode( dc, oldPF ); + + DeleteObject( rgn ); + + DeleteObject( brBg ); + DeleteObject( brBorder ); + + // draw a line in the middle + int midy = ( handleRect.bottom + handleRect.top ) * 0.5f; + int lineinset = GROW_HANDLE_INSETPIXELS *1.5; + + helper.DrawColoredLine( RGB( 63, 63, 63 ), PS_SOLID, 1, + handleRect.left + lineinset, midy, + handleRect.right - lineinset, midy ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : rc - +//----------------------------------------------------------------------------- +void TimelineItem::GetGrowHandleRect( RECT& rc ) +{ + rc = m_rcBounds; + rc.bottom -= 1; + rc.top = rc.bottom - GROW_HANDLE_HEIGHT; + rc.left = ( rc.right + rc.left ) / 2 - GROW_HANDLE_WIDTH / 2; + rc.right = rc.left + GROW_HANDLE_WIDTH; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool TimelineItem::CanHaveGrowHandle() +{ + if ( IsCollapsed() ) + return false; + + if ( !g_pExpressionTool->IsFocusItem( this ) ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : x - +// y - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool TimelineItem::IsMouseOverGrowHandle( int x, int y) +{ + if ( !CanHaveGrowHandle() ) + return false; + + RECT rcGrowHandle; + GetGrowHandleRect( rcGrowHandle ); + + POINT pt; + pt.x = x + m_rcBounds.left; + pt.y = y + m_rcBounds.top; + + return PtInRect( &rcGrowHandle, pt ) ? true : false; +} + +void TimelineItem::DrawGrowRect() +{ + RECT rcFocus = m_rcBounds; + rcFocus.bottom = m_rcBounds.top + m_nLastY; + OffsetRect( &rcFocus, -m_rcBounds.left, -m_rcBounds.top ); + + POINT offset; + offset.x = m_rcBounds.left; + offset.y = m_rcBounds.top; + ClientToScreen( (HWND)m_pWorkspace->getHandle(), &offset ); + OffsetRect( &rcFocus, offset.x, offset.y ); + + HDC dc = GetDC( NULL ); + + ::DrawFocusRect( dc, &rcFocus ); + + ReleaseDC( NULL, dc ); +} + +void TimelineItem::GetWorkList( bool reflect, CUtlVector< TimelineItem * >& list ) +{ + if ( !reflect ) + { + list.AddToTail( this ); + } + else + { + g_pExpressionTool->GetTimelineItems( list ); + } +} diff --git a/utils/hlfaceposer/timelineitem.h b/utils/hlfaceposer/timelineitem.h new file mode 100644 index 0000000..34cc472 --- /dev/null +++ b/utils/hlfaceposer/timelineitem.h @@ -0,0 +1,161 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TIMELINEITEM_H +#define TIMELINEITEM_H +#ifdef _WIN32 +#pragma once +#endif + +#include <mxtk/mx.h> +#include "utlvector.h" +#include "ExpressionSample.h" + +class CExpression; +class ExpressionTool; +class CFlexAnimationTrack; +class CChoreoWidgetDrawHelper; +class CChoreoView; + +template< class T > class CCurveEditorHelper; + +#define FP_TL_SELECTION_TOLERANCE 30.0f +#define FP_TL_SELECTION_RECTANGLE_TOLERANCE 5.0f +#define FP_TL_ADDSAMPLE_TOLERANCE 5.0f + +class TimelineItem +{ +public: + // Construction + TimelineItem( mxWindow *workspace ); + ~TimelineItem( void ); + + virtual int handleEvent( mxEvent *event ); + virtual void Draw( CChoreoWidgetDrawHelper& drawHelper ); + void DrawEventEnd( CChoreoWidgetDrawHelper& drawHelper ); + void DrawSelf( void ); + + void SetExpressionInfo( CFlexAnimationTrack *track, int flexnum ); + + void Clear( bool preserveundo ); + + void SetCollapsed( bool state ); + bool IsCollapsed( void ) const; + + void SetActive( bool state ); + bool IsActive( void ); + + int GetHeight( void ); + void ResetHeight(); + + // If samples > 0 + bool IsValid( void ); + + void SetEditType( int type ); + int GetEditType( void ); + + void SetBounds( const RECT& rect ); + + void GetBounds( RECT& rect ); + + void SetVisible( bool vis ); + bool GetVisible( void ) const; + + int CountSelected( void ); + CFlexAnimationTrack *GetSafeTrack( void ); + int GetNumSelected( void ); + + void Copy( void ); + void Paste( void ); + + void SelectAll( void ); + void DeselectAll( void ); + void Delete( void ); + + void GetLastMouse( int& mx, int& my ) + { + mx = m_nLastX; + my = m_nLastY; + } + + void SnapAll(); + void SnapSelected(); + void DeletePoints( float start, float end ); + + float GetTimeForMouse( int mx, bool clip = false ); + int GetMouseForTime( float t, bool *clipped = NULL ); + + void SetMousePositionForEvent( mxEvent *event ); + + int NumSamples(); + CExpressionSample *GetSample( int idx ); + void PreDataChanged( char const *undodescription ); + void PostDataChanged( char const *redodescription ); + CExpressionSample *GetSampleUnderMouse( int mx, int my, float tolerance = FP_TL_SELECTION_TOLERANCE ); + void GetWorkList( bool reflect, CUtlVector< TimelineItem * >& list ); + +private: + enum + { + DRAGTYPE_NONE = 0, + DRAGTYPE_MOVEPOINTS_VALUE, + DRAGTYPE_MOVEPOINTS_TIME, + DRAGTYPE_SELECTION, + DRAGTYPE_GROW, + }; + + + bool CanHaveGrowHandle(); + void DrawGrowHandle( CChoreoWidgetDrawHelper& helper, RECT& handleRect ); + void GetGrowHandleRect( RECT& rc ); + bool IsMouseOverGrowHandle( int x, int y); + + void MouseDrag( int x, int y, int modifiers, bool snap = false ); + + void DrawGrowRect(); + + void DrawFocusRect( void ); + void SelectPoints( void ); + + void OnDoubleClicked( void ); + + void DrawAutoHighlight( mxEvent *event ); + + int m_nDragging; + int m_nLastX; + int m_nLastY; + + int m_nStartX; + int m_nStartY; + + void AddSample( CExpressionSample const& sample ); + + void DrawRelativeTags( CChoreoWidgetDrawHelper& drawHelper ); + + int m_nNumSelected; + + int m_nFlexNum; + + bool m_bCollapsed; + + char m_szTrackName[ 128 ]; + + int m_nEditType; + + int m_nUndoSetup; + RECT m_rcBounds; + bool m_bVisible; + + mxWindow *m_pWorkspace; + double m_flLastClickTime; + + int m_nCurrentHeight; + + CCurveEditorHelper< TimelineItem > *m_pHelper; +}; + +#endif // TIMELINEITEM_H diff --git a/utils/hlfaceposer/vcd1.ico b/utils/hlfaceposer/vcd1.ico Binary files differnew file mode 100644 index 0000000..249d061 --- /dev/null +++ b/utils/hlfaceposer/vcd1.ico diff --git a/utils/hlfaceposer/vcdbrowser.cpp b/utils/hlfaceposer/vcdbrowser.cpp new file mode 100644 index 0000000..0aa6263 --- /dev/null +++ b/utils/hlfaceposer/vcdbrowser.cpp @@ -0,0 +1,881 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include <windows.h> +#include "resource.h" +#include "vcdbrowser.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "filesystem.h" +#include "tabwindow.h" +#include "inputproperties.h" +#include "choreowidgetdrawhelper.h" +#include "UtlBuffer.h" +#include "ChoreoEvent.h" +#include "ChoreoView.h" + +CVCDBrowser *g_pVCDBrowser = NULL; + +enum +{ + // Controls + IDC_VB_LISTVIEW = 101, + IDC_VB_FILETREE, + // Messages + IDC_VB_OPENVCD = 1000, +}; + +enum +{ + COL_VCD = 0, +}; + +class CVCDList : public mxListView +{ +public: + CVCDList( mxWindow *parent, int id = 0 ) + : mxListView( parent, 0, 0, 0, 0, id ) + { + // Add column headers + insertTextColumn( COL_VCD, 700, "VCD" ); + } +}; + +class CUtlSymbolTree : public mxTreeView +{ +public: + CUtlSymbolTree( mxWindow *parent, int id = 0 ) : mxTreeView( parent, 0, 0, 0, 0, id ), + m_Paths( 0, 0, FileTreeLessFunc ) + { + } + + void Clear() + { + removeAll(); + m_Paths.RemoveAll(); + } + + void FindOrAddSubdirectory( char const *subdir ) + { + FileTreePath fp; + Q_strcpy( fp.path, subdir ); + + if ( m_Paths.Find( fp ) != m_Paths.InvalidIndex() ) + return; + + m_Paths.Insert( fp ); + } + + mxTreeViewItem *FindOrAddChildItem( mxTreeViewItem *parent, char const *child ) + { + mxTreeViewItem *p = getFirstChild( parent ); + if ( !p ) + { + return add( parent, child ); + } + + while ( p ) + { + if ( !Q_stricmp( getLabel( p ), child ) ) + return p; + + p = getNextChild( p ); + } + + return add( parent, child ); + } + + void _PopulateTree( int pathId, char const *path ) + { + char sz[ 512 ]; + Q_strcpy( sz, path ); + char *p = sz; + + // Start at root + mxTreeViewItem *cur = NULL; + + // Tokenize path + while ( p && p[0] ) + { + char *slash = Q_strstr( p, "/" ); + if ( !slash ) + { + slash = Q_strstr( p, "\\" ); + } + + char *check = p; + + if ( slash ) + { + *slash = 0; + + // see if a child of current already exists with this name + p = slash + 1; + } + else + { + p = NULL; + } + + Assert( check ); + + cur = FindOrAddChildItem( cur, check ); + } + + setUserData( cur, (void *)pathId ); + } + + char const *GetSelectedPath( void ) + { + mxTreeViewItem *tvi = getSelectedItem(); + unsigned int id = (unsigned int)getUserData( tvi ); + + if ( id < 0 || id >= m_Paths.Count() ) + { + Assert( 0 ); + return ""; + } + return m_Paths[ id ].path; + } + + void PopulateTree() + { + int i; + for ( i = m_Paths.FirstInorder(); i != m_Paths.InvalidIndex(); i = m_Paths.NextInorder( i ) ) + { + _PopulateTree( i, m_Paths[ i ].path ); + } + + mxTreeViewItem *p = getFirstChild( NULL ); + setOpen( p, true ); + } + + struct FileTreePath + { + char path[ MAX_PATH ]; + }; + + static bool FileTreeLessFunc( const FileTreePath &lhs, const FileTreePath &rhs ) + { + return Q_stricmp( lhs.path, rhs.path ) < 0; + } + + CUtlRBTree< FileTreePath, int > m_Paths; +}; + +#pragma optimize( "", off ) +class CVCDOptionsWindow : public mxWindow +{ +typedef mxWindow BaseClass; +public: + enum + { + IDC_OPENFILE = 1000, + IDC_SEARCH, + IDC_CANCELSEARCH, + }; + + CVCDOptionsWindow( CVCDBrowser *browser ) : BaseClass( browser, 0, 0, 0, 0 ), m_pBrowser( browser ) + { + FacePoser_AddWindowStyle( this, WS_CLIPSIBLINGS | WS_CLIPCHILDREN ); + + m_szSearchString[0]=0; + + m_pOpen = new mxButton( this, 0, 0, 0, 0, "Open", IDC_OPENFILE ); + + m_pSearch = new mxLineEdit( this, 0, 0, 0, 0, "", IDC_SEARCH ); + + m_pCancelSearch = new mxButton( this, 0, 0, 0, 0, "Cancel", IDC_CANCELSEARCH ); + } + + bool PaintBackground( void ) + { + redraw(); + return false; + } + + + virtual void redraw() + { + CChoreoWidgetDrawHelper drawHelper( this, GetSysColor( COLOR_BTNFACE ) ); + } + virtual int handleEvent( mxEvent *event ) + { + int iret = 0; + switch ( event->event ) + { + default: + break; + case mxEvent::Size: + { + iret = 1; + + int split = 120; + + int x = 1; + + m_pOpen->setBounds( x, 1, split, h2() - 2 ); + + + x += split + 10; + + m_pCancelSearch->setBounds( x, 1, split, h2() - 2 ); + + x += split + 10; + + m_pSearch->setBounds( x, 0, split * 3, h2() - 1 ); + + x += split * 3 + 10; + } + break; + case mxEvent::KeyDown: + switch ( event->action ) + { + default: + break; + case IDC_SEARCH: + { + if ( event->event == mxEvent::KeyDown ) + { + OnSearch(); + } + iret = 1; + }; + break; + } + break; + case mxEvent::Action: + { + switch ( event->action ) + { + case IDC_SEARCH: + iret = 1; + break; + case IDC_OPENFILE: + { + iret = 1; + m_pBrowser->OnOpen(); + } + break; + case IDC_CANCELSEARCH: + { + iret = 1; + OnCancelSearch(); + } + break; + default: + break; + } + } + break; + } + + return iret; + } + + char const *GetSearchString() + { + return m_szSearchString; + } + + void OnSearch() + { + m_pSearch->getText( m_szSearchString, sizeof( m_szSearchString ) ); + + m_pBrowser->OnSearch(); + } + + void OnCancelSearch() + { + m_szSearchString[ 0 ] = 0; + m_pSearch->clear(); + + m_pBrowser->OnCancelSearch(); + } + +private: + + mxButton *m_pOpen; + mxLineEdit *m_pSearch; + mxButton *m_pCancelSearch; + + CVCDBrowser *m_pBrowser; + + char m_szSearchString[ 256 ]; +}; + +#pragma optimize( "", on ) +//----------------------------------------------------------------------------- +// Purpose: +// Input : *parent - +//----------------------------------------------------------------------------- +CVCDBrowser::CVCDBrowser( mxWindow *parent ) + : IFacePoserToolWindow( "VCDBrowser", "VCDs" ), mxWindow( parent, 0, 0, 0, 0 ) +{ + SetAutoProcess( false ); + + m_bTextSearch = false; + m_nPrevProcessed = -1; + + m_pListView = new CVCDList( this, IDC_VB_LISTVIEW ); + m_pOptions = new CVCDOptionsWindow( this ); + m_pFileTree = new CUtlSymbolTree( this, IDC_VB_FILETREE ); + + //HIMAGELIST list = CreateImageList(); + + // Associate the image list with the tree-view control. + //m_pListView->setImageList( (void *)list ); + + LoadAllSounds(); + + PopulateTree( NULL ); +} + +#define CX_ICON 16 +#define CY_ICON 16 + +HIMAGELIST CVCDBrowser::CreateImageList() +{ + HIMAGELIST list; + + list = ImageList_Create( CX_ICON, CY_ICON, + FALSE, VCD_NUM_IMAGES, 0 ); + + // Load the icon resources, and add the icons to the image list. + HICON hicon; + int slot; +#if defined( DBGFLAG_ASSERT ) + int c = 0; +#endif + + hicon = LoadIcon(GetModuleHandle( 0 ), MAKEINTRESOURCE(IDI_VCD)); + slot = ImageList_AddIcon(list, hicon); + Assert( slot == c++ ); + DeleteObject( hicon ); + + return list; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVCDBrowser::OnDelete() +{ + RemoveAllSounds(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +// Output : int +//----------------------------------------------------------------------------- +int CVCDBrowser::handleEvent( mxEvent *event ) +{ + int iret = 0; + + if ( HandleToolEvent( event ) ) + { + return iret; + } + + switch ( event->event ) + { + default: + break; + case mxEvent::Action: + { + iret = 1; + switch ( event->action ) + { + default: + { + iret = 0; + } + break; + case IDC_VB_LISTVIEW: + { + SetActiveTool( this ); + + bool rightmouse = ( event->flags == mxEvent::RightClicked ) ? true : false; + bool doubleclicked = ( event->flags == mxEvent::DoubleClicked ) ? true : false; + + if ( rightmouse ) + { + ShowContextMenu(); + } + else if ( doubleclicked ) + { + if ( m_pListView->getNumSelected() == 1 ) + { + int index = m_pListView->getNextSelectedItem( -1 ); + if ( index >= 0 ) + { + FileNameHandle_t vcd = (FileNameHandle_t)m_pListView->getUserData( index, 0 ); + OpenVCD( vcd ); + } + } + } + } + break; + case IDC_VB_FILETREE: + { + SetActiveTool( this ); + + PopulateTree( m_pFileTree->GetSelectedPath() ); + } + break; + case IDC_VB_OPENVCD: + { + OnOpen(); + } + break; + } + } + break; + case mxEvent::Size: + { + int optionsh = 20; + + m_pOptions->setBounds( 0, 0, w2(), optionsh ); + + int filetreewidth = 175; + + m_pFileTree->setBounds( 0, optionsh, filetreewidth, h2() - optionsh ); + m_pListView->setBounds( filetreewidth, optionsh, w2() - filetreewidth, h2() - optionsh ); + + iret = 1; + } + break; + case mxEvent::Close: + { + iret = 1; + } + break; + } + + return iret; +} + +bool CVCDBrowser::CNameLessFunc::Less( const FileNameHandle_t &name1, const FileNameHandle_t &name2, void *pContext ) +{ + if ( name1 < name2 ) + return true; + return false; +} + +void CVCDBrowser::OpenVCD( const FileNameHandle_t& handle ) +{ + char fn[ 512 ]; + if ( filesystem->String( handle, fn, sizeof( fn ) ) ) + { + char pFullPath[MAX_PATH]; + const char *pFileName = filesystem->RelativePathToFullPath( fn, "GAME", pFullPath, sizeof(pFullPath) ); + if ( !pFileName ) + { + pFileName = fn; + } + g_pChoreoView->LoadSceneFromFile( pFileName ); + } +} + +#define SCENES_PREFIX_LEN 0 +//----------------------------------------------------------------------------- +// Finds all .vcd files in a particular directory +//----------------------------------------------------------------------------- +bool CVCDBrowser::LoadVCDsFilesInDirectory( CUtlSortVector< FileNameHandle_t, CNameLessFunc >& soundlist, char const* pDirectoryName, int nDirectoryNameLen ) +{ + char *pWildCard; + pWildCard = ( char * )stackalloc( nDirectoryNameLen + 7 ); + Q_snprintf( pWildCard, nDirectoryNameLen + 7, "%s/*.vcd", pDirectoryName ); + + if ( !filesystem ) + { + return false; + } + + FileFindHandle_t findHandle; + const char *pFileName = filesystem->FindFirst( pWildCard, &findHandle ); + while( pFileName ) + { + if( !filesystem->FindIsDirectory( findHandle ) ) + { + // Strip off the 'sound/' part of the name. + char *pFileNameWithPath; + int nAllocSize = nDirectoryNameLen + Q_strlen(pFileName) + 2; + pFileNameWithPath = (char *)stackalloc( nAllocSize ); + Q_snprintf( pFileNameWithPath, nAllocSize, "%s/%s", &pDirectoryName[ SCENES_PREFIX_LEN ], pFileName ); + Q_strnlwr( pFileNameWithPath, nAllocSize ); + + FileNameHandle_t vcd; + vcd = filesystem->FindOrAddFileName( pFileNameWithPath ); + soundlist.InsertNoSort( vcd ); + } + pFileName = filesystem->FindNext( findHandle ); + } + + m_pFileTree->FindOrAddSubdirectory( &pDirectoryName[ SCENES_PREFIX_LEN ] ); + + filesystem->FindClose( findHandle ); + return true; +} + +bool CVCDBrowser::InitDirectoryRecursive( CUtlSortVector< FileNameHandle_t, CNameLessFunc >& soundlist, char const* pDirectoryName ) +{ + // Compute directory name length + int nDirectoryNameLen = Q_strlen( pDirectoryName ); + + if (!LoadVCDsFilesInDirectory( soundlist, pDirectoryName, nDirectoryNameLen ) ) + return false; + + char *pWildCard = ( char * )stackalloc( nDirectoryNameLen + 4 ); + strcpy(pWildCard, pDirectoryName); + strcat(pWildCard, "/*."); + int nPathStrLen = nDirectoryNameLen + 1; + + FileFindHandle_t findHandle; + const char *pFileName = filesystem->FindFirst( pWildCard, &findHandle ); + while( pFileName ) + { + if ((pFileName[0] != '.') || (pFileName[1] != '.' && pFileName[1] != 0)) + { + if( filesystem->FindIsDirectory( findHandle ) ) + { + int fileNameStrLen = Q_strlen( pFileName ); + char *pFileNameWithPath = ( char * )stackalloc( nPathStrLen + fileNameStrLen + 1 ); + memcpy( pFileNameWithPath, pWildCard, nPathStrLen ); + pFileNameWithPath[nPathStrLen] = '\0'; + strcat( pFileNameWithPath, pFileName ); + + if (!InitDirectoryRecursive( soundlist, pFileNameWithPath )) + return false; + } + } + pFileName = filesystem->FindNext( findHandle ); + } + + return true; +} + +void CVCDBrowser::LoadAllSounds() +{ + RemoveAllSounds(); + + Con_Printf( "Building list of all .vcds in sound/ folder\n" ); + + InitDirectoryRecursive( m_AllVCDs, "scenes" ); + m_AllVCDs.RedoSort(); + + m_pFileTree->PopulateTree(); +} + +void CVCDBrowser::RemoveAllSounds() +{ + m_AllVCDs.Purge(); + m_Scripts.RemoveAll(); + m_CurrentSelection.RemoveAll(); + + m_pFileTree->Clear(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVCDBrowser::PopulateTree( char const *subdirectory ) +{ + char subdir[ 512 ]; + subdir[ 0 ] = 0; + + int i; + + CUtlSortVector< FileNameHandle_t, CNameLessFunc > sorted( 0, 0 ); + + char const *texttofind = NULL; + + if ( m_bTextSearch ) + { + subdirectory = NULL; + texttofind = GetSearchString(); + } + + int len = 0; + if ( subdirectory ) + { + len = Q_strlen( subdirectory ); + Q_strncpy( subdir, subdirectory, sizeof( subdir ) ); + Q_strlower( subdir ); + Q_FixSlashes( subdir ); + } + + int c = m_AllVCDs.Count(); + for ( i = 0; i < c; i++ ) + { + const FileNameHandle_t &vcd = m_AllVCDs[ i ]; + char name[ 512 ]; + if ( !filesystem->String( vcd, name, sizeof( name ) ) ) + continue; + + if ( subdirectory ) + { + if ( Q_strnicmp( subdir, name, len ) ) + continue; + } + + if ( m_bTextSearch && texttofind ) + { + if ( !Q_stristr( name, texttofind ) ) + continue; + } + + sorted.InsertNoSort( vcd ); + } + + sorted.RedoSort(); + + char prevSelectedName[ 512 ]; + prevSelectedName[ 0 ] = 0; + if ( m_pListView->getNumSelected() == 1 ) + { + int selectedItem = m_pListView->getNextSelectedItem( 0 ); + if ( selectedItem >= 0 ) + { + // Grab name of previously selected item + Q_strcpy( prevSelectedName, m_pListView->getLabel( selectedItem, 0 ) ); + } + } + +// Repopulate tree + m_pListView->removeAll(); + + int loadcount = 0; + + m_pListView->setDrawingEnabled( false ); + + int selectedSlot = -1; + + for ( i = 0; i < sorted.Count(); ++i ) + { + const FileNameHandle_t &vcd = sorted[ i ]; + char name[ 512 ]; + if ( !filesystem->String( vcd, name, sizeof( name ) ) ) + continue; + + int slot = m_pListView->add( name ); + m_pListView->setUserData( slot, COL_VCD, (void *)vcd ); + + if ( !Q_stricmp( prevSelectedName, name ) ) + { + selectedSlot = slot; + } + ++loadcount; + } + + m_pListView->setDrawingEnabled( true ); + + if ( selectedSlot != -1 ) + { + m_pListView->setSelected( selectedSlot, true ); + m_pListView->scrollToItem( selectedSlot ); + } +} + +void CVCDBrowser::RepopulateTree() +{ + PopulateTree( m_pFileTree->GetSelectedPath() ); +} + +void CVCDBrowser::BuildSelectionList( CUtlVector< FileNameHandle_t >& selected ) +{ + selected.RemoveAll(); + + int idx = -1; + do + { + idx = m_pListView->getNextSelectedItem( idx ); + if ( idx != -1 ) + { + FileNameHandle_t vcd = (FileNameHandle_t)m_pListView->getUserData( idx, 0 ); + selected.AddToTail( vcd ); + } + } while ( idx != -1 ); + +} + +void CVCDBrowser::ShowContextMenu( void ) +{ + SetActiveTool( this ); + + BuildSelectionList( m_CurrentSelection ); + if ( m_CurrentSelection.Count() <= 0 ) + return; + + POINT pt; + GetCursorPos( &pt ); + ScreenToClient( (HWND)getHandle(), &pt ); + + mxPopupMenu *pop = new mxPopupMenu(); + + if ( m_CurrentSelection.Count() == 1 && m_CurrentSelection[ 0 ] ) + { + char sz[ 512 ]; + char name[ 512 ]; + if ( filesystem->String( m_CurrentSelection[ 0 ], name, sizeof( name ) ) ) + { + Q_snprintf( sz, sizeof( sz ), "&Open '%s'", name ); + pop->add ( sz, IDC_VB_OPENVCD ); + } + } + + pop->popup( this, pt.x, pt.y ); +} + +void CVCDBrowser::OnOpen() +{ + SetActiveTool( this ); + + BuildSelectionList( m_CurrentSelection ); + if ( m_CurrentSelection.Count() == 1 ) + { + FileNameHandle_t& vcd = m_CurrentSelection[ 0 ]; + OpenVCD( vcd ); + } +} + +static void SplitFileName( char const *in, char *path, int maxpath, char *filename, int maxfilename ) +{ + char drive[_MAX_DRIVE]; + char dir[_MAX_DIR]; + char fname[_MAX_FNAME]; + char ext[_MAX_EXT]; + + _splitpath( in, drive, dir, fname, ext ); + + if ( dir[0] ) + { + Q_snprintf( path, maxpath, "\\%s", dir ); + } + else + { + path[0] = 0; + } + Q_snprintf( filename, maxfilename, "%s%s", fname, ext ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *se - +//----------------------------------------------------------------------------- +void CVCDBrowser::JumpToItem( const FileNameHandle_t& vcd ) +{ + SetActiveTool( this ); + + char path[ 256 ]; + char filename[ 256 ]; + + char vcdfile[ 512 ]; + if ( !filesystem->String( vcd, vcdfile, sizeof( vcdfile ) ) ) + return; + + SplitFileName( vcdfile, path, sizeof( path ), filename, sizeof( filename ) ); + + char *usepath = path + Q_strlen( "/scenes/" ); + PopulateTree( usepath ); + + int idx = 0; + int c = m_pListView->getItemCount(); + for ( ; idx < c; idx++ ) + { + FileNameHandle_t item = (FileNameHandle_t)m_pListView->getUserData( idx, 0 ); + if ( item == vcd ) + { + break; + } + } + + if ( idx < c ) + { + m_pListView->scrollToItem( idx ); + } +} + +int CVCDBrowser::GetVCDCount() const +{ + return m_AllVCDs.Count(); +} + +FileNameHandle_t CVCDBrowser::GetVCD( int index ) +{ + if ( index < 0 || index >= (int)m_AllVCDs.Count() ) + return NULL; + + return m_AllVCDs[ index ]; +} + + +void CVCDBrowser::OnSearch() +{ + if ( !GetSearchString()[ 0 ] ) + { + OnCancelSearch(); + return; + } + + SetActiveTool( this ); + m_bTextSearch = true; + PopulateTree( GetSearchString()); +} + +void CVCDBrowser::OnCancelSearch() +{ + SetActiveTool( this ); + + m_bTextSearch = false; + + PopulateTree( m_pFileTree->GetSelectedPath() ); +} + +char const *CVCDBrowser::GetSearchString() +{ + return m_pOptions->GetSearchString(); +} + +void CVCDBrowser::SetCurrent( char const *filename ) +{ +// Get sound name and look up .vcd from it + char const *p = filename; + if ( p && + ( !Q_strnicmp( p, "sound/", 6 ) || !Q_strnicmp( p, "sound\\", 6 ) ) ) + { + p += 6; + } + + char fn[ 512 ]; + Q_strncpy( fn, p, sizeof( fn ) ); + Q_FixSlashes( fn ); + + int i; + int c = m_pListView->getItemCount(); + + for ( i = 0; i < c; ++i ) + { + FileNameHandle_t vcd = (FileNameHandle_t)( m_pListView->getUserData( i, COL_VCD ) ); + + char fixed[ 512 ]; + if ( !filesystem->String( vcd, fixed, sizeof( fixed ) ) ) + continue; + + Q_FixSlashes( fixed ); + + if ( !Q_stricmp( fixed, fn ) ) + { + m_pListView->scrollToItem( i ); + m_pListView->setSelected( i, true ); + } + else + { + m_pListView->setSelected( i, false ); + } + } +} diff --git a/utils/hlfaceposer/vcdbrowser.h b/utils/hlfaceposer/vcdbrowser.h new file mode 100644 index 0000000..d2e82ff --- /dev/null +++ b/utils/hlfaceposer/vcdbrowser.h @@ -0,0 +1,120 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef VCDBROWSER_H +#define VCDBROWSER_H +#ifdef _WIN32 +#pragma once +#endif + +#include "mxtk/mxListView.h" +#include "commctrl.h" +#include "utldict.h" +#include "faceposertoolwindow.h" +#include "filesystem.h" +#include "tier1/UtlSortVector.h" + +class CVCDList; +class CUtlSymbolTree; +class CVCDOptionsWindow; +// class CChoreoEvent; + +struct _IMAGELIST; +typedef struct _IMAGELIST NEAR* HIMAGELIST; + +enum +{ +/// IMAGE_WORKSPACE = 0, +// IMAGE_WORKSPACE_CHECKEDOUT, +// IMAGE_PROJECT, +// IMAGE_PROJECT_CHECKEDOUT, +// IMAGE_SCENE, +// IMAGE_SCENE_CHECKEDOUT, +// IMAGE_VCD, +// IMAGE_VCD_CHECKEDOUT, +// IMAGE_WAV, +// IMAGE_WAV_CHECKEDOUT, +// IMAGE_SPEAK, +// IMAGE_SPEAK_CHECKEDOUT, + + VCD_NUM_IMAGES, +}; + +class CVCDBrowser : public mxWindow, public IFacePoserToolWindow +{ + typedef mxWindow BaseClass; +public: + + CVCDBrowser( mxWindow *parent ); + + virtual int handleEvent( mxEvent *event ); + virtual void OnDelete(); + + void RepopulateTree(); + + void BuildSelectionList( CUtlVector< FileNameHandle_t >& selected ); + + void OnOpen(); + + void JumpToItem( const FileNameHandle_t& vcd ); + + int GetVCDCount() const; + FileNameHandle_t GetVCD( int index ); + + void OnSearch(); + void OnCancelSearch(); + + HIMAGELIST CreateImageList(); + + void SetCurrent( char const *fn ); + +private: + + class CNameLessFunc + { + public: + bool Less( const FileNameHandle_t &name1, const FileNameHandle_t &name2, void *pContext ); + }; + + + void OpenVCD( const FileNameHandle_t& handle ); + + char const *GetSearchString(); + + bool LoadVCDsFilesInDirectory( CUtlSortVector< FileNameHandle_t, CNameLessFunc >& soundlist, char const* pDirectoryName, int nDirectoryNameLen ); + bool InitDirectoryRecursive( CUtlSortVector< FileNameHandle_t, CNameLessFunc >& soundlist, char const* pDirectoryName ); + + void PopulateTree( char const *subdirectory ); + + void ShowContextMenu( void ); + + void LoadAllSounds(); + void RemoveAllSounds(); + + CVCDList *m_pListView; + + enum + { + NUM_BITMAPS = 6, + }; + + CUtlSortVector< FileNameHandle_t, CNameLessFunc > m_AllVCDs; + CUtlSymbolTable m_ScriptTable; + + CUtlVector< CUtlSymbol > m_Scripts; + + CVCDOptionsWindow *m_pOptions; + CUtlSymbolTree *m_pFileTree; + + CUtlVector< FileNameHandle_t > m_CurrentSelection; + + int m_nPrevProcessed; + bool m_bTextSearch; +}; + +extern CVCDBrowser *g_pVCDBrowser; + +#endif // VCDBROWSER_H diff --git a/utils/hlfaceposer/wav1.ico b/utils/hlfaceposer/wav1.ico Binary files differnew file mode 100644 index 0000000..d4ae51b --- /dev/null +++ b/utils/hlfaceposer/wav1.ico diff --git a/utils/hlfaceposer/wavebrowser.cpp b/utils/hlfaceposer/wavebrowser.cpp new file mode 100644 index 0000000..c961369 --- /dev/null +++ b/utils/hlfaceposer/wavebrowser.cpp @@ -0,0 +1,1182 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//===========================================================================// + +#include "cbase.h" +#include <windows.h> +#include "resource.h" +#include "wavefile.h" +#include "wavebrowser.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "ifaceposersound.h" +#include "snd_wave_source.h" +#include "filesystem.h" +#include "tabwindow.h" +#include "inputproperties.h" +#include "choreowidgetdrawhelper.h" +#include "ifileloader.h" +#include "tier2/riff.h" +#include "UtlBuffer.h" +#include "ChoreoEvent.h" + +CWaveBrowser *g_pWaveBrowser = NULL; + +//----------------------------------------------------------------------------- +// Purpose: Implements the RIFF i/o interface on stdio +//----------------------------------------------------------------------------- +class StdIOReadBinary : public IFileReadBinary +{ +public: + int open( const char *pFileName ) + { + return (int)filesystem->Open( pFileName, "rb" ); + } + + int read( void *pOutput, int size, int file ) + { + if ( !file ) + return 0; + + return filesystem->Read( pOutput, size, (FileHandle_t)file ); + } + + void seek( int file, int pos ) + { + if ( !file ) + return; + + filesystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD ); + } + + unsigned int tell( int file ) + { + if ( !file ) + return 0; + + return filesystem->Tell( (FileHandle_t)file ); + } + + unsigned int size( int file ) + { + if ( !file ) + return 0; + + return filesystem->Size( (FileHandle_t)file ); + } + + void close( int file ) + { + if ( !file ) + return; + + filesystem->Close( (FileHandle_t)file ); + } +}; + +class StdIOWriteBinary : public IFileWriteBinary +{ +public: + int create( const char *pFileName ) + { + return (int)filesystem->Open( pFileName, "wb" ); + } + + int write( void *pData, int size, int file ) + { + return filesystem->Write( pData, size, (FileHandle_t)file ); + } + + void close( int file ) + { + filesystem->Close( (FileHandle_t)file ); + } + + void seek( int file, int pos ) + { + filesystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD ); + } + + unsigned int tell( int file ) + { + return filesystem->Tell( (FileHandle_t)file ); + } +}; + +static StdIOReadBinary io_in; +static StdIOWriteBinary io_out; + +#define RIFF_WAVE MAKEID('W','A','V','E') +#define WAVE_FMT MAKEID('f','m','t',' ') +#define WAVE_DATA MAKEID('d','a','t','a') +#define WAVE_FACT MAKEID('f','a','c','t') +#define WAVE_CUE MAKEID('c','u','e',' ') + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &walk - +//----------------------------------------------------------------------------- +static void SceneManager_ParseSentence( CSentence& sentence, IterateRIFF &walk ) +{ + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + + buf.EnsureCapacity( walk.ChunkSize() ); + walk.ChunkRead( buf.Base() ); + buf.SeekPut( CUtlBuffer::SEEK_HEAD, walk.ChunkSize() ); + + sentence.InitFromDataChunk( buf.Base(), buf.TellPut() ); +} + +bool SceneManager_LoadSentenceFromWavFileUsingIO( char const *wavfile, CSentence& sentence, IFileReadBinary& io ) +{ + sentence.Reset(); + + InFileRIFF riff( wavfile, io ); + + // UNDONE: Don't use printf to handle errors + if ( riff.RIFFName() != RIFF_WAVE ) + { + return false; + } + + // set up the iterator for the whole file (root RIFF is a chunk) + IterateRIFF walk( riff, riff.RIFFSize() ); + + // This chunk must be first as it contains the wave's format + // break out when we've parsed it + bool found = false; + while ( walk.ChunkAvailable() && !found ) + { + switch( walk.ChunkName() ) + { + case WAVE_VALVEDATA: + { + found = true; + SceneManager_ParseSentence( sentence, walk ); + } + break; + } + walk.ChunkNext(); + } + + return true; +} + +bool SceneManager_LoadSentenceFromWavFile( char const *wavfile, CSentence& sentence ) +{ + return SceneManager_LoadSentenceFromWavFileUsingIO( wavfile, sentence, io_in ); +} + +enum +{ + // Controls + IDC_SB_LISTVIEW = 101, + IDC_SB_FILETREE, + + // Messages + IDC_SB_PLAY = 1000, +}; + +enum +{ + COL_WAV = 0, + COL_DUCKED, + COL_PHONEMES, + COL_SENTENCE +}; + +class CWaveList : public mxListView +{ +public: + CWaveList( mxWindow *parent, int id = 0 ) + : mxListView( parent, 0, 0, 0, 0, id ) + { + // Add column headers + insertTextColumn( COL_WAV, 300, "WAV" ); + insertTextColumn( COL_DUCKED, 50, "Ducked" ); + insertTextColumn( COL_PHONEMES, 120, "Words [ Phonemes ]" ); + insertTextColumn( COL_SENTENCE, 300, "Sentence Text" ); + } +}; + +class CWaveFileTree : public mxTreeView +{ +public: + CWaveFileTree( mxWindow *parent, int id = 0 ) : mxTreeView( parent, 0, 0, 0, 0, id ), + m_Paths( 0, 0, FileTreeLessFunc ) + { + } + + void Clear() + { + removeAll(); + m_Paths.RemoveAll(); + } + + void FindOrAddSubdirectory( char const *subdir ) + { + FileTreePath fp; + Q_strcpy( fp.path, subdir ); + + if ( m_Paths.Find( fp ) != m_Paths.InvalidIndex() ) + return; + + m_Paths.Insert( fp ); + } + + mxTreeViewItem *FindOrAddChildItem( mxTreeViewItem *parent, char const *child ) + { + mxTreeViewItem *p = getFirstChild( parent ); + if ( !p ) + { + return add( parent, child ); + } + + while ( p ) + { + if ( !Q_stricmp( getLabel( p ), child ) ) + return p; + + p = getNextChild( p ); + } + + return add( parent, child ); + } + + void _PopulateTree( int pathId, char const *path ) + { + char sz[ 512 ]; + Q_strcpy( sz, path ); + char *p = sz; + + // Start at root + mxTreeViewItem *cur = NULL; + + // Tokenize path + while ( p && p[0] ) + { + char *slash = Q_strstr( p, "/" ); + if ( !slash ) + { + slash = Q_strstr( p, "\\" ); + } + + char *check = p; + + if ( slash ) + { + *slash = 0; + + // see if a child of current already exists with this name + p = slash + 1; + } + else + { + p = NULL; + } + + Assert( check ); + + cur = FindOrAddChildItem( cur, check ); + } + + setUserData( cur, (void *)pathId ); + } + + char const *GetSelectedPath( void ) + { + mxTreeViewItem *tvi = getSelectedItem(); + unsigned int id = (unsigned int)getUserData( tvi ); + + if ( id < 0 || id >= m_Paths.Count() ) + { + Assert( 0 ); + return ""; + } + return m_Paths[ id ].path; + } + + void PopulateTree() + { + int i; + for ( i = m_Paths.FirstInorder(); i != m_Paths.InvalidIndex(); i = m_Paths.NextInorder( i ) ) + { + _PopulateTree( i, m_Paths[ i ].path ); + } + + mxTreeViewItem *p = getFirstChild( NULL ); + setOpen( p, true ); + } + + struct FileTreePath + { + char path[ MAX_PATH ]; + }; + + static bool FileTreeLessFunc( const FileTreePath &lhs, const FileTreePath &rhs ) + { + return Q_stricmp( lhs.path, rhs.path ) < 0; + } + + CUtlRBTree< FileTreePath, int > m_Paths; +}; +#pragma optimize( "", off ) +class CWaveOptionsWindow : public mxWindow +{ +typedef mxWindow BaseClass; +public: + enum + { + IDC_PLAYSOUND = 1000, + IDC_STOP_SOUNDS, + IDC_SEARCH, + IDC_CANCELSEARCH, + }; + + CWaveOptionsWindow( CWaveBrowser *browser ) : BaseClass( browser, 0, 0, 0, 0 ), m_pBrowser( browser ) + { + FacePoser_AddWindowStyle( this, WS_CLIPSIBLINGS | WS_CLIPCHILDREN ); + + m_szSearchString[0]=0; + + m_pPlay = new mxButton( this, 0, 0, 0, 0, "Play", IDC_PLAYSOUND ); + + m_pStopSounds = new mxButton( this, 0, 0, 0, 0, "Stop Sounds", IDC_STOP_SOUNDS ); + + m_pSearch = new mxLineEdit( this, 0, 0, 0, 0, "", IDC_SEARCH ); + + m_pCancelSearch = new mxButton( this, 0, 0, 0, 0, "Cancel", IDC_CANCELSEARCH ); + } + + bool PaintBackground( void ) + { + redraw(); + return false; + } + + + virtual void redraw() + { + CChoreoWidgetDrawHelper drawHelper( this, GetSysColor( COLOR_BTNFACE ) ); + } + virtual int handleEvent( mxEvent *event ) + { + int iret = 0; + switch ( event->event ) + { + default: + break; + case mxEvent::Size: + { + iret = 1; + + int split = 120; + + int x = 1; + + m_pPlay->setBounds( x, 1, split, h2() - 2 ); + + x += split + 10; + + m_pStopSounds->setBounds( x, 1, split, h2()-2 ); + + x += split + 10; + + m_pCancelSearch->setBounds( x, 1, split, h2() - 2 ); + + x += split + 10; + + m_pSearch->setBounds( x, 0, split * 3, h2() - 1 ); + + x += split * 3 + 10; + } + break; + case mxEvent::KeyDown: + switch ( event->action ) + { + default: + break; + case IDC_SEARCH: + { + if ( event->event == mxEvent::KeyDown ) + { + OnSearch(); + } + iret = 1; + }; + break; + } + break; + case mxEvent::Action: + { + switch ( event->action ) + { + case IDC_STOP_SOUNDS: + { + iret = 1; + sound->StopAll(); + } + break; + case IDC_SEARCH: + iret = 1; + break; + case IDC_PLAYSOUND: + { + iret = 1; + m_pBrowser->OnPlay(); + } + break; + case IDC_CANCELSEARCH: + { + iret = 1; + OnCancelSearch(); + } + break; + default: + break; + } + } + break; + } + + return iret; + } + + char const *GetSearchString() + { + return m_szSearchString; + } + + void OnSearch() + { + m_pSearch->getText( m_szSearchString, sizeof( m_szSearchString ) ); + + m_pBrowser->OnSearch(); + } + + void OnCancelSearch() + { + m_szSearchString[ 0 ] = 0; + m_pSearch->clear(); + + m_pBrowser->OnCancelSearch(); + } + +private: + + mxButton *m_pStopSounds; + mxButton *m_pPlay; + mxLineEdit *m_pSearch; + mxButton *m_pCancelSearch; + + CWaveBrowser *m_pBrowser; + + char m_szSearchString[ 256 ]; +}; + +#pragma optimize( "", on ) +//----------------------------------------------------------------------------- +// Purpose: +// Input : *parent - +//----------------------------------------------------------------------------- +CWaveBrowser::CWaveBrowser( mxWindow *parent ) + : IFacePoserToolWindow( "WaveBrowser", "Waves" ), mxWindow( parent, 0, 0, 0, 0 ) +{ + SetAutoProcess( false ); + + m_bTextSearch = false; + m_nPrevProcessed = -1; + + m_pListView = new CWaveList( this, IDC_SB_LISTVIEW ); + m_pOptions = new CWaveOptionsWindow( this ); + m_pFileTree = new CWaveFileTree( this, IDC_SB_FILETREE ); + + //HIMAGELIST list = CreateImageList(); + + // Associate the image list with the tree-view control. + //m_pListView->setImageList( (void *)list ); + + LoadAllSounds(); + + PopulateTree( NULL ); +} + +#define CX_ICON 16 +#define CY_ICON 16 + +HIMAGELIST CWaveBrowser::CreateImageList() +{ + HIMAGELIST list; + + list = ImageList_Create( CX_ICON, CY_ICON, + FALSE, NUM_IMAGES, 0 ); + + // Load the icon resources, and add the icons to the image list. + HICON hicon; + int slot; +#if defined( DBGFLAG_ASSERT ) + int c = 0; +#endif + + /* + hicon = LoadIcon( GetModuleHandle( 0 ), MAKEINTRESOURCE(IDI_WORKSPACE)); + slot = ImageList_AddIcon(list, hicon); + Assert( slot == c++ ); + DeleteObject( hicon ); + + hicon = LoadIcon( GetModuleHandle( 0 ), MAKEINTRESOURCE(IDI_WORKSPACE_CHECKEDOUT)); + slot = ImageList_AddIcon(list, hicon); + Assert( slot == c++ ); + DeleteObject( hicon ); + + hicon = LoadIcon(GetModuleHandle( 0 ), MAKEINTRESOURCE(IDI_PROJECT)); + slot = ImageList_AddIcon(list, hicon); + Assert( slot == c++ ); + DeleteObject( hicon ); + + hicon = LoadIcon(GetModuleHandle( 0 ), MAKEINTRESOURCE(IDI_PROJECT_CHECKEDOUT)); + slot = ImageList_AddIcon(list, hicon); + Assert( slot == c++ ); + DeleteObject( hicon ); + + hicon = LoadIcon(GetModuleHandle( 0 ), MAKEINTRESOURCE(IDI_SCENE)); + slot = ImageList_AddIcon(list, hicon); + Assert( slot == c++ ); + DeleteObject( hicon ); + */ + +// hicon = LoadIcon(GetModuleHandle( 0 ), MAKEINTRESOURCE(IDI_SCENE_CHECKEDOUT)); +// slot = ImageList_AddIcon(list, hicon); +// Assert( slot == c++ ); +// DeleteObject( hicon ); + + /* + hicon = LoadIcon(GetModuleHandle( 0 ), MAKEINTRESOURCE(IDI_VCD)); + slot = ImageList_AddIcon(list, hicon); + Assert( slot == c++ ); + DeleteObject( hicon ); + + hicon = LoadIcon(GetModuleHandle( 0 ), MAKEINTRESOURCE(IDI_VCD_CHECKEDOUT )); + slot = ImageList_AddIcon(list, hicon); + Assert( slot == c++ ); + DeleteObject( hicon ); + */ + + hicon = LoadIcon(GetModuleHandle( 0 ), MAKEINTRESOURCE(IDI_WAV)); + slot = ImageList_AddIcon(list, hicon); + Assert( slot == c++ ); + DeleteObject( hicon ); + + /* + hicon = LoadIcon(GetModuleHandle( 0 ), MAKEINTRESOURCE(IDI_WAV_CHECKEDOUT)); + slot = ImageList_AddIcon(list, hicon); + Assert( slot == c++ ); + DeleteObject( hicon ); + + hicon = LoadIcon(GetModuleHandle( 0 ), MAKEINTRESOURCE(IDI_SPEAK)); + slot = ImageList_AddIcon(list, hicon); + Assert( slot == c++ ); + DeleteObject( hicon ); + + hicon = LoadIcon(GetModuleHandle( 0 ), MAKEINTRESOURCE(IDI_SPEAK_CHECKEDOUT)); + slot = ImageList_AddIcon(list, hicon); + Assert( slot == c++ ); + DeleteObject( hicon ); + */ + + return list; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWaveBrowser::OnDelete() +{ + RemoveAllSounds(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *event - +// Output : int +//----------------------------------------------------------------------------- +int CWaveBrowser::handleEvent( mxEvent *event ) +{ + int iret = 0; + + if ( HandleToolEvent( event ) ) + { + return iret; + } + + switch ( event->event ) + { + default: + break; + case mxEvent::Action: + { + iret = 1; + switch ( event->action ) + { + default: + { + iret = 0; + } + break; + case IDC_SB_LISTVIEW: + { + SetActiveTool( this ); + + bool rightmouse = ( event->flags == mxEvent::RightClicked ) ? true : false; + bool doubleclicked = ( event->flags == mxEvent::DoubleClicked ) ? true : false; + + if ( rightmouse ) + { + ShowContextMenu(); + } + else if ( doubleclicked ) + { + if ( m_pListView->getNumSelected() == 1 ) + { + int index = m_pListView->getNextSelectedItem( -1 ); + if ( index >= 0 ) + { + CWaveFile *wav = (CWaveFile *)m_pListView->getUserData( index, 0 ); + if ( wav ) + { + wav->Play(); + } + } + } + } + } + break; + case IDC_SB_FILETREE: + { + SetActiveTool( this ); + + PopulateTree( m_pFileTree->GetSelectedPath() ); + } + break; + case IDC_SB_PLAY: + { + OnPlay(); + } + break; + } + } + break; + case mxEvent::Size: + { + int optionsh = 20; + + m_pOptions->setBounds( 0, 0, w2(), optionsh ); + + int filetreewidth = 175; + + m_pFileTree->setBounds( 0, optionsh, filetreewidth, h2() - optionsh ); + m_pListView->setBounds( filetreewidth, optionsh, w2() - filetreewidth, h2() - optionsh ); + + iret = 1; + } + break; + case mxEvent::Close: + { + iret = 1; + } + break; + } + + return iret; +} + +static bool NameLessFunc( CWaveFile *const& name1, CWaveFile *const& name2 ) +{ + if ( Q_stricmp( name1->GetName(), name2->GetName() ) < 0 ) + return true; + return false; +} + +#define SOUND_PREFIX_LEN 6 +//----------------------------------------------------------------------------- +// Finds all .wav files in a particular directory +//----------------------------------------------------------------------------- +bool CWaveBrowser::LoadWaveFilesInDirectory( CUtlDict< CWaveFile *, int >& soundlist, char const* pDirectoryName, int nDirectoryNameLen ) +{ + Assert( Q_strnicmp( pDirectoryName, "sound", 5 ) == 0 ); + + char *pWildCard; + pWildCard = ( char * )stackalloc( nDirectoryNameLen + 7 ); + Q_snprintf( pWildCard, nDirectoryNameLen + 7, "%s/*.wav", pDirectoryName ); + + if ( !filesystem ) + { + return false; + } + + FileFindHandle_t findHandle; + const char *pFileName = filesystem->FindFirst( pWildCard, &findHandle ); + while( pFileName ) + { + if( !filesystem->FindIsDirectory( findHandle ) ) + { + // Strip off the 'sound/' part of the name. + char *pFileNameWithPath; + int nAllocSize = nDirectoryNameLen + Q_strlen(pFileName) + 2; + pFileNameWithPath = (char *)stackalloc( nAllocSize ); + Q_snprintf( pFileNameWithPath, nAllocSize, "%s/%s", &pDirectoryName[SOUND_PREFIX_LEN], pFileName ); + Q_strnlwr( pFileNameWithPath, nAllocSize ); + + CWaveFile *wav = new CWaveFile( pFileNameWithPath ); + soundlist.Insert( pFileNameWithPath, wav ); + + /* + if ( !(soundlist.Count() % 500 ) ) + { + Con_Printf( "CWaveBrowser: loaded %i sounds\n", soundlist.Count() ); + } + */ + } + pFileName = filesystem->FindNext( findHandle ); + } + + m_pFileTree->FindOrAddSubdirectory( &pDirectoryName[ SOUND_PREFIX_LEN ] ); + + filesystem->FindClose( findHandle ); + return true; +} + +bool CWaveBrowser::InitDirectoryRecursive( CUtlDict< CWaveFile *, int >& soundlist, char const* pDirectoryName ) +{ + // Compute directory name length + int nDirectoryNameLen = Q_strlen( pDirectoryName ); + + if (!LoadWaveFilesInDirectory( soundlist, pDirectoryName, nDirectoryNameLen ) ) + return false; + + char *pWildCard = ( char * )stackalloc( nDirectoryNameLen + 4 ); + strcpy(pWildCard, pDirectoryName); + strcat(pWildCard, "/*."); + int nPathStrLen = nDirectoryNameLen + 1; + + FileFindHandle_t findHandle; + const char *pFileName = filesystem->FindFirst( pWildCard, &findHandle ); + while( pFileName ) + { + if ((pFileName[0] != '.') || (pFileName[1] != '.' && pFileName[1] != 0)) + { + if( filesystem->FindIsDirectory( findHandle ) ) + { + int fileNameStrLen = Q_strlen( pFileName ); + char *pFileNameWithPath = ( char * )stackalloc( nPathStrLen + fileNameStrLen + 1 ); + memcpy( pFileNameWithPath, pWildCard, nPathStrLen ); + pFileNameWithPath[nPathStrLen] = '\0'; + strcat( pFileNameWithPath, pFileName ); + + if (!InitDirectoryRecursive( soundlist, pFileNameWithPath )) + return false; + } + } + pFileName = filesystem->FindNext( findHandle ); + } + + return true; +} + +void CWaveBrowser::LoadAllSounds() +{ + RemoveAllSounds(); + + Con_Printf( "Building list of all .wavs in sound/ folder\n" ); + + InitDirectoryRecursive( m_AllSounds, "sound" ); + + m_pFileTree->PopulateTree(); +} + +void CWaveBrowser::RemoveAllSounds() +{ + int c = m_AllSounds.Count(); + for ( int i = 0; i < c; i++ ) + { + CWaveFile *wav = m_AllSounds[ i ]; + delete wav; + } + + m_AllSounds.RemoveAll(); + m_Scripts.RemoveAll(); + m_CurrentSelection.RemoveAll(); + + m_pFileTree->Clear(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWaveBrowser::PopulateTree( char const *subdirectory ) +{ + int i; + + CUtlRBTree< CWaveFile *, int > m_Sorted( 0, 0, NameLessFunc ); + + bool check_load_sentence_data = false; + + char const *texttofind = NULL; + + if ( m_bTextSearch ) + { + subdirectory = NULL; + texttofind = GetSearchString(); + } + + int len = 0; + if ( subdirectory ) + { + len = Q_strlen( subdirectory ); + check_load_sentence_data = ( Q_strstr( subdirectory, "/" ) || subdirectory[0] ) ? true : false; + } + + int c = m_AllSounds.Count(); + for ( i = 0; i < c; i++ ) + { + CWaveFile *wav = m_AllSounds[ i ]; + char const *name = wav->GetName(); + + if ( subdirectory ) + { + if ( Q_strnicmp( subdirectory, wav->GetName(), len ) ) + continue; + } + + if ( m_bTextSearch && texttofind ) + { + if ( !Q_stristr( name, texttofind ) ) + continue; + } + + m_Sorted.Insert( wav ); + } + + char prevSelectedName[ 512 ]; + prevSelectedName[ 0 ] = 0; + if ( m_pListView->getNumSelected() == 1 ) + { + int selectedItem = m_pListView->getNextSelectedItem( 0 ); + if ( selectedItem >= 0 ) + { + // Grab wave name of previously selected item + Q_strcpy( prevSelectedName, m_pListView->getLabel( selectedItem, 0 ) ); + } + } + +// Repopulate tree + m_pListView->removeAll(); + + int loadcount = 0; + + m_pListView->setDrawingEnabled( false ); + + int selectedSlot = -1; + + CUtlVector< CWaveFile * > list; + + + for ( i = m_Sorted.FirstInorder(); i != m_Sorted.InvalidIndex(); i = m_Sorted.NextInorder( i ) ) + { + CWaveFile *wav = m_Sorted[ i ]; + char const *name = wav->GetName(); + + int slot = m_pListView->add( name ); + + if ( !Q_stricmp( prevSelectedName, name ) ) + { + selectedSlot = slot; + } + + if ( ( check_load_sentence_data || m_bTextSearch ) && + !wav->HasLoadedSentenceInfo() && !wav->IsAsyncLoading() ) + { + wav->SetAsyncLoading( true ); + list.AddToTail( wav ); + } + + // m_pListView->setImage( slot, COL_WAV, wav->GetIconIndex() ); + m_pListView->setUserData( slot, COL_WAV, (void *)wav ); + + if ( wav->HasLoadedSentenceInfo() ) + { + m_pListView->setLabel( slot, COL_DUCKED, wav->GetVoiceDuck() ? "yes" : "no" ); + m_pListView->setLabel( slot, COL_PHONEMES, wav->GetPhonemeCount() || wav->GetWordCount() ? va( "%i [ %i ]", wav->GetWordCount(), wav->GetPhonemeCount() ) : "" ); + m_pListView->setLabel( slot, COL_SENTENCE, wav->GetSentenceText() ); + } + else + { + m_pListView->setLabel( slot, COL_SENTENCE, "(loading...)" ); + } + + ++loadcount; + } + + m_pListView->setDrawingEnabled( true ); + + if ( selectedSlot != -1 ) + { + m_pListView->setSelected( selectedSlot, true ); + m_pListView->scrollToItem( selectedSlot ); + } + + if ( list.Count() > 0 ) + { + fileloader->AddWaveFilesToThread( list ); + } + + // Con_Printf( "CWaveBrowser: selected %i sounds\n", loadcount ); +} + +void CWaveBrowser::RepopulateTree() +{ + PopulateTree( m_pFileTree->GetSelectedPath() ); +} + +void CWaveBrowser::BuildSelectionList( CUtlVector< CWaveFile * >& selected ) +{ + selected.RemoveAll(); + + int idx = -1; + do + { + idx = m_pListView->getNextSelectedItem( idx ); + if ( idx != -1 ) + { + CWaveFile *wav = (CWaveFile *)m_pListView->getUserData( idx, 0 ); + if ( wav ) + { + selected.AddToTail( wav ); + } + } + } while ( idx != -1 ); + +} + +void CWaveBrowser::ShowContextMenu( void ) +{ + SetActiveTool( this ); + + BuildSelectionList( m_CurrentSelection ); + if ( m_CurrentSelection.Count() <= 0 ) + return; + + POINT pt; + GetCursorPos( &pt ); + ScreenToClient( (HWND)getHandle(), &pt ); + + // New scene, edit comments + mxPopupMenu *pop = new mxPopupMenu(); + + if ( m_CurrentSelection.Count() == 1 ) + { + pop->add ("&Play", IDC_SB_PLAY ); +// pop->addSeparator(); + } + +// pop->add( "Import Sentence Data", IDC_SB_IMPORTSENTENCE ); +// pop->add( "Export Sentence Data", IDC_SB_EXPORTSENTENCE ); + + pop->popup( this, pt.x, pt.y ); +} + +void CWaveBrowser::OnPlay() +{ + SetActiveTool( this ); + + BuildSelectionList( m_CurrentSelection ); + if ( m_CurrentSelection.Count() == 1 ) + { + CWaveFile *wav = m_CurrentSelection[ 0 ]; + if ( wav ) + { + wav->Play(); + } + } +} + +static void SplitFileName( char const *in, char *path, int maxpath, char *filename, int maxfilename ) +{ + char drive[_MAX_DRIVE]; + char dir[_MAX_DIR]; + char fname[_MAX_FNAME]; + char ext[_MAX_EXT]; + + _splitpath( in, drive, dir, fname, ext ); + + if ( dir[0] ) + { + Q_snprintf( path, maxpath, "\\%s", dir ); + } + else + { + path[0] = 0; + } + Q_snprintf( filename, maxfilename, "%s%s", fname, ext ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *se - +//----------------------------------------------------------------------------- +void CWaveBrowser::JumpToItem( CWaveFile *wav ) +{ + + SetActiveTool( this ); + + char path[ 256 ]; + char filename[ 256 ]; + + SplitFileName( wav->GetFileName(), path, sizeof( path ), filename, sizeof( filename ) ); + + char *usepath = path + Q_strlen( "/sound/" ); + PopulateTree( usepath ); + + int idx = 0; + int c = m_pListView->getItemCount(); + for ( ; idx < c; idx++ ) + { + CWaveFile *item = (CWaveFile *)m_pListView->getUserData( idx, 0 ); + if ( !Q_stricmp( item->GetFileName(), wav->GetFileName() ) ) + { + break; + } + } + + if ( idx < c ) + { + m_pListView->scrollToItem( idx ); + } +} + +CWaveFile *CWaveBrowser::FindEntry( char const *wavname, bool jump /*= false*/ ) +{ + int idx = m_AllSounds.Find( wavname ); + if ( idx != m_AllSounds.InvalidIndex() ) + { + CWaveFile *wav = m_AllSounds[ idx ]; + if ( jump ) + { + JumpToItem( wav ); + } + + return wav; + } + + return NULL; +} + +int CWaveBrowser::GetSoundCount() const +{ + return m_AllSounds.Count(); +} + +CWaveFile *CWaveBrowser::GetSound( int index ) +{ + if ( index < 0 || index >= (int)m_AllSounds.Count() ) + return NULL; + + return m_AllSounds[ index ]; +} + + +void CWaveBrowser::OnSearch() +{ + SetActiveTool( this ); + + m_bTextSearch = true; + + PopulateTree( GetSearchString()); +} + +void CWaveBrowser::OnCancelSearch() +{ + SetActiveTool( this ); + + m_bTextSearch = false; + + PopulateTree( m_pFileTree->GetSelectedPath() ); +} + +char const *CWaveBrowser::GetSearchString() +{ + return m_pOptions->GetSearchString(); +} + +void CWaveBrowser::Think( float dt ) +{ + int pending = fileloader->GetPendingLoadCount(); + if ( pending != m_nPrevProcessed ) + { + m_nPrevProcessed = pending; + + // Put into suffix of window title + if ( pending == 0 ) + { + SetSuffix( "" ); + } + else + { + SetSuffix( va( " - %i", pending ) ); + } + } + + int c = fileloader->ProcessCompleted(); + if ( c > 0 ) + { + RepopulateTree(); + } +} + +void CWaveBrowser::SetEvent( CChoreoEvent *event ) +{ + if ( event->GetType() != CChoreoEvent::SPEAK ) + return; + + SetCurrent( FacePoser_TranslateSoundName( event->GetParameters() ) ); +} + +void CWaveBrowser::SetCurrent( char const *filename ) +{ +// Get sound name and look up .wav from it + char const *p = filename; + if ( p && + ( !Q_strnicmp( p, "sound/", 6 ) || !Q_strnicmp( p, "sound\\", 6 ) ) ) + { + p += 6; + } + + char fn[ 512 ]; + Q_strncpy( fn, p, sizeof( fn ) ); + Q_FixSlashes( fn ); + + int i; + int c = m_pListView->getItemCount(); + + for ( i = 0; i < c; ++i ) + { + CWaveFile *wav = reinterpret_cast< CWaveFile * >( m_pListView->getUserData( i, COL_WAV ) ); + if ( !wav ) + continue; + + char fixed[ 512 ]; + Q_strncpy( fixed, wav->GetName(), sizeof( fixed ) ); + Q_FixSlashes( fixed ); + + if ( !Q_stricmp( fixed, fn ) ) + { + m_pListView->scrollToItem( i ); + m_pListView->setSelected( i, true ); + } + else + { + m_pListView->setSelected( i, false ); + } + } +} diff --git a/utils/hlfaceposer/wavebrowser.h b/utils/hlfaceposer/wavebrowser.h new file mode 100644 index 0000000..7959428 --- /dev/null +++ b/utils/hlfaceposer/wavebrowser.h @@ -0,0 +1,125 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef WAVEBROWSER_H +#define WAVEBROWSER_H +#ifdef _WIN32 +#pragma once +#endif + +#include "mxtk/mxListView.h" +#include "commctrl.h" +#include "utldict.h" +#include "faceposertoolwindow.h" + +class CWaveFile; +class CWaveList; +class CWaveFileTree; +class CWaveOptionsWindow; +class CChoreoEvent; + +struct _IMAGELIST; +typedef struct _IMAGELIST NEAR* HIMAGELIST; + +enum +{ +/// IMAGE_WORKSPACE = 0, +// IMAGE_WORKSPACE_CHECKEDOUT, +// IMAGE_PROJECT, +// IMAGE_PROJECT_CHECKEDOUT, +// IMAGE_SCENE, +// IMAGE_SCENE_CHECKEDOUT, +// IMAGE_VCD, +// IMAGE_VCD_CHECKEDOUT, +// IMAGE_WAV, +// IMAGE_WAV_CHECKEDOUT, +// IMAGE_SPEAK, +// IMAGE_SPEAK_CHECKEDOUT, + + NUM_IMAGES, +}; + +class CWaveBrowser : public mxWindow, public IFacePoserToolWindow +{ + typedef mxWindow BaseClass; +public: + + CWaveBrowser( mxWindow *parent ); + + virtual void Think( float dt ); + + virtual int handleEvent( mxEvent *event ); + virtual void OnDelete(); + + void RepopulateTree(); + + void BuildSelectionList( CUtlVector< CWaveFile * >& selected ); + + void OnPlay(); + + void JumpToItem( CWaveFile *wav ); + + CWaveFile *FindEntry( char const *wavname, bool jump = false ); + + + int GetSoundCount() const; + CWaveFile *GetSound( int index ); + + void OnSearch(); + void OnCancelSearch(); + + HIMAGELIST CreateImageList(); + + void SetEvent( CChoreoEvent *event ); + void SetCurrent( char const *fn ); + +private: + + char const *GetSearchString(); + + bool LoadWaveFilesInDirectory( CUtlDict< CWaveFile *, int >& soundlist, char const* pDirectoryName, int nDirectoryNameLen ); + bool InitDirectoryRecursive( CUtlDict< CWaveFile *, int >& soundlist, char const* pDirectoryName ); + + void OnWaveProperties(); + void OnEnableVoiceDucking(); + void OnDisableVoiceDucking(); +// void OnCheckout(); +// void OnCheckin(); + + void OnImportSentence(); + void OnExportSentence(); + + void PopulateTree( char const *subdirectory ); + + void ShowContextMenu( void ); + + void LoadAllSounds(); + void RemoveAllSounds(); + + CWaveList *m_pListView; + + enum + { + NUM_BITMAPS = 6, + }; + + CUtlDict< CWaveFile *, int > m_AllSounds; + CUtlSymbolTable m_ScriptTable; + + CUtlVector< CUtlSymbol > m_Scripts; + + CWaveOptionsWindow *m_pOptions; + CWaveFileTree *m_pFileTree; + + CUtlVector< CWaveFile * > m_CurrentSelection; + + int m_nPrevProcessed; + bool m_bTextSearch; +}; + +extern CWaveBrowser *g_pWaveBrowser; + +#endif // WAVEBROWSER_H diff --git a/utils/hlfaceposer/wavefile.cpp b/utils/hlfaceposer/wavefile.cpp new file mode 100644 index 0000000..83cd418 --- /dev/null +++ b/utils/hlfaceposer/wavefile.cpp @@ -0,0 +1,132 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "wavefile.h" +#include "wavebrowser.h" +#include "sentence.h" +#include "ifaceposersound.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "snd_wave_source.h" +#include "filesystem.h" +#include "UtlBuffer.h" +#include "phonemeeditor.h" + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +//----------------------------------------------------------------------------- +CWaveFile::CWaveFile( char const *filename ) +{ + m_bAsyncLoading = false; + + m_bSentenceLoaded = false; + + m_Sentence.Reset(); + + Q_strncpy( m_szName, filename, sizeof( m_szName ) ); + + Q_snprintf( m_szFileName, sizeof( m_szFileName ), "sound/%s", filename ); +} + +CWaveFile::~CWaveFile() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CWaveFile::GetLanguageId() +{ + return GetCloseCaptionLanguageId(); +} + +bool SceneManager_LoadSentenceFromWavFile( char const *wavfile, CSentence& sentence ); + +void CWaveFile::EnsureSentence() +{ + if ( m_bSentenceLoaded ) + return; + + m_bSentenceLoaded = true; + + if ( m_szFileName[ 0 ] ) + { + SceneManager_LoadSentenceFromWavFile( m_szFileName, m_Sentence ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWaveFile::HasLoadedSentenceInfo() const +{ + return m_bSentenceLoaded; +} + +char const *CWaveFile::GetName() const +{ + return m_szName; +} + +char const *CWaveFile::GetFileName() const +{ + return m_szFileName; +} + +char const *CWaveFile::GetSentenceText() +{ + EnsureSentence(); + return m_Sentence.GetText(); +} + +int CWaveFile::GetPhonemeCount() +{ + EnsureSentence(); + return m_Sentence.CountPhonemes(); +} + +int CWaveFile::GetWordCount() +{ + EnsureSentence(); + return m_Sentence.m_Words.Count(); +} + + +void CWaveFile::Play() +{ + Con_Printf( "Playing '%s' : '%s'\n", GetFileName(), GetSentenceText() ); + + g_pPhonemeEditor->SetCurrentWaveFile( GetFileName() ); + g_pPhonemeEditor->Play(); +} + + +bool CWaveFile::GetVoiceDuck() +{ + EnsureSentence(); + return m_Sentence.GetVoiceDuck(); +} + +int CWaveFile::GetIconIndex() const +{ + return 0; // IMAGE_WAV; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : sentence - +//----------------------------------------------------------------------------- +void CWaveFile::SetThreadLoadedSentence( CSentence& sentence ) +{ + if ( m_bSentenceLoaded ) + return; + + m_bSentenceLoaded = true; + m_Sentence = sentence; +}
\ No newline at end of file diff --git a/utils/hlfaceposer/wavefile.h b/utils/hlfaceposer/wavefile.h new file mode 100644 index 0000000..482e769 --- /dev/null +++ b/utils/hlfaceposer/wavefile.h @@ -0,0 +1,86 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef WAVEFILE_H +#define WAVEFILE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "sentence.h" + +class CAudioSource; + +class CWaveFile +{ +public: + // One or both may be valid + CWaveFile( char const *filename ); + + ~CWaveFile(); + + static int GetLanguageId(); + + char const *GetName() const; + char const *GetFileName() const; + + char const *GetSentenceText(); + + int GetPhonemeCount(); + int GetWordCount(); + + bool IsAsyncLoading() const { return m_bAsyncLoading; } + void SetAsyncLoading( bool async ) { m_bAsyncLoading = async; } + + bool HasLoadedSentenceInfo() const; + void EnsureSentence(); + + void Play(); + + + bool GetVoiceDuck(); + /* + void SetVoiceDuck( bool duck ); + void ToggleVoiceDucking(); + + virtual void Checkout( bool updatestateicons = true ); + virtual void Checkin( bool updatestateicons = true ); + + bool IsCheckedOut() const; + */ + + int GetIconIndex() const; + + void SetThreadLoadedSentence( CSentence& sentence ); + +// void ExportValveDataChunk( char const *tempfile ); +// void ImportValveDataChunk( char const *tempfile ); + +// void GetPhonemeExportFile( char *path, int maxlen ); + +private: + + CSentence m_Sentence; + + enum + { + MAX_SOUND_NAME = 256, + MAX_SCRIPT_FILE = 64, + MAX_SOUND_FILENAME = 128, + }; + + char m_szName[ MAX_SOUND_FILENAME ]; + char m_szFileName[ MAX_SOUND_FILENAME ]; + +// CVCDFile *m_pOwner; +// CSoundEntry *m_pOwnerSE; + + bool m_bSentenceLoaded; + bool m_bAsyncLoading; +}; + +#endif // WAVEFILE_H diff --git a/utils/hlfaceposer/workspac.ico b/utils/hlfaceposer/workspac.ico Binary files differnew file mode 100644 index 0000000..b3e4e4a --- /dev/null +++ b/utils/hlfaceposer/workspac.ico |