diff options
Diffstat (limited to 'utils/hlfaceposer/choreoview.cpp')
| -rw-r--r-- | utils/hlfaceposer/choreoview.cpp | 11647 |
1 files changed, 11647 insertions, 0 deletions
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 ); +} + |