//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //===========================================================================// #include "client_pch.h" #include "cl_demosmootherpanel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cl_demouipanel.h" #include "demofile/demoformat.h" #include "cl_demoactionmanager.h" #include "tier2/renderutils.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" using namespace vgui; static float Ease_In( float t ) { float out = sqrt( t ); return out; } static float Ease_Out( float t ) { float out = t * t; return out; } static float Ease_Both( float t ) { return SimpleSpline( t ); } //----------------------------------------------------------------------------- // Purpose: A menu button that knows how to parse cvar/command menu data from gamedir\scripts\debugmenu.txt //----------------------------------------------------------------------------- class CSmoothingTypeButton : public vgui::MenuButton { typedef vgui::MenuButton BaseClass; public: // Construction CSmoothingTypeButton( vgui::Panel *parent, const char *panelName, const char *text ); private: // Menu associated with this button Menu *m_pMenu; }; //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CSmoothingTypeButton::CSmoothingTypeButton(Panel *parent, const char *panelName, const char *text) : BaseClass( parent, panelName, text ) { // Assume no menu m_pMenu = new Menu( this, "DemoSmootherTypeMenu" ); m_pMenu->AddMenuItem( "Smooth Selection Angles", "smoothselectionangles", parent ); m_pMenu->AddMenuItem( "Smooth Selection Origin", "smoothselectionorigin", parent ); m_pMenu->AddMenuItem( "Linear Interp Angles", "smoothlinearinterpolateangles", parent ); m_pMenu->AddMenuItem( "Linear Interp Origin", "smoothlinearinterpolateorigin", parent ); m_pMenu->AddMenuItem( "Spline Angles", "splineangles", parent ); m_pMenu->AddMenuItem( "Spline Origin", "splineorigin", parent ); m_pMenu->AddMenuItem( "Look At Points", "lookatpoints", parent ); m_pMenu->AddMenuItem( "Look At Points Spline", "lookatpointsspline", parent ); m_pMenu->AddMenuItem( "Two Point Origin Ease Out", "origineaseout", parent ); m_pMenu->AddMenuItem( "Two Point Origin Ease In", "origineasein", parent ); m_pMenu->AddMenuItem( "Two Point Origin Ease In/Out", "origineaseboth", parent ); m_pMenu->AddMenuItem( "Auto-setup keys 1/2 second", "keyshalf", parent ); m_pMenu->AddMenuItem( "Auto-setup keys 1 second", "keys1", parent ); m_pMenu->AddMenuItem( "Auto-setup keys 2 second", "keys2", parent ); m_pMenu->AddMenuItem( "Auto-setup keys 4 second", "keys4", parent ); m_pMenu->MakePopup(); MenuButton::SetMenu(m_pMenu); SetOpenDirection(Menu::UP); } //----------------------------------------------------------------------------- // Purpose: A menu button that knows how to parse cvar/command menu data from gamedir\scripts\debugmenu.txt //----------------------------------------------------------------------------- class CFixEdgeButton : public vgui::MenuButton { typedef vgui::MenuButton BaseClass; public: // Construction CFixEdgeButton( vgui::Panel *parent, const char *panelName, const char *text ); private: // Menu associated with this button Menu *m_pMenu; }; //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CFixEdgeButton::CFixEdgeButton(Panel *parent, const char *panelName, const char *text) : BaseClass( parent, panelName, text ) { // Assume no menu m_pMenu = new Menu( this, "DemoSmootherEdgeFixType" ); m_pMenu->AddMenuItem( "Smooth Left", "smoothleft", parent ); m_pMenu->AddMenuItem( "Smooth Right", "smoothright", parent ); m_pMenu->AddMenuItem( "Smooth Both", "smoothboth", parent ); m_pMenu->MakePopup(); MenuButton::SetMenu(m_pMenu); SetOpenDirection(Menu::UP); } //----------------------------------------------------------------------------- // Purpose: Basic help dialog //----------------------------------------------------------------------------- CDemoSmootherPanel::CDemoSmootherPanel( vgui::Panel *parent ) : Frame( parent, "DemoSmootherPanel") { int w = 440; int h = 300; SetSize( w, h ); SetTitle("Demo Smoother", true); m_pType = new CSmoothingTypeButton( this, "DemoSmootherType", "Process->" ); m_pRevert = new vgui::Button( this, "DemoSmoothRevert", "Revert" );; m_pOK = new vgui::Button( this, "DemoSmoothOk", "OK" ); m_pCancel = new vgui::Button( this, "DemoSmoothCancel", "Cancel" ); m_pSave = new vgui::Button( this, "DemoSmoothSave", "Save" ); m_pReloadFromDisk = new vgui::Button( this, "DemoSmoothReload", "Reload" ); m_pStartFrame = new vgui::TextEntry( this, "DemoSmoothStartFrame" ); m_pEndFrame = new vgui::TextEntry( this, "DemoSmoothEndFrame" ); m_pPreviewOriginal = new vgui::Button( this, "DemoSmoothPreviewOriginal", "Show Original" ); m_pPreviewProcessed = new vgui::Button( this, "DemoSmoothPreviewProcessed", "Show Processed" ); m_pBackOff = new vgui::CheckButton( this, "DemoSmoothBackoff", "Back off" ); m_pHideLegend = new vgui::CheckButton( this, "DemoSmoothHideLegend", "Hide legend" ); m_pHideOriginal = new vgui::CheckButton( this, "DemoSmoothHideOriginal", "Hide original" ); m_pHideProcessed = new vgui::CheckButton( this, "DemoSmoothHideProcessed", "Hide processed" ); m_pSelectionInfo = new vgui::Label( this, "DemoSmoothSelectionInfo", "" ); m_pShowAllSamples = new vgui::CheckButton( this, "DemoSmoothShowAll", "Show All" ); m_pSelectSamples = new vgui::Button( this, "DemoSmoothSelect", "Select" ); m_pPauseResume = new vgui::Button( this, "DemoSmoothPauseResume", "Pause" ); m_pStepForward = new vgui::Button( this, "DemoSmoothStepForward", ">>" ); m_pStepBackward = new vgui::Button( this, "DemoSmoothStepBackward", "<<" ); m_pRevertPoint = new vgui::Button( this, "DemoSmoothRevertPoint", "Revert Pt." ); m_pToggleKeyFrame = new vgui::Button( this, "DemoSmoothSetKeyFrame", "Mark Keyframe" ); m_pToggleLookTarget = new vgui::Button( this, "DemoSmoothSetLookTarget", "Mark Look Target" ); m_pUndo = new vgui::Button( this, "DemoSmoothUndo", "Undo" ); m_pRedo = new vgui::Button( this, "DemoSmoothRedo", "Redo" ); m_pNextKey = new vgui::Button( this, "DemoSmoothNextKey", "+Key" ); m_pPrevKey = new vgui::Button( this, "DemoSmoothPrevKey", "-Key" ); m_pNextTarget = new vgui::Button( this, "DemoSmoothNextTarget", "+Target" ); m_pPrevTarget = new vgui::Button( this, "DemoSmoothPrevTarget", "-Target" ); m_pMoveCameraToPoint = new vgui::Button( this, "DemoSmoothCameraAtPoint", "Set View" ); m_pFixEdges = new CFixEdgeButton( this, "DemoSmoothFixFrameButton", "Edge->" ); m_pFixEdgeFrames = new vgui::TextEntry( this, "DemoSmoothFixFrames" ); m_pProcessKey = new vgui::Button( this, "DemoSmoothSaveKey", "Save Key" ); m_pGotoFrame = new vgui::TextEntry( this, "DemoSmoothGotoFrame" ); m_pGoto = new vgui::Button( this, "DemoSmoothGoto", "Jump To" ); //m_pCurrentDemo = new vgui::Label( this, "DemoName", "" ); vgui::ivgui()->AddTickSignal( GetVPanel(), 0 ); LoadControlSettings("Resource\\DemoSmootherPanel.res"); /* int xpos, ypos; parent->GetPos( xpos, ypos ); ypos += parent->GetTall(); SetPos( xpos, ypos ); */ OnRefresh(); SetVisible( true ); SetSizeable( false ); SetMoveable( true ); Reset(); m_vecEyeOffset = Vector( 0, 0, 64 ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CDemoSmootherPanel::~CDemoSmootherPanel() { } void CDemoSmootherPanel::Reset( void ) { ClearSmoothingInfo( m_Smoothing ); m_bPreviewing = false; m_bPreviewPaused = false; m_bPreviewOriginal = false; m_iPreviewStartTick = 0; m_fPreviewCurrentTime = 0.0f; m_nPreviewLastFrame = 0; m_bHasSelection = false; memset( m_nSelection, 0, sizeof( m_nSelection ) ); m_iSelectionTicksSpan = 0; m_bInputActive = false; memset( m_nOldCursor, 0, sizeof( m_nOldCursor ) ); WipeUndo(); WipeRedo(); m_bRedoPending = false; m_nUndoLevel = 0; m_bDirty = false; } void CDemoSmootherPanel::OnTick() { BaseClass::OnTick(); m_pUndo->SetEnabled( CanUndo() ); m_pRedo->SetEnabled( CanRedo() ); m_pPauseResume->SetEnabled( m_bPreviewing ); m_pStepForward->SetEnabled( m_bPreviewing ); m_pStepBackward->SetEnabled( m_bPreviewing ); m_pSave->SetEnabled( m_bDirty ); demosmoothing_t *p = GetCurrent(); if ( p ) { m_pToggleKeyFrame->SetEnabled( true ); m_pToggleLookTarget->SetEnabled( true ); m_pToggleKeyFrame->SetText( p->samplepoint ? "Delete Key" : "Make Key" ); m_pToggleLookTarget->SetText( p->targetpoint ? "Delete Target" : "Make Target" ); m_pProcessKey->SetEnabled( p->samplepoint ); } else { m_pToggleKeyFrame->SetEnabled( false ); m_pToggleLookTarget->SetEnabled( false ); m_pProcessKey->SetEnabled( false ); } if ( m_bPreviewing ) { m_pPauseResume->SetText( m_bPreviewPaused ? "Resume" : "Pause" ); } if ( !m_Smoothing.active ) { m_pSelectionInfo->SetText( "No smoothing info loaded" ); return; } if ( !demoplayer->IsPlayingBack() ) { m_pSelectionInfo->SetText( "Not playing back .dem" ); return; } if ( !m_bHasSelection ) { m_pSelectionInfo->SetText( "No selection." ); return; } char sz[ 512 ]; if ( m_bPreviewing ) { Q_snprintf( sz, sizeof( sz ), "%.3f at tick %i (%.3f s)", m_fPreviewCurrentTime, GetTickForFrame( m_nPreviewLastFrame ), TICKS_TO_TIME( m_iSelectionTicksSpan ) ); } else { Q_snprintf( sz, sizeof( sz ), "%i to %i (%.3f s)", m_Smoothing.smooth[ m_nSelection[ 0 ] ].frametick, m_Smoothing.smooth[ m_nSelection[ 1 ] ].frametick, TICKS_TO_TIME( m_iSelectionTicksSpan ) ); } m_pSelectionInfo->SetText( sz ); } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CDemoSmootherPanel::CanEdit() { if ( !m_Smoothing.active ) return false; if ( !demoplayer->IsPlayingBack() ) return false; return true; } //----------------------------------------------------------------------------- // Purpose: // Input : *command - //----------------------------------------------------------------------------- void CDemoSmootherPanel::OnCommand(const char *command) { if ( !Q_strcasecmp( command, "cancel" ) ) { OnRevert(); MarkForDeletion(); Reset(); OnClose(); } else if ( !Q_strcasecmp( command, "close" ) ) { OnSave(); MarkForDeletion(); Reset(); OnClose(); } else if ( !Q_strcasecmp( command, "gotoframe" ) ) { OnGotoFrame(); } else if ( !Q_strcasecmp( command, "undo" ) ) { Undo(); } else if ( !Q_strcasecmp( command, "redo" ) ) { Redo(); } else if ( !Q_strcasecmp( command, "revert" ) ) { OnRevert(); } else if ( !Q_strcasecmp( command, "original" ) ) { OnPreview( true ); } else if ( !Q_strcasecmp( command, "processed" ) ) { OnPreview( false ); } else if ( !Q_strcasecmp( command, "save" ) ) { OnSave(); } else if ( !Q_strcasecmp( command, "reload" ) ) { OnReload(); } else if ( !Q_strcasecmp( command, "select" ) ) { OnSelect(); } else if ( !Q_strcasecmp( command, "togglepause" ) ) { OnTogglePause(); } else if ( !Q_strcasecmp( command, "stepforward" ) ) { OnStep( true ); } else if ( !Q_strcasecmp( command, "stepbackward" ) ) { OnStep( false ); } else if ( !Q_strcasecmp( command, "revertpoint" ) ) { OnRevertPoint(); } else if ( !Q_strcasecmp( command, "keyframe" ) ) { OnToggleKeyFrame(); } else if ( !Q_strcasecmp( command, "looktarget" ) ) { OnToggleLookTarget(); } else if ( !Q_strcasecmp( command, "nextkey" ) ) { OnNextKey(); } else if ( !Q_strcasecmp( command, "prevkey" ) ) { OnPrevKey(); } else if ( !Q_strcasecmp( command, "nexttarget" ) ) { OnNextTarget(); } else if ( !Q_strcasecmp( command, "prevtarget" ) ) { OnPrevTarget(); } else if ( !Q_strcasecmp( command, "smoothselectionangles" ) ) { OnSmoothSelectionAngles(); } else if ( !Q_strcasecmp( command, "keyshalf" ) ) { OnSetKeys( 0.5f ); } else if ( !Q_strcasecmp( command, "keys1" ) ) { OnSetKeys( 1.0f ); } else if ( !Q_strcasecmp( command, "keys2" ) ) { OnSetKeys( 2.0f ); } else if ( !Q_strcasecmp( command, "keys4" ) ) { OnSetKeys( 4.0f ); } else if ( !Q_strcasecmp( command, "smoothselectionorigin" ) ) { OnSmoothSelectionOrigin(); } else if ( !Q_strcasecmp( command, "smoothlinearinterpolateangles" ) ) { OnLinearInterpolateAnglesBasedOnEndpoints(); } else if ( !Q_strcasecmp( command, "smoothlinearinterpolateorigin" ) ) { OnLinearInterpolateOriginBasedOnEndpoints(); } else if ( !Q_strcasecmp( command, "splineorigin" ) ) { OnSplineSampleOrigin(); } else if ( !Q_strcasecmp( command, "splineangles" ) ) { OnSplineSampleAngles(); } else if ( !Q_strcasecmp( command, "lookatpoints" ) ) { OnLookAtPoints( false ); } else if ( !Q_strcasecmp( command, "lookatpointsspline" ) ) { OnLookAtPoints( true ); } else if ( !Q_strcasecmp( command, "smoothleft" ) ) { OnSmoothEdges( true, false ); } else if ( !Q_strcasecmp( command, "smoothright" ) ) { OnSmoothEdges( false, true ); } else if ( !Q_strcasecmp( command, "smoothboth" ) ) { OnSmoothEdges( true, true ); } else if ( !Q_strcasecmp( command, "origineasein" ) ) { OnOriginEaseCurve( Ease_In ); } else if ( !Q_strcasecmp( command, "origineaseout" ) ) { OnOriginEaseCurve( Ease_Out ); } else if ( !Q_strcasecmp( command, "origineaseboth" ) ) { OnOriginEaseCurve( Ease_Both ); } else if ( !Q_strcasecmp( command, "processkey" ) ) { OnSaveKey(); } else if ( !Q_strcasecmp( command, "setview" ) ) { OnSetView(); } else { BaseClass::OnCommand( command ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDemoSmootherPanel::OnSave() { if ( !m_Smoothing.active ) return; SaveSmoothingInfo( demoaction->GetCurrentDemoFile(), m_Smoothing ); WipeUndo(); m_bDirty = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDemoSmootherPanel::OnReload() { WipeUndo(); WipeRedo(); LoadSmoothingInfo( demoaction->GetCurrentDemoFile(), m_Smoothing ); m_bDirty = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDemoSmootherPanel::OnVDMChanged( void ) { if ( IsVisible() ) { OnReload(); } else { Reset(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDemoSmootherPanel::OnRevert() { OnRefresh(); if ( !m_Smoothing.active ) { LoadSmoothingInfo( demoaction->GetCurrentDemoFile(), m_Smoothing ); WipeUndo(); WipeRedo(); } else { ClearSmoothingInfo( m_Smoothing ); WipeUndo(); WipeRedo(); } m_bDirty = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDemoSmootherPanel::OnRefresh() { } //----------------------------------------------------------------------------- // Purpose: // Input : *pScheme - //----------------------------------------------------------------------------- void CDemoSmootherPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); } //----------------------------------------------------------------------------- // Purpose: // Output : int //----------------------------------------------------------------------------- int CDemoSmootherPanel::GetStartFrame() { char text[ 32 ]; m_pStartFrame->GetText( text, sizeof( text ) ); int tick = atoi( text ); return GetFrameForTick( tick ); } //----------------------------------------------------------------------------- // Purpose: // Output : int //----------------------------------------------------------------------------- int CDemoSmootherPanel::GetEndFrame() { char text[ 32 ]; m_pEndFrame->GetText( text, sizeof( text ) ); int tick = atoi( text ); return GetFrameForTick( tick ); } //----------------------------------------------------------------------------- // Purpose: // Input : original - //----------------------------------------------------------------------------- void CDemoSmootherPanel::OnPreview( bool original ) { if ( !CanEdit() ) return; if ( !m_bHasSelection ) { ConMsg( "Must have smoothing selection active\n" ); return; } m_bPreviewing = true; m_bPreviewPaused = false; m_bPreviewOriginal = original; SetLastFrame( false, max( 0, m_nSelection[0] - 10 ) ); m_iPreviewStartTick = GetTickForFrame( m_nPreviewLastFrame ); m_fPreviewCurrentTime = TICKS_TO_TIME( m_iPreviewStartTick ); } //----------------------------------------------------------------------------- // Purpose: // Input : frame - // elapsed - // info - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CDemoSmootherPanel::OverrideView( democmdinfo_t& info, int tick ) { if ( !CanEdit() ) return false; if ( !demoplayer->IsPlaybackPaused() ) return false; if ( m_bPreviewing ) { if ( m_bPreviewPaused && GetCurrent() && GetCurrent()->samplepoint ) { info.viewOrigin = GetCurrent()->vecmoved; info.viewAngles = GetCurrent()->angmoved; info.localViewAngles = info.viewAngles; bool back_off = m_pBackOff->IsSelected(); if ( back_off ) { Vector fwd; AngleVectors( info.viewAngles, &fwd, NULL, NULL ); info.viewOrigin -= fwd * 75.0f; } return true; } // TODO: Hook up previewing view if ( !m_bPreviewPaused ) { m_fPreviewCurrentTime += host_frametime; } if ( GetInterpolatedViewPoint( info.viewOrigin, info.viewAngles ) ) { info.localViewAngles = info.viewAngles; return true; } else { return false; } } bool back_off = m_pBackOff->IsSelected(); if ( back_off ) { int useframe = GetFrameForTick( tick ); if ( useframe < m_Smoothing.smooth.Count() && useframe >= 0 ) { demosmoothing_t *p = &m_Smoothing.smooth[ useframe ]; Vector fwd; AngleVectors( p->info.viewAngles, &fwd, NULL, NULL ); info.viewOrigin = p->info.viewOrigin - fwd * 75.0f; } } return false; } void DrawVecForward( bool active, const Vector& origin, const QAngle& angles, int r, int g, int b ) { Vector fwd; AngleVectors( angles, &fwd, NULL, NULL ); Vector end; end = origin + fwd * ( active ? 64 : 16 ); RenderLine( origin, end, Color( r, g, b, 255 ), true ); } void GetColorForSample( bool original, bool samplepoint, bool targetpoint, demosmoothing_t *sample, int& r, int& g, int& b ) { if ( samplepoint && sample->samplepoint ) { r = 0; g = 255; b = 0; return; } if ( targetpoint && sample->targetpoint ) { r = 255; g = 0; b = 0; return; } if ( sample->selected ) { if( original ) { r = 255; g = 200; b = 100; } else { r = 200; g = 100; b = 255; } if ( sample->samplepoint || sample->targetpoint ) { r = 255; g = 255; b = 0; } return; } if ( original ) { r = g = b = 255; } else { r = 150; g = 255; b = 100; } } //----------------------------------------------------------------------------- // Purpose: // Input : origin - // mins - // maxs - // angles - // r - // g - // b - // a - //----------------------------------------------------------------------------- void Draw_Box( const Vector& origin, const Vector& mins, const Vector& maxs, const QAngle& angles, int r, int g, int b, int a ) { RenderBox( origin, angles, mins, maxs, Color( r, g, b, a ), false ); RenderWireframeBox( origin, angles, mins, maxs, Color( r, g, b, a ), true ); } //----------------------------------------------------------------------------- // Purpose: // Input : *sample - // *next - //----------------------------------------------------------------------------- void CDemoSmootherPanel::DrawSmoothingSample( bool original, bool processed, int samplenumber, demosmoothing_t *sample, demosmoothing_t *next ) { int r, g, b; if ( original ) { RenderLine( sample->info.viewOrigin + m_vecEyeOffset, next->info.viewOrigin + m_vecEyeOffset, Color( 180, 180, 180, 255 ), true ); GetColorForSample( true, false, false, sample, r, g, b ); DrawVecForward( false, sample->info.viewOrigin + m_vecEyeOffset, sample->info.viewAngles, r, g, b ); } if ( processed && sample->info.flags != 0 ) { RenderLine( sample->info.GetViewOrigin() + m_vecEyeOffset, next->info.GetViewOrigin() + m_vecEyeOffset, Color( 255, 255, 180, 255 ), true ); GetColorForSample( false, false, false, sample, r, g, b ); DrawVecForward( false, sample->info.GetViewOrigin() + m_vecEyeOffset, sample->info.GetViewAngles(), r, g, b ); } if ( sample->samplepoint ) { GetColorForSample( false, true, false, sample, r, g, b ); RenderBox( sample->vecmoved + m_vecEyeOffset, sample->angmoved, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), Color( r, g, b, 127 ), false ); DrawVecForward( false, sample->vecmoved + m_vecEyeOffset, sample->angmoved, r, g, b ); } if ( sample->targetpoint ) { GetColorForSample( false, false, true, sample, r, g, b ); RenderBox( sample->vectarget, vec3_angle, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), Color( r, g, b, 127 ), false ); } if ( samplenumber == m_nPreviewLastFrame + 1 ) { r = 50; g = 100; b = 250; RenderBox( sample->info.GetViewOrigin() + m_vecEyeOffset, sample->info.GetViewAngles(), Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), Color( r, g, b, 92 ), false ); } if ( sample->targetpoint ) { r = 200; g = 200; b = 220; RenderLine( sample->info.GetViewOrigin() + m_vecEyeOffset, sample->vectarget, Color( r, g, b, 255 ), true ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDemoSmootherPanel::DrawDebuggingInfo( int frame, float elapsed ) { if ( !CanEdit() ) return; if ( !IsVisible() ) return; int c = m_Smoothing.smooth.Count(); if ( c < 2 ) return; int start = 0; int end = c - 1; bool showall = m_pShowAllSamples->IsSelected(); if ( !showall ) { start = max( frame - 200, 0 ); end = min( frame + 200, c - 1 ); } if ( m_bHasSelection && !showall ) { start = max( m_nSelection[ 0 ] - 10, 0 ); end = min( m_nSelection[ 1 ] + 10, c - 1 ); } bool draworiginal = !m_pHideOriginal->IsSelected(); bool drawprocessed = !m_pHideProcessed->IsSelected(); int i; demosmoothing_t *p = NULL; demosmoothing_t *prev = NULL; for ( i = start; i < end; i++ ) { p = &m_Smoothing.smooth[ i ]; if ( prev && p ) { DrawSmoothingSample( draworiginal, drawprocessed, i, prev, p ); } prev = p; } Vector org; QAngle ang; if ( m_bPreviewing ) { if ( GetInterpolatedOriginAndAngles( true, org, ang ) ) { DrawVecForward( true, org + m_vecEyeOffset, ang, 200, 10, 50 ); } } int useframe = frame; useframe = clamp( useframe, 0, c - 1 ); if ( useframe < c ) { p = &m_Smoothing.smooth[ useframe ]; org = p->info.GetViewOrigin(); ang = p->info.GetViewAngles(); DrawVecForward( true, org + m_vecEyeOffset, ang, 100, 220, 250 ); Draw_Box( org + m_vecEyeOffset, Vector( -1, -1, -1 ), Vector( 1, 1, 1 ), ang, 100, 220, 250, 127 ); } DrawKeySpline(); DrawTargetSpline(); if ( !m_pHideLegend->IsSelected() ) { DrawLegend( start, end ); } } void CDemoSmootherPanel::OnSelect() { if ( !CanEdit() ) return; m_bHasSelection = false; m_iSelectionTicksSpan = 0; memset( m_nSelection, 0, sizeof( m_nSelection ) ); int start, end; start = GetStartFrame(); end = GetEndFrame(); int c = m_Smoothing.smooth.Count(); if ( c < 2 ) return; start = clamp( start, 0, c - 1 ); end = clamp( end, 0, c - 1 ); if ( start >= end ) return; m_nSelection[ 0 ] = start; m_nSelection[ 1 ] = end; m_bHasSelection = true; demosmoothing_t *startsample = &m_Smoothing.smooth[ start ]; demosmoothing_t *endsample = &m_Smoothing.smooth[ end ]; m_bDirty = true; PushUndo( "select" ); int i = 0; for ( i = 0; i < c; i++ ) { if ( i >= start && i <= end ) { m_Smoothing.smooth[ i ].selected = true; } else { m_Smoothing.smooth[ i ].selected = false; } } PushRedo( "select" ); m_iSelectionTicksSpan = endsample->frametick - startsample->frametick; } int CDemoSmootherPanel::GetFrameForTick( int tick ) { int count = m_Smoothing.smooth.Count(); int last = count - 1; int first = m_Smoothing.m_nFirstSelectableSample; if ( first > last ) return -1; if ( count <= 0 ) { return -1; // no valid index } else if ( count == 1 ) { return 0; // return the one and only frame we have } if ( tick <= m_Smoothing.smooth[ first ].frametick ) return first; if ( tick >= m_Smoothing.smooth[ last ].frametick ) return last; // binary search int middle; while ( true ) { middle = (first+last)/2; int middleTick = m_Smoothing.smooth[ middle ].frametick; if ( tick == middleTick ) return middle; if ( tick > middleTick ) { if ( first == middle ) return first; first = middle; } else { if ( last == middle ) return last; last = middle; } } } int CDemoSmootherPanel::GetTickForFrame( int frame ) { if ( !CanEdit() ) return -1; int c = m_Smoothing.smooth.Count(); if ( c < 1 ) return -1; if ( frame < 0 ) return m_Smoothing.smooth[ 0 ].frametick; if ( frame >= c ) return m_Smoothing.smooth[ c - 1 ].frametick; return m_Smoothing.smooth[ frame ].frametick; } //----------------------------------------------------------------------------- // Purpose: Interpolate Euler angles using quaternions to avoid singularities // Input : start - // end - // output - // frac - //----------------------------------------------------------------------------- static void InterpolateAngles( const QAngle& start, const QAngle& end, QAngle& output, float frac ) { Quaternion src, dest; // Convert to quaternions AngleQuaternion( start, src ); AngleQuaternion( end, dest ); Quaternion result; // Slerp QuaternionSlerp( src, dest, frac, result ); // Convert to euler QuaternionAngles( result, output ); } bool CDemoSmootherPanel::GetInterpolatedOriginAndAngles( bool readonly, Vector& origin, QAngle& angles ) { origin.Init(); angles.Init(); Assert( m_bPreviewing ); // Figure out the best samples int startframe = m_nPreviewLastFrame; int nextframe = startframe + 1; float time = m_fPreviewCurrentTime; int c = m_Smoothing.smooth.Count(); do { if ( startframe >= c || nextframe >= c ) { if ( !readonly ) { //m_bPreviewing = false; } return false; } demosmoothing_t *startsample = &m_Smoothing.smooth[ startframe ]; demosmoothing_t *endsample = &m_Smoothing.smooth[ nextframe ]; if ( nextframe >= min( m_nSelection[1] + 10, c - 1 ) ) { if ( !readonly ) { OnPreview( m_bPreviewOriginal ); } return false; } // If large dt, then jump ahead quickly in time float dt = TICKS_TO_TIME( endsample->frametick - startsample->frametick ); if ( dt > 1.0f ) { startframe++; nextframe++; continue; } if ( TICKS_TO_TIME( endsample->frametick ) >= time ) { // Found a spot dt = TICKS_TO_TIME( endsample->frametick - startsample->frametick ); // Should never occur!!! if ( dt <= 0.0f ) { return false; } float frac = (float)( time - TICKS_TO_TIME(startsample->frametick) ) / dt; frac = clamp( frac, 0.0f, 1.0f ); // Compute render origin/angles Vector renderOrigin; QAngle renderAngles; if ( m_bPreviewOriginal ) { VectorLerp( startsample->info.viewOrigin, endsample->info.viewOrigin, frac, renderOrigin ); InterpolateAngles( startsample->info.viewAngles, endsample->info.viewAngles, renderAngles, frac ); } else { VectorLerp( startsample->info.GetViewOrigin(), endsample->info.GetViewOrigin(), frac, renderOrigin ); InterpolateAngles( startsample->info.GetViewAngles(), endsample->info.GetViewAngles(), renderAngles, frac ); } origin = renderOrigin; angles = renderAngles; if ( !readonly ) { SetLastFrame( false, startframe ); } break; } startframe++; nextframe++; } while ( true ); return true; } //----------------------------------------------------------------------------- // Purpose: // Input : t - //----------------------------------------------------------------------------- bool CDemoSmootherPanel::GetInterpolatedViewPoint( Vector& origin, QAngle& angles ) { Assert( m_bPreviewing ); if ( !GetInterpolatedOriginAndAngles( false, origin, angles ) ) return false; bool back_off = m_pBackOff->IsSelected(); if ( back_off ) { Vector fwd; AngleVectors( angles, &fwd, NULL, NULL ); origin = origin - fwd * 75.0f; } return true; } void CDemoSmootherPanel::OnTogglePause() { if ( !m_bPreviewing ) return; m_bPreviewPaused = !m_bPreviewPaused; } void CDemoSmootherPanel::OnStep( bool forward ) { if ( !m_bPreviewing ) return; if ( !m_bPreviewPaused ) return; int c = m_Smoothing.smooth.Count(); SetLastFrame( false, m_nPreviewLastFrame + ( forward ? 1 : -1 ) ); SetLastFrame( false, clamp( m_nPreviewLastFrame, max( m_nSelection[ 0 ] - 10, 0 ), min( m_nSelection[ 1 ] + 10, c - 1 ) ) ); m_fPreviewCurrentTime = TICKS_TO_TIME( GetTickForFrame( m_nPreviewLastFrame ) ); } void CDemoSmootherPanel::DrawLegend( int startframe, int endframe ) { int i; int skip = 20; bool back_off = m_pBackOff->IsSelected(); for ( i = startframe; i <= endframe; i++ ) { bool show = ( i % skip ) == 0; demosmoothing_t *sample = &m_Smoothing.smooth[ i ]; if ( sample->samplepoint || sample->targetpoint ) show = true; if ( !show ) continue; char sz[ 512 ]; Q_snprintf( sz, sizeof( sz ), "%.3f", TICKS_TO_TIME(sample->frametick) ); Vector fwd; AngleVectors( sample->info.GetViewAngles(), &fwd, NULL, NULL ); CDebugOverlay::AddTextOverlay( sample->info.GetViewOrigin() + m_vecEyeOffset + fwd * ( back_off ? 5.0f : 50.0f ), 0, -1.0f, sz ); } } #define EASE_TIME 0.2f Quaternion SmoothAngles( CUtlVector< Quaternion >& stack ) { int c = stack.Count(); Assert( c >= 1 ); float weight = 1.0f / (float)c; Quaternion output; output.Init(); int i; for ( i = 0; i < c; i++ ) { Quaternion t = stack[ i ]; QuaternionBlend( output, t, weight, output ); } return output; } Vector SmoothOrigin( CUtlVector< Vector >& stack ) { int c = stack.Count(); Assert( c >= 1 ); Vector output; output.Init(); int i; for ( i = 0; i < c; i++ ) { Vector t = stack[ i ]; VectorAdd( output, t, output ); } VectorScale( output, 1.0f / (float)c, output ); return output; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDemoSmootherPanel::OnSetKeys(float interval) { if ( !m_bHasSelection ) return; m_bDirty = true; PushUndo( "OnSetKeys" ); int c = m_Smoothing.smooth.Count(); int i; demosmoothing_t *lastkey = NULL; for ( i = 0; i < c; i++ ) { demosmoothing_t *p = &m_Smoothing.smooth[ i ]; if ( !p->selected ) continue; p->angmoved = p->info.GetViewAngles();; p->vecmoved = p->info.GetViewOrigin(); p->samplepoint = false; if ( !lastkey || TICKS_TO_TIME( p->frametick - lastkey->frametick ) >= interval ) { lastkey = p; p->samplepoint = true; } } PushRedo( "OnSetKeys" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDemoSmootherPanel::OnSmoothSelectionAngles( void ) { if ( !m_bHasSelection ) return; int c = m_Smoothing.smooth.Count(); int i; CUtlVector< Quaternion > stack; m_bDirty = true; PushUndo( "smooth angles" ); for ( i = 0; i < c; i++ ) { demosmoothing_t *p = &m_Smoothing.smooth[ i ]; if ( !p->selected ) continue; while ( stack.Count() > 10 ) { stack.Remove( 0 ); } Quaternion q; AngleQuaternion( p->info.GetViewAngles(), q ); stack.AddToTail( q ); p->info.flags |= FDEMO_USE_ANGLES2; Quaternion aveq = SmoothAngles( stack ); QAngle outangles; QuaternionAngles( aveq, outangles ); p->info.viewAngles2 = outangles; p->info.localViewAngles2 = outangles; } PushRedo( "smooth angles" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDemoSmootherPanel::OnSmoothSelectionOrigin( void ) { if ( !m_bHasSelection ) return; int c = m_Smoothing.smooth.Count(); int i; CUtlVector< Vector > stack; m_bDirty = true; PushUndo( "smooth origin" ); for ( i = 0; i < c; i++ ) { demosmoothing_t *p = &m_Smoothing.smooth[ i ]; if ( !p->selected ) continue; if ( i < 2 ) continue; if ( i >= c - 2 ) continue; stack.RemoveAll(); for ( int j = -2; j <= 2; j++ ) { stack.AddToTail( m_Smoothing.smooth[ i + j ].info.GetViewOrigin() ); } p->info.flags |= FDEMO_USE_ORIGIN2; Vector org = SmoothOrigin( stack ); p->info.viewOrigin2 = org; } PushRedo( "smooth origin" ); } void CDemoSmootherPanel::PerformLinearInterpolatedAngleSmoothing( int startframe, int endframe ) { demosmoothing_t *pstart = &m_Smoothing.smooth[ startframe ]; demosmoothing_t *pend = &m_Smoothing.smooth[ endframe ]; int dt = pend->frametick - pstart->frametick; if ( dt <= 0 ) { dt = 1; } CUtlVector< Quaternion > stack; Quaternion qstart, qend; AngleQuaternion( pstart->info.GetViewAngles(), qstart ); AngleQuaternion( pend->info.GetViewAngles(), qend ); for ( int i = startframe; i <= endframe; i++ ) { demosmoothing_t *p = &m_Smoothing.smooth[ i ]; int elapsed = p->frametick - pstart->frametick; float frac = (float)elapsed / (float)dt; frac = clamp( frac, 0.0f, 1.0f ); p->info.flags |= FDEMO_USE_ANGLES2; Quaternion interpolated; QuaternionSlerp( qstart, qend, frac, interpolated ); QAngle outangles; QuaternionAngles( interpolated, outangles ); p->info.viewAngles2 = outangles; p->info.localViewAngles2 = outangles; } } void CDemoSmootherPanel::OnLinearInterpolateAnglesBasedOnEndpoints( void ) { if ( !m_bHasSelection ) return; int c = m_Smoothing.smooth.Count(); if ( c < 2 ) return; m_bDirty = true; PushUndo( "linear interp angles" ); PerformLinearInterpolatedAngleSmoothing( m_nSelection[ 0 ], m_nSelection[ 1 ] ); PushRedo( "linear interp angles" ); } void CDemoSmootherPanel::OnLinearInterpolateOriginBasedOnEndpoints( void ) { if ( !m_bHasSelection ) return; int c = m_Smoothing.smooth.Count(); if ( c < 2 ) return; demosmoothing_t *pstart = &m_Smoothing.smooth[ m_nSelection[ 0 ] ]; demosmoothing_t *pend = &m_Smoothing.smooth[ m_nSelection[ 1 ] ]; int dt = pend->frametick - pstart->frametick; if ( dt <= 0 ) return; m_bDirty = true; PushUndo( "linear interp origin" ); Vector vstart, vend; vstart = pstart->info.GetViewOrigin(); vend = pend->info.GetViewOrigin(); for ( int i = m_nSelection[0]; i <= m_nSelection[1]; i++ ) { demosmoothing_t *p = &m_Smoothing.smooth[ i ]; float elapsed = p->frametick - pstart->frametick; float frac = elapsed / (float)dt; frac = clamp( frac, 0.0f, 1.0f ); p->info.flags |= FDEMO_USE_ORIGIN2; Vector interpolated; VectorLerp( vstart, vend, frac, interpolated ); p->info.viewOrigin2 = interpolated; } PushRedo( "linear interp origin" ); } void CDemoSmootherPanel::OnRevertPoint( void ) { demosmoothing_t *p = GetCurrent(); if ( !p ) return; m_bDirty = true; PushUndo( "revert point" ); p->angmoved = p->info.GetViewAngles(); p->vecmoved = p->info.GetViewOrigin(); p->samplepoint = false; p->vectarget = p->info.GetViewOrigin(); p->targetpoint = false; // m_ViewOrigin = p->info.viewOrigin; // m_ViewAngles = p->info.viewAngles; PushRedo( "revert point" ); } demosmoothing_t *CDemoSmootherPanel::GetCurrent( void ) { if ( !CanEdit() ) return NULL; int c = m_Smoothing.smooth.Count(); if ( c < 1 ) return NULL; int frame = clamp( m_nPreviewLastFrame, 0, c - 1 ); return &m_Smoothing.smooth[ frame ]; } void CDemoSmootherPanel::AddSamplePoints( bool usetarget, bool includeboundaries, CUtlVector< demosmoothing_t * >& points, int start, int end ) { points.RemoveAll(); int i; for ( i = start; i <= end; i++ ) { demosmoothing_t *p = &m_Smoothing.smooth[ i ]; if ( includeboundaries ) { if ( i == start ) { // Add it twice points.AddToTail( p ); continue; } else if ( i == end ) { // Add twice points.AddToTail( p ); continue; } } if ( usetarget && p->targetpoint ) { points.AddToTail( p ); } if ( !usetarget && p->samplepoint ) { points.AddToTail( p ); } } } demosmoothing_t *CDemoSmootherPanel::GetBoundedSample( CUtlVector< demosmoothing_t * >& points, int sample ) { int c = points.Count(); if ( sample < 0 ) return points[ 0 ]; else if ( sample >= c ) return points[ c - 1 ]; return points[ sample ]; } //----------------------------------------------------------------------------- // Purpose: // Input : t - // points - // prev - // next - //----------------------------------------------------------------------------- void CDemoSmootherPanel::FindSpanningPoints( int tick, CUtlVector< demosmoothing_t * >& points, int& prev, int& next ) { prev = -1; next = 0; int c = points.Count(); int i; for ( i = 0; i < c; i++ ) { demosmoothing_t *p = points[ i ]; if ( tick < p->frametick ) break; } next = i; prev = i - 1; next = clamp( next, 0, c - 1 ); prev = clamp( prev, 0, c - 1 ); } void CDemoSmootherPanel::OnSplineSampleOrigin( void ) { if ( !m_bHasSelection ) return; int c = m_Smoothing.smooth.Count(); if ( c < 2 ) return; demosmoothing_t *pstart = &m_Smoothing.smooth[ m_nSelection[ 0 ] ]; demosmoothing_t *pend = &m_Smoothing.smooth[ m_nSelection[ 1 ] ]; if ( pend->frametick - pstart->frametick <= 0 ) return; CUtlVector< demosmoothing_t * > points; AddSamplePoints( false, false, points, m_nSelection[ 0 ], m_nSelection[ 1 ] ); if ( points.Count() <= 0 ) return; m_bDirty = true; PushUndo( "spline origin" ); for ( int i = m_nSelection[0]; i <= m_nSelection[1]; i++ ) { demosmoothing_t *p = &m_Smoothing.smooth[ i ]; demosmoothing_t *earliest; demosmoothing_t *current; demosmoothing_t *next; demosmoothing_t *latest; int cur; int cur2; FindSpanningPoints( p->frametick, points, cur, cur2 ); earliest = GetBoundedSample( points, cur - 1 ); current = GetBoundedSample( points, cur ); next = GetBoundedSample( points, cur2 ); latest = GetBoundedSample( points, cur2 + 1 ); float frac = 0.0f; float dt = next->frametick - current->frametick; if ( dt > 0.0f ) { frac = (float)( p->frametick - current->frametick ) / dt; } frac = clamp( frac, 0.0f, 1.0f ); Vector splined; Catmull_Rom_Spline_Normalize( earliest->vecmoved, current->vecmoved, next->vecmoved, latest->vecmoved, frac, splined ); p->info.flags |= FDEMO_USE_ORIGIN2; p->info.viewOrigin2 = splined; } PushRedo( "spline origin" ); } void CDemoSmootherPanel::OnSplineSampleAngles( void ) { if ( !m_bHasSelection ) return; int c = m_Smoothing.smooth.Count(); if ( c < 2 ) return; demosmoothing_t *pstart = &m_Smoothing.smooth[ m_nSelection[ 0 ] ]; demosmoothing_t *pend = &m_Smoothing.smooth[ m_nSelection[ 1 ] ]; if ( pend->frametick - pstart->frametick <= 0 ) return; CUtlVector< demosmoothing_t * > points; AddSamplePoints( false, false, points, m_nSelection[ 0 ], m_nSelection[ 1 ] ); if ( points.Count() <= 0 ) return; m_bDirty = true; PushUndo( "spline angles" ); for ( int i = m_nSelection[0]; i <= m_nSelection[1]; i++ ) { demosmoothing_t *p = &m_Smoothing.smooth[ i ]; demosmoothing_t *current; demosmoothing_t *next; int cur; int cur2; FindSpanningPoints( p->frametick, points, cur, cur2 ); current = GetBoundedSample( points, cur ); next = GetBoundedSample( points, cur2 ); float frac = 0.0f; float dt = next->frametick - current->frametick; if ( dt > 0.0f ) { frac = (float)( p->frametick - current->frametick ) / dt; } frac = clamp( frac, 0.0f, 1.0f ); frac = SimpleSpline( frac ); QAngle splined; InterpolateAngles( current->angmoved, next->angmoved, splined, frac ); p->info.flags |= FDEMO_USE_ANGLES2; p->info.viewAngles2 = splined; p->info.localViewAngles2 = splined; } PushRedo( "spline angles" ); } void CDemoSmootherPanel::OnLookAtPoints( bool spline ) { if ( !m_bHasSelection ) return; int c = m_Smoothing.smooth.Count(); int i; if ( c < 2 ) return; demosmoothing_t *pstart = &m_Smoothing.smooth[ m_nSelection[ 0 ] ]; demosmoothing_t *pend = &m_Smoothing.smooth[ m_nSelection[ 1 ] ]; if ( pend->frametick - pstart->frametick <= 0 ) return; CUtlVector< demosmoothing_t * > points; AddSamplePoints( true, false, points, m_nSelection[ 0 ], m_nSelection[ 1 ] ); if ( points.Count() < 1 ) return; m_bDirty = true; PushUndo( "lookat points" ); for ( i = m_nSelection[0]; i <= m_nSelection[1]; i++ ) { demosmoothing_t *p = &m_Smoothing.smooth[ i ]; demosmoothing_t *earliest; demosmoothing_t *current; demosmoothing_t *next; demosmoothing_t *latest; int cur; int cur2; FindSpanningPoints( p->frametick, points, cur, cur2 ); earliest = GetBoundedSample( points, cur - 1 ); current = GetBoundedSample( points, cur ); next = GetBoundedSample( points, cur2 ); latest = GetBoundedSample( points, cur2 + 1 ); float frac = 0.0f; float dt = next->frametick - current->frametick; if ( dt > 0.0f ) { frac = (float)( p->frametick - current->frametick ) / dt; } frac = clamp( frac, 0.0f, 1.0f ); Vector splined; if ( spline ) { Catmull_Rom_Spline_Normalize( earliest->vectarget, current->vectarget, next->vectarget, latest->vectarget, frac, splined ); } else { Vector d = next->vectarget - current->vectarget; VectorMA( current->vectarget, frac, d, splined ); } Vector vecToTarget = splined - ( p->info.GetViewOrigin() + m_vecEyeOffset ); VectorNormalize( vecToTarget ); QAngle angles; VectorAngles( vecToTarget, angles ); p->info.flags |= FDEMO_USE_ANGLES2; p->info.viewAngles2 = angles; p->info.localViewAngles2 = angles; } PushRedo( "lookat points" ); } void CDemoSmootherPanel::SetLastFrame( bool jumptotarget, int frame ) { // bool changed = frame != m_nPreviewLastFrame; int useFrame = max( m_Smoothing.m_nFirstSelectableSample, frame ); m_nPreviewLastFrame = useFrame; /* if ( changed && !m_pLockCamera->IsSelected() ) { // Reset default view/angles demosmoothing_t *p = GetCurrent(); if ( p ) { if ( p->samplepoint && !jumptotarget ) { m_ViewOrigin = p->vecmoved; m_ViewAngles = p->angmoved; } else if ( p->targetpoint && jumptotarget ) { m_ViewOrigin = p->vectarget - m_vecEyeOffset; } else { if ( m_bPreviewing && m_bPreviewOriginal ) { m_ViewOrigin = p->info.viewOrigin; m_ViewAngles = p->info.viewAngles; } else { m_ViewOrigin = p->info.GetViewOrigin(); m_ViewAngles = p->info.GetViewAngles(); } } } } */ } // Undo/Redo void CDemoSmootherPanel::Undo( void ) { if ( m_UndoStack.Size() > 0 && m_nUndoLevel > 0 ) { m_nUndoLevel--; DemoSmoothUndo *u = m_UndoStack[ m_nUndoLevel ]; Assert( u->undo ); m_Smoothing = *(u->undo); } InvalidateLayout(); } void CDemoSmootherPanel::Redo( void ) { if ( m_UndoStack.Size() > 0 && m_nUndoLevel <= m_UndoStack.Size() - 1 ) { DemoSmoothUndo *u = m_UndoStack[ m_nUndoLevel ]; Assert( u->redo ); m_Smoothing = *(u->redo); m_nUndoLevel++; } InvalidateLayout(); } void CDemoSmootherPanel::PushUndo( const char *description ) { Assert( !m_bRedoPending ); m_bRedoPending = true; WipeRedo(); // Copy current data CSmoothingContext *u = new CSmoothingContext; *u = m_Smoothing; DemoSmoothUndo *undo = new DemoSmoothUndo; undo->undo = u; undo->redo = NULL; undo->udescription = COM_StringCopy( description ); undo->rdescription = NULL; m_UndoStack.AddToTail( undo ); m_nUndoLevel++; } void CDemoSmootherPanel::PushRedo( const char *description ) { Assert( m_bRedoPending ); m_bRedoPending = false; // Copy current data CSmoothingContext *r = new CSmoothingContext; *r = m_Smoothing; DemoSmoothUndo *undo = m_UndoStack[ m_nUndoLevel - 1 ]; undo->redo = r; undo->rdescription = COM_StringCopy( description ); } void CDemoSmootherPanel::WipeUndo( void ) { while ( m_UndoStack.Size() > 0 ) { DemoSmoothUndo *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 CDemoSmootherPanel::WipeRedo( void ) { // Wipe everything above level while ( m_UndoStack.Size() > m_nUndoLevel ) { DemoSmoothUndo *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 *CDemoSmootherPanel::GetUndoDescription( void ) { if ( m_nUndoLevel != 0 ) { DemoSmoothUndo *u = m_UndoStack[ m_nUndoLevel - 1 ]; return u->udescription; } return "???undo"; } //----------------------------------------------------------------------------- // Purpose: // Output : const char //----------------------------------------------------------------------------- const char *CDemoSmootherPanel::GetRedoDescription( void ) { if ( m_nUndoLevel != m_UndoStack.Size() ) { DemoSmoothUndo *u = m_UndoStack[ m_nUndoLevel ]; return u->rdescription; } return "???redo"; } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CDemoSmootherPanel::CanRedo( void ) { if ( !m_UndoStack.Count() ) return false; if ( m_nUndoLevel == m_UndoStack.Count() ) return false; return true; } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CDemoSmootherPanel::CanUndo( void ) { if ( !m_UndoStack.Count() ) return false; if ( m_nUndoLevel == 0 ) return false; return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDemoSmootherPanel::OnToggleKeyFrame( void ) { demosmoothing_t *p = GetCurrent(); if ( !p ) return; m_bDirty = true; PushUndo( "toggle keyframe" ); // use orginal data by default p->angmoved = p->info.GetViewAngles(); p->vecmoved = p->info.GetViewOrigin(); if ( !p->samplepoint ) { if ( g_pDemoUI->IsInDriveMode() ) { g_pDemoUI->GetDriveViewPoint( p->vecmoved, p->angmoved ); } if ( g_pDemoUI2->IsInDriveMode() ) { g_pDemoUI2->GetDriveViewPoint( p->vecmoved, p->angmoved ); } p->samplepoint = true; } else { p->samplepoint = false; } PushRedo( "toggle keyframe" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDemoSmootherPanel::OnToggleLookTarget( void ) { demosmoothing_t *p = GetCurrent(); if ( !p ) return; m_bDirty = true; PushUndo( "toggle look target" ); // use orginal data by default p->vectarget = p->info.GetViewOrigin(); if ( !p->targetpoint ) { QAngle angles; g_pDemoUI->GetDriveViewPoint( p->vectarget, angles ); g_pDemoUI2->GetDriveViewPoint( p->vectarget, angles ); p->targetpoint = true; } else { p->targetpoint = false; } PushRedo( "toggle look target" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDemoSmootherPanel::OnNextKey() { if( !m_bHasSelection ) return; int start = m_nPreviewLastFrame + 1; int maxmove = m_nSelection[1] - m_nSelection[0] + 1; int moved = 0; while ( moved < maxmove ) { demosmoothing_t *p = &m_Smoothing.smooth[ start ]; if ( p->samplepoint ) { SetLastFrame( false, start ); break; } start++; if ( start > m_nSelection[1] ) start = m_nSelection[0]; moved++; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDemoSmootherPanel::OnPrevKey() { if( !m_bHasSelection ) return; int start = m_nPreviewLastFrame - 1; int maxmove = m_nSelection[1] - m_nSelection[0] + 1; int moved = 0; while ( moved < maxmove && start >= 0 ) { demosmoothing_t *p = &m_Smoothing.smooth[ start ]; if ( p->samplepoint ) { SetLastFrame( false, start ); break; } start--; if ( start < m_nSelection[0] ) start = m_nSelection[1]; moved++; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDemoSmootherPanel::OnNextTarget() { if( !m_bHasSelection ) return; int start = m_nPreviewLastFrame + 1; int maxmove = m_nSelection[1] - m_nSelection[0] + 1; int moved = 0; while ( moved < maxmove ) { demosmoothing_t *p = &m_Smoothing.smooth[ start ]; if ( p->targetpoint ) { SetLastFrame( true, start ); break; } start++; if ( start > m_nSelection[1] ) start = m_nSelection[0]; moved++; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDemoSmootherPanel::OnPrevTarget() { if( !m_bHasSelection ) return; int start = m_nPreviewLastFrame - 1; int maxmove = m_nSelection[1] - m_nSelection[0] + 1; int moved = 0; while ( moved < maxmove ) { demosmoothing_t *p = &m_Smoothing.smooth[ start ]; if ( p->targetpoint ) { SetLastFrame( true, start ); break; } start--; if ( start < m_nSelection[0] ) start = m_nSelection[1]; moved++; } } void CDemoSmootherPanel::DrawTargetSpline() { if ( !m_bHasSelection ) return; int c = m_Smoothing.smooth.Count(); int i; if ( c < 2 ) return; demosmoothing_t *pstart = &m_Smoothing.smooth[ m_nSelection[ 0 ] ]; demosmoothing_t *pend = &m_Smoothing.smooth[ m_nSelection[ 1 ] ]; if ( pend->frametick - pstart->frametick <= 0 ) return; CUtlVector< demosmoothing_t * > points; AddSamplePoints( true, false, points, m_nSelection[ 0 ], m_nSelection[ 1 ] ); if ( points.Count() < 1 ) return; Vector previous(0,0,0); for ( i = m_nSelection[0]; i <= m_nSelection[1]; i++ ) { demosmoothing_t *p = &m_Smoothing.smooth[ i ]; demosmoothing_t *earliest; demosmoothing_t *current; demosmoothing_t *next; demosmoothing_t *latest; int cur; int cur2; FindSpanningPoints( p->frametick, points, cur, cur2 ); earliest = GetBoundedSample( points, cur - 1 ); current = GetBoundedSample( points, cur ); next = GetBoundedSample( points, cur2 ); latest = GetBoundedSample( points, cur2 + 1 ); float frac = 0.0f; float dt = next->frametick - current->frametick; if ( dt > 0.0f ) { frac = (float)( p->frametick - current->frametick ) / dt; } frac = clamp( frac, 0.0f, 1.0f ); Vector splined; Catmull_Rom_Spline_Normalize( earliest->vectarget, current->vectarget, next->vectarget, latest->vectarget, frac, splined ); if ( i > m_nSelection[0] ) { RenderLine( previous, splined, Color( 0, 255, 0, 255 ), true ); } previous = splined; } } void CDemoSmootherPanel::DrawKeySpline() { if ( !m_bHasSelection ) return; int c = m_Smoothing.smooth.Count(); int i; if ( c < 2 ) return; demosmoothing_t *pstart = &m_Smoothing.smooth[ m_nSelection[ 0 ] ]; demosmoothing_t *pend = &m_Smoothing.smooth[ m_nSelection[ 1 ] ]; if ( pend->frametick - pstart->frametick <= 0 ) return; CUtlVector< demosmoothing_t * > points; AddSamplePoints( false, false, points, m_nSelection[ 0 ], m_nSelection[ 1 ] ); if ( points.Count() < 1 ) return; Vector previous(0,0,0); for ( i = m_nSelection[0]; i <= m_nSelection[1]; i++ ) { demosmoothing_t *p = &m_Smoothing.smooth[ i ]; demosmoothing_t *earliest; demosmoothing_t *current; demosmoothing_t *next; demosmoothing_t *latest; int cur; int cur2; FindSpanningPoints( p->frametick, points, cur, cur2 ); earliest = GetBoundedSample( points, cur - 1 ); current = GetBoundedSample( points, cur ); next = GetBoundedSample( points, cur2 ); latest = GetBoundedSample( points, cur2 + 1 ); float frac = 0.0f; float dt = next->frametick - current->frametick; if ( dt > 0.0f ) { frac = (float)( p->frametick - current->frametick ) / dt; } frac = clamp( frac, 0.0f, 1.0f ); Vector splined; Catmull_Rom_Spline_Normalize( earliest->vecmoved, current->vecmoved, next->vecmoved, latest->vecmoved, frac, splined ); splined += m_vecEyeOffset; if ( i > m_nSelection[0] ) { RenderLine( previous, splined, Color( 0, 255, 0, 255 ), true ); } previous = splined; } } void CDemoSmootherPanel::OnSmoothEdges( bool left, bool right ) { if ( !m_bHasSelection ) return; if ( !left && !right ) return; int c = m_Smoothing.smooth.Count(); // Get number of frames char sz[ 512 ]; m_pFixEdgeFrames->GetText( sz, sizeof( sz ) ); int frames = atoi( sz ); if ( frames <= 2 ) return; m_bDirty = true; PushUndo( "smooth edges" ); if ( left && m_nSelection[0] > 0 ) { PerformLinearInterpolatedAngleSmoothing( m_nSelection[ 0 ] - 1, m_nSelection[ 0 ] + frames ); } if ( right && m_nSelection[1] < c - 1 ) { PerformLinearInterpolatedAngleSmoothing( m_nSelection[ 1 ] - frames, m_nSelection[ 1 ] + 1 ); } PushRedo( "smooth edges" ); } void CDemoSmootherPanel::OnSaveKey() { if ( !m_bHasSelection ) return; demosmoothing_t *p = GetCurrent(); if ( !p ) return; if ( !p->samplepoint ) return; m_bDirty = true; PushUndo( "save key" ); p->info.viewAngles2 = p->angmoved; p->info.localViewAngles2 = p->angmoved; p->info.viewOrigin2 = p->vecmoved; p->info.flags |= FDEMO_USE_ORIGIN2; p->info.flags |= FDEMO_USE_ANGLES2; PushRedo( "save key" ); } void CDemoSmootherPanel::OnSetView() { if ( !m_bHasSelection ) return; demosmoothing_t *p = GetCurrent(); if ( !p ) return; Vector origin = p->info.GetViewOrigin(); QAngle angle = p->info.GetViewAngles(); g_pDemoUI->SetDriveViewPoint( origin, angle ); g_pDemoUI2->SetDriveViewPoint( origin, angle ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDemoSmootherPanel::OnGotoFrame() { int c = m_Smoothing.smooth.Count(); if ( c < 2 ) return; char sz[ 256 ]; m_pGotoFrame->GetText( sz, sizeof( sz ) ); int frame = atoi( sz ); if ( !m_bPreviewing ) { if ( !m_bHasSelection ) { m_pStartFrame->SetText( va( "%i", 0 ) ); m_pEndFrame->SetText( va( "%i", c - 1 ) ); OnSelect(); } OnPreview( false ); OnTogglePause(); } if ( !m_bPreviewing ) return; SetLastFrame( false, frame ); m_iPreviewStartTick = GetTickForFrame( m_nPreviewLastFrame ); m_fPreviewCurrentTime = TICKS_TO_TIME( m_iPreviewStartTick ); } void CDemoSmootherPanel::OnOriginEaseCurve( EASEFUNC easefunc ) { if ( !m_bHasSelection ) return; int c = m_Smoothing.smooth.Count(); if ( c < 2 ) return; demosmoothing_t *pstart = &m_Smoothing.smooth[ m_nSelection[ 0 ] ]; demosmoothing_t *pend = &m_Smoothing.smooth[ m_nSelection[ 1 ] ]; float dt = pend->frametick - pstart->frametick; if ( dt <= 0.0f ) return; m_bDirty = true; PushUndo( "ease origin" ); Vector vstart, vend; vstart = pstart->info.GetViewOrigin(); vend = pend->info.GetViewOrigin(); for ( int i = m_nSelection[0]; i <= m_nSelection[1]; i++ ) { demosmoothing_t *p = &m_Smoothing.smooth[ i ]; float elapsed = p->frametick - pstart->frametick; float frac = elapsed / dt; // Apply ease function frac = (*easefunc)( frac ); frac = clamp( frac, 0.0f, 1.0f ); p->info.flags |= FDEMO_USE_ORIGIN2; Vector interpolated; VectorLerp( vstart, vend, frac, interpolated ); p->info.viewOrigin2 = interpolated; } PushRedo( "ease origin" ); } void CDemoSmootherPanel::ParseSmoothingInfo( CDemoFile &demoFile, CSmoothingContext& smoothing ) { democmdinfo_t info; int dummy; bool foundFirstSelectable = false; bool demofinished = false; while ( !demofinished ) { int tick = 0; byte cmd; bool swallowmessages = true; do { demoFile.ReadCmdHeader( cmd, tick ); // COMMAND HANDLERS switch ( cmd ) { case dem_synctick: break; case dem_stop: { swallowmessages = false; demofinished = true; } break; case dem_consolecmd: { demoFile.ReadConsoleCommand(); } break; case dem_datatables: { demoFile.ReadNetworkDataTables( NULL ); } break; case dem_stringtables: { demoFile.ReadStringTables( NULL ); } break; case dem_usercmd: { demoFile.ReadUserCmd( NULL, dummy ); } break; default: { swallowmessages = false; } break; } } while ( swallowmessages ); if ( demofinished ) { // StopPlayback(); return; } int curpos = demoFile.GetCurPos( true ); demoFile.ReadCmdInfo( info ); demoFile.ReadSequenceInfo( dummy, dummy ); demoFile.ReadRawData( NULL, 0 ); // Add to end of list demosmoothing_t smoothing_entry; smoothing_entry.file_offset = curpos; smoothing_entry.frametick = tick; smoothing_entry.info = info; smoothing_entry.samplepoint = false; smoothing_entry.vecmoved = info.GetViewOrigin(); smoothing_entry.angmoved = info.GetViewAngles(); smoothing_entry.targetpoint = false; smoothing_entry.vectarget = info.GetViewOrigin(); int sampleIndex = smoothing.smooth.AddToTail( smoothing_entry ); if ( !foundFirstSelectable && smoothing_entry.vecmoved.LengthSqr() > 0.0f ) { foundFirstSelectable = true; smoothing.m_nFirstSelectableSample = sampleIndex; } } } void CDemoSmootherPanel::LoadSmoothingInfo( const char *filename, CSmoothingContext& smoothing ) { char name[ MAX_OSPATH ]; Q_strncpy (name, filename, sizeof(name) ); Q_DefaultExtension( name, ".dem", sizeof( name ) ); CDemoFile demoFile; if ( !demoFile.Open( filename, true ) ) { ConMsg( "ERROR: couldn't open %s.\n", name ); return; } demoheader_t * header = demoFile.ReadDemoHeader(); if ( !header ) { demoFile.Close(); return; } ConMsg ("Smoothing demo from %s ...", name ); smoothing.active = true; Q_strncpy( smoothing.filename, name, sizeof(smoothing.filename) ); smoothing.smooth.RemoveAll(); ClearSmoothingInfo( smoothing ); ParseSmoothingInfo( demoFile, smoothing ); demoFile.Close(); //Performsmoothing( smooth ); //SaveSmoothedDemo( name, smooth ); ConMsg ( " done.\n" ); } void CDemoSmootherPanel::ClearSmoothingInfo( CSmoothingContext& smoothing ) { int c = smoothing.smooth.Count(); int i; for ( i = 0; i < c; i++ ) { demosmoothing_t *p = &smoothing.smooth[ i ]; p->info.Reset(); p->vecmoved = p->info.GetViewOrigin(); p->angmoved = p->info.GetViewAngles(); p->samplepoint = false; p->vectarget = p->info.GetViewOrigin(); p->targetpoint = false; } } void CDemoSmootherPanel::SaveSmoothingInfo( char const *filename, CSmoothingContext& smoothing ) { // Nothing to do int c = smoothing.smooth.Count(); if ( !c ) return; IFileSystem *fs = g_pFileSystem; FileHandle_t infile, outfile; COM_OpenFile( filename, &infile ); if ( infile == FILESYSTEM_INVALID_HANDLE ) return; int filesize = fs->Size( infile ); char outfilename[ 512 ]; Q_StripExtension( filename, outfilename, sizeof( outfilename ) ); Q_strncat( outfilename, "_smooth", sizeof(outfilename), COPY_ALL_CHARACTERS ); Q_DefaultExtension( outfilename, ".dem", sizeof( outfilename ) ); outfile = fs->Open( outfilename, "wb" ); if ( outfile == FILESYSTEM_INVALID_HANDLE ) { fs->Close( infile ); return; } int i; int lastwritepos = 0; for ( i = 0; i < c; i++ ) { demosmoothing_t *p = &smoothing.smooth[ i ]; int copyamount = p->file_offset - lastwritepos; COM_CopyFileChunk( outfile, infile, copyamount ); fs->Seek( infile, p->file_offset, FILESYSTEM_SEEK_HEAD ); // wacky hacky overwriting fs->Write( &p->info, sizeof( democmdinfo_t ), outfile ); lastwritepos = fs->Tell( outfile ); fs->Seek( infile, p->file_offset + sizeof( democmdinfo_t ), FILESYSTEM_SEEK_HEAD ); } int final = filesize - lastwritepos; COM_CopyFileChunk( outfile, infile, final ); fs->Close( outfile ); fs->Close( infile ); }