//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//===========================================================================//
#include "client_pch.h"
#include "cl_demosmootherpanel.h"
#include <vgui_controls/Button.h>
#include <vgui_controls/CheckButton.h>
#include <vgui_controls/Label.h>

#include <vgui_controls/Controls.h>
#include <vgui/ISystem.h>
#include <vgui/ISurface.h>
#include <vgui_controls/PropertySheet.h>
#include <vgui/IVGui.h>
#include <vgui_controls/FileOpenDialog.h>
#include <vgui_controls/ProgressBar.h>
#include <vgui_controls/ListPanel.h>
#include <vgui_controls/MenuButton.h>
#include <vgui_controls/Menu.h>
#include <vgui_controls/TextEntry.h>
#include <vgui/IInput.h>

#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 );
}