//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//
/*

  glapp.c - Simple OpenGL shell
  
	There are several options allowed on the command line.  They are:
	-height : what window/screen height do you want to use?
	-width  : what window/screen width do you want to use?
	-bpp    : what color depth do you want to use?
	-window : create a rendering window rather than full-screen
	-fov    : use a field of view other than 90 degrees
*/


//
//                 Half-Life Model Viewer (c) 1999 by Mete Ciragan
//
// file:           MatSysWindow.cpp
// last modified:  May 04 1999, Mete Ciragan
// copyright:      The programs and associated files contained in this
//                 distribution were developed by Mete Ciragan. The programs
//                 are not in the public domain, but they are freely
//                 distributable without licensing fees. These programs are
//                 provided without guarantee or warrantee expressed or
//                 implied.
//
// version:        1.2
//
// email:          mete@swissquake.ch
// web:            http://www.swissquake.ch/chumbalum-soft/
//
#include <mxtk/mx.h>
#include <mxtk/mxMessageBox.h>
#include <mxtk/mxTga.h>
#include <mxtk/mxPcx.h>
#include <mxtk/mxBmp.h>
#include <mxtk/mxMatSysWindow.h>
// #include "gl.h"
// #include <GL/glu.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "tier1/convar.h"
#include <string.h>
#include "MatSysWin.h"
#include "MDLViewer.h"
#include "StudioModel.h"
#include "ControlPanel.h"
#include "ViewerSettings.h"
#include "materialsystem/imaterialsystem.h"
#include "materialsystem/imaterialproxyfactory.h"
#include "FileSystem.h"
#include <KeyValues.h>
#include "materialsystem/IMesh.h"
#include "materialsystem/IMaterialSystemHardwareConfig.h"
#include "materialsystem/ITexture.h"
#include "materialsystem/MaterialSystem_Config.h"
#include "tier0/dbg.h"
#include "istudiorender.h"
#include "tier0/icommandline.h"
#include "mathlib/vmatrix.h"
#include "studio_render.h"
#include "vstdlib/cvar.h"
#include "SoundEmitterSystem/isoundemittersystembase.h"
#include "soundsystem/isoundsystem.h"
#include "soundchars.h"
#include "mathlib/softbodyenvironment.h"
#include "valve_ipc_win32.h"

extern char g_appTitle[];
extern bool g_bInError;
extern int g_dxlevel;

extern ISoundEmitterSystemBase *g_pSoundEmitterBase;

extern CValveIpcClientUtl g_HlmvIpcClient;
extern bool g_bHlmvMaster;

extern CSoftbodyEnvironment g_SoftbodyEnvironment;


void UpdateSounds()
{
	static double prev = 0;
	double curr = (double) mx::getTickCount () / 1000.0;
	if ( prev != 0 )
	{
		double dt = (curr - prev);
		g_pSoundSystem->Update( dt * g_viewerSettings.speedScale );
	}
	prev = curr;
}


// FIXME: move all this to mxMatSysWin

class DummyMaterialProxyFactory : public IMaterialProxyFactory
{
public:
	virtual IMaterialProxy *CreateProxy( const char *proxyName )	{return NULL;}
	virtual void DeleteProxy( IMaterialProxy *pProxy )				{}
	virtual CreateInterfaceFn GetFactory()							{return NULL;}
};
DummyMaterialProxyFactory	g_DummyMaterialProxyFactory;


static void ReleaseMaterialSystemObjects( int nChangeFlags )
{
	StudioModel::ReleaseStudioModel();
}

static void RestoreMaterialSystemObjects( int nChangeFlags )
{
	StudioModel::RestoreStudioModel();
	g_ControlPanel->OnLoadModel();
}

static ConVar mat_bumpmap( "mat_bumpmap", "1" );
static ConVar mat_specular( "mat_specular", "1" );
static ConVar mat_parallaxmap( "mat_parallaxmap", "0" );
static ConVar mat_displacementmap( "mat_displacementmap", "1" );

void InitMaterialSystemConfig(MaterialSystem_Config_t *pConfig)
{
	mat_bumpmap.SetValue( g_viewerSettings.enableNormalMapping );
	mat_displacementmap.SetValue( g_viewerSettings.enableDisplacementMapping );
	mat_specular.SetValue( g_viewerSettings.enableSpecular );
	mat_parallaxmap.SetValue( g_viewerSettings.enableParallaxMapping );
}

MatSysWindow *g_MatSysWindow = 0;

Vector g_vright( 50, 50, 0 );		// needs to be set to viewer's right in order for chrome to work

IMaterial *g_materialBackground = NULL;
IMaterial *g_materialWireframe = NULL;
IMaterial *g_materialWireframeVertexColor = NULL;
IMaterial *g_materialWireframeVertexColorNoCull = NULL;
IMaterial *g_materialDebugCopyBaseTexture = NULL;
IMaterial *g_materialFlatshaded = NULL;
IMaterial *g_materialSmoothshaded = NULL;
IMaterial *g_materialBones = NULL;
IMaterial *g_materialLines = NULL;
IMaterial *g_materialFloor = NULL;
IMaterial *g_materialVertexColor = NULL;
IMaterial *g_materialShadow = NULL;
IMaterial *g_materialArcActive = NULL;
IMaterial *g_materialArcInActive = NULL;
IMaterial *g_materialDebugText = NULL;

MatSysWindow::MatSysWindow (mxWindow *parent, int x, int y, int w, int h, const char *label, int style)
: mxMatSysWindow (parent, x, y, w, h, label, style)
{
	g_pMaterialSystem->SetMaterialProxyFactory( &g_DummyMaterialProxyFactory );

	m_pCubemapTexture = NULL;
	m_hWnd = (HWND)getHandle();

	MaterialSystem_Config_t config;
	config = g_pMaterialSystem->GetCurrentConfigForVideoCard();
	InitMaterialSystemConfig(&config);
	if ( g_dxlevel != 0 )
	{
		config.dxSupportLevel = g_dxlevel;
	}
	
//	config.m_VideoMode.m_Width = config.m_VideoMode.m_Height = 0;
	config.SetFlag( MATSYS_VIDCFG_FLAGS_WINDOWED, true );
	config.SetFlag( MATSYS_VIDCFG_FLAGS_RESIZING, true );

	if (!g_pMaterialSystem->SetMode( ( void * )m_hWnd, config ) )
	{
		return;
	}
	
	g_pMaterialSystem->OverrideConfig( config, false );

	g_pMaterialSystem->AddReleaseFunc( ReleaseMaterialSystemObjects );
	g_pMaterialSystem->AddRestoreFunc( RestoreMaterialSystemObjects );

	m_pCubemapTexture = g_pMaterialSystem->FindTexture( "hlmv/cubemap", NULL, true );
	m_pCubemapTexture->IncrementReferenceCount();
	CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
	pRenderContext->BindLocalCubemap( m_pCubemapTexture );

	g_materialBackground	= g_pMaterialSystem->FindMaterial("hlmv/background", TEXTURE_GROUP_OTHER, true);
	g_materialWireframe		= g_pMaterialSystem->FindMaterial("debug/debugmrmwireframe", TEXTURE_GROUP_OTHER, true);
	g_materialWireframeVertexColor = g_pMaterialSystem->FindMaterial("debug/debugwireframevertexcolor", TEXTURE_GROUP_OTHER, true);

	// test: create this from code - you need a vmt to make $nocull 1 happen, can't do it from the render context
	{
		KeyValues *pVMTKeyValues = new KeyValues( "Wireframe" );
		pVMTKeyValues->SetInt("$ignorez", 1);
		pVMTKeyValues->SetInt("$nocull", 1);
		pVMTKeyValues->SetInt("$vertexcolor", 1);
		pVMTKeyValues->SetInt("$decal", 1);
		g_materialWireframeVertexColorNoCull = g_pMaterialSystem->CreateMaterial( "debug/wireframenocull", pVMTKeyValues );
	}
	{
		KeyValues *pVMTKeyValues = new KeyValues( "UnlitGeneric" );
		pVMTKeyValues->SetString("$basetexture", "vgui/white" );
		g_materialDebugCopyBaseTexture = g_pMaterialSystem->CreateMaterial( "debug/copybasetexture", pVMTKeyValues );

	}

	g_materialFlatshaded	= g_pMaterialSystem->FindMaterial("debug/debugdrawflatpolygons", TEXTURE_GROUP_OTHER, true);
	g_materialSmoothshaded	= g_pMaterialSystem->FindMaterial("debug/debugmrmfullbright2", TEXTURE_GROUP_OTHER, true);
	g_materialBones			= g_pMaterialSystem->FindMaterial("debug/debugskeleton", TEXTURE_GROUP_OTHER, true);
	g_materialLines			= g_pMaterialSystem->FindMaterial("debug/debugwireframevertexcolor", TEXTURE_GROUP_OTHER, true);
	g_materialFloor			= g_pMaterialSystem->FindMaterial("hlmv/floor", TEXTURE_GROUP_OTHER, true);
	g_materialVertexColor   = g_pMaterialSystem->FindMaterial("debug/debugvertexcolor", TEXTURE_GROUP_OTHER, true);
	g_materialShadow		= g_pMaterialSystem->FindMaterial("hlmv/shadow", TEXTURE_GROUP_OTHER, true);
	g_materialDebugText		= g_pMaterialSystem->FindMaterial("hlmv/debugtext", TEXTURE_GROUP_OTHER, true);

	{
		KeyValues *pVMTKeyValues = new KeyValues( "UnLitGeneric" );
		pVMTKeyValues->SetInt("$nocull", 1);
		pVMTKeyValues->SetInt("$vertexcolor", 1);
		pVMTKeyValues->SetFloat("$alpha", 0.5f);
		g_materialArcActive = g_pMaterialSystem->CreateMaterial( "hlmv/arc_active", pVMTKeyValues );
	}

	{
		KeyValues *pVMTKeyValues = new KeyValues( "UnLitGeneric" );
		pVMTKeyValues->SetInt("$nocull", 1);
		pVMTKeyValues->SetInt("$vertexcolor", 1);
		pVMTKeyValues->SetFloat("$alpha", 0.2f);
		g_materialArcInActive = g_pMaterialSystem->CreateMaterial( "hlmv/arc_inactive", pVMTKeyValues );
	}

	if (!parent)
		setVisible (true);
	else
		mx::setIdleWindow (this);
}



MatSysWindow::~MatSysWindow ()
{
	if (m_pCubemapTexture)
	{
		m_pCubemapTexture->DecrementReferenceCount();
	}
	mx::setIdleWindow (0);
}



int
MatSysWindow::handleEvent (mxEvent *event)
{
	MDLCACHE_CRITICAL_SECTION_( g_pMDLCache );

	static float oldrx = 0, oldry = 0, oldtz = 50, oldtx = 0, oldty = 0;
	static float oldlrx = 0, oldlry = 0;
	static int oldx, oldy;

	switch (event->event)
	{

	case mxEvent::Idle:
	{
		static double prev;
		double curr = (double) mx::getTickCount () / 1000.0;
		double dt = (curr - prev);

		// clamp to 100fps
		if (dt >= 0.0 && dt < 0.01)
		{
			Sleep( 10 - dt * 1000.0 );
			return 1;
		}

		if ( prev != 0.0 )
		{
	//		dt = 0.001;

			g_pStudioModel->AdvanceFrame ( dt * g_viewerSettings.speedScale );
			if ( g_viewerSettings.animateWeapons )
			{
				const char *pszMainSequenceName = g_pStudioModel->GetSequenceName( g_pStudioModel->GetSequence() );
				if ( pszMainSequenceName )
				{
					for ( int i = 0; i < HLMV_MAX_MERGED_MODELS; i++ )
					{
						if ( !g_pStudioExtraModel[ i ] )
							continue;

						// match weapon sequence and frame to marine
						int iSequence = g_pStudioExtraModel[ i ]->LookupSequence( pszMainSequenceName );
						if ( iSequence == -1 )
						{
							g_pStudioExtraModel[ i ]->SetFrame( 0 );
						}
						else
						{
							g_pStudioExtraModel[ i ]->SetSequence( iSequence );
							g_pStudioExtraModel[ i ]->SetFrame( g_pStudioModel->GetCycle() * g_pStudioExtraModel[ i ]->GetMaxFrame() );
						}
					}
				}
			}
			g_ControlPanel->updateFrameSlider( );
			g_ControlPanel->updateGroundSpeed( );
		}
		prev = curr;
		g_pStudioModel->SetSoftbodyOrientation();
		for ( int i = 0; i < HLMV_MAX_MERGED_MODELS; i++ )
		{
			if ( g_pStudioExtraModel[ i ] != NULL )
			{
				g_pStudioExtraModel[ i ]->SetSoftbodyOrientation();
			}
		}
		if ( g_viewerSettings.simulateSoftbodies )
		{
			g_SoftbodyEnvironment.Step( dt * g_viewerSettings.speedScale );
		}

		if (!g_viewerSettings.pause)
			redraw ();

		g_ControlPanel->updateTransitionAmount();

		UpdateSounds();

		return 1;
	}
	break;

	case mxEvent::MouseUp:
	{
		g_viewerSettings.mousedown = false;

		if ( g_pWidgetControl )
			g_pWidgetControl->m_WidgetState = WIDGET_STATE_NONE;
	}
	break;

	case mxEvent::MouseDown:
	{
		g_viewerSettings.mousedown = true;

		if ( g_pWidgetControl != NULL && g_viewerSettings.highlightHitbox >= 0 )
		{
			g_pWidgetControl->WidgetMouseDown( event->x, event->y );
			g_pWidgetControl->SetStateUsingInputColor( getViewportPixelColor( event->x, event->y ) );
			if ( g_pWidgetControl->m_WidgetState != WIDGET_STATE_NONE )
				return 1;
		}

		oldrx = g_pStudioModel->m_angles[0];
		oldry = g_pStudioModel->m_angles[1];
		oldtx = g_pStudioModel->m_origin[0];
		oldty = g_pStudioModel->m_origin[1];
		oldtz = g_pStudioModel->m_origin[2];
		oldx = event->x;
		oldy = event->y;
		oldlrx = g_viewerSettings.lightrot[1];
		oldlry = g_viewerSettings.lightrot[0];
		g_viewerSettings.pause = false;

		float r = 1.0/3.0 * min( w(), h() );

		float d = sqrt( ( float )( (event->x - w()/2) * (event->x - w()/2) + (event->y - h()/2) * (event->y - h()/2) ) );

		if ( d < r || !g_viewerSettings.allowOrbitYaw )
			g_viewerSettings.rotating = false;
		else
			g_viewerSettings.rotating = true;

		return 1;
	}
	break;

	case mxEvent::MouseDrag:
	{
		if (event->buttons & mxEvent::MouseLeftButton)
		{

			if ( g_pWidgetControl && g_pWidgetControl->m_WidgetState != WIDGET_STATE_NONE )
				{
					g_pWidgetControl->WidgetMouseDrag( event->x, event->y );
					break;
				}

			if ( g_viewerSettings.dotaMode )
			{
				if (event->modifiers & mxEvent::KeyShift)
				{
					g_pStudioModel->m_origin[1] = oldty - (float) (event->x - oldx);
					g_pStudioModel->m_origin[2] = oldtz + (float) (event->y - oldy);
				}
				else
				{
					float rx = (float) (event->x - oldx);
					oldx = event->x;

					QAngle movement;
					matrix3x4_t tmp1, tmp2, tmp3;

					movement = QAngle( 0, rx, 0 );
					AngleMatrix( g_pStudioModel->m_angles, tmp1 );
					AngleMatrix( movement, tmp2 );
					ConcatTransforms( tmp1, tmp2, tmp3 );
					MatrixAngles( tmp3, g_pStudioModel->m_angles );
				}
			}
			else if (event->modifiers & mxEvent::KeyShift)
			{
				g_pStudioModel->m_origin[1] = oldty - (float) (event->x - oldx) / 8.0;
				g_pStudioModel->m_origin[2] = oldtz + (float) (event->y - oldy) / 8.0;
			}
			else if (event->modifiers & mxEvent::KeyCtrl)
			{
				float ry = (float) (event->y - oldy);
				float rx = (float) (event->x - oldx);
				oldx = event->x;
				oldy = event->y;

				QAngle movement = QAngle( ry, rx, 0 );

				matrix3x4_t tmp1, tmp2, tmp3;
				AngleMatrix( g_viewerSettings.lightrot, tmp1 );
				AngleMatrix( movement, tmp2 );
				ConcatTransforms( tmp2, tmp1, tmp3 );
				MatrixAngles( tmp3, g_viewerSettings.lightrot );

				// g_viewerSettings.lightrot[0] = oldlrx + (float) (event->y - oldy);
				// g_viewerSettings.lightrot[1] = oldlry + (float) (event->x - oldx);
			}
			else
			{
				if (!g_viewerSettings.rotating)
				{
					float ry = (float) (event->y - oldy);
					float rx = (float) (event->x - oldx);
					oldx = event->x;
					oldy = event->y;

					QAngle movement;
					matrix3x4_t tmp1, tmp2, tmp3;

					movement = QAngle( 0, rx, 0 );
					AngleMatrix( g_pStudioModel->m_angles, tmp1 );
					AngleMatrix( movement, tmp2 );
					ConcatTransforms( tmp1, tmp2, tmp3 );
					MatrixAngles( tmp3, g_pStudioModel->m_angles );

					movement = QAngle( ry, 0, 0 );
					AngleMatrix( g_pStudioModel->m_angles, tmp1 );
					AngleMatrix( movement, tmp2 );
					ConcatTransforms( tmp2, tmp1, tmp3 );
					MatrixAngles( tmp3, g_pStudioModel->m_angles );
				}
				else
				{
					float ang1 = (180 / 3.1415) * atan2( oldx - w()/2.0, oldy - h()/2.0 );
					float ang2 = (180 / 3.1415) * atan2( event->x - w()/2.0, event->y - h()/2.0 );
					oldx = event->x;
					oldy = event->y;

					QAngle movement = QAngle( 0, 0, ang2 - ang1 );

					matrix3x4_t tmp1, tmp2, tmp3;
					AngleMatrix( g_pStudioModel->m_angles, tmp1 );
					AngleMatrix( movement, tmp2 );
					ConcatTransforms( tmp2, tmp1, tmp3 );
					MatrixAngles( tmp3, g_pStudioModel->m_angles );
				}
			}
		}
		else if (event->buttons & mxEvent::MouseRightButton)
		{
			if ( !g_viewerSettings.dotaMode )
			{
				g_pStudioModel->m_origin[0] = oldtx + (float) (event->y - oldy);
			}
		}

		if ( g_bHlmvMaster && g_HlmvIpcClient.Connect() )
		{
			CUtlBuffer cmd;
			CUtlBuffer res;

			matrix3x4_t m;
			g_pStudioModel->GetModelTransform( m );

			cmd.Printf( "%s %f %f %f %f %f %f %f %f %f %f %f %f",
				"hlmvModelTransform",
				m.m_flMatVal[0][0], m.m_flMatVal[0][1], m.m_flMatVal[0][2], m.m_flMatVal[0][3],
				m.m_flMatVal[1][0], m.m_flMatVal[1][1], m.m_flMatVal[1][2], m.m_flMatVal[1][3],
				m.m_flMatVal[2][0], m.m_flMatVal[2][1], m.m_flMatVal[2][2], m.m_flMatVal[2][3] );

			g_HlmvIpcClient.ExecuteCommand( cmd, res );
			g_HlmvIpcClient.Disconnect();
		}

		redraw ();

		return 1;
	}
	break;

	case mxEvent::KeyDown:
	{
		switch (event->key)
		{
			case VK_SPACE:
			{
				int iSeq = g_pStudioModel->GetSequence ();
				if (iSeq == g_pStudioModel->SetSequence (iSeq + 1))
				{
					g_pStudioModel->SetSequence (0);
				}
			}
			break;
		}
	}
	break;

	case mxEvent::DropFile:
	{
		V_strlower( event->szChars );
		g_ControlPanel->AddQCRecordPath( event->szChars );
		break;
	}
	break;

	} // switch (event->event)

	return 1;
}


void DrawBackground()
{
	if (!g_viewerSettings.showBackground)
		return;

	CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
	pRenderContext->Bind(g_materialBackground);
	pRenderContext->MatrixMode(MATERIAL_MODEL);
	pRenderContext->PushMatrix();
	pRenderContext->LoadIdentity();
	pRenderContext->MatrixMode(MATERIAL_VIEW);
	pRenderContext->PushMatrix();
	pRenderContext->LoadIdentity();
	{
		IMesh* pMesh = pRenderContext->GetDynamicMesh();
		CMeshBuilder meshBuilder;
		meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 );

		float dist=-15000.0f;
		float tMin=0, tMax=1;
		
		meshBuilder.Position3f(-dist, dist, dist);
		meshBuilder.TexCoord2f( 0, tMin,tMax );
		meshBuilder.Color4ub( 255, 255, 255, 255 );
		meshBuilder.AdvanceVertex();

		meshBuilder.Position3f( dist, dist, dist);
		meshBuilder.TexCoord2f( 0, tMax,tMax );
		meshBuilder.Color4ub( 255, 255, 255, 255 );
		meshBuilder.AdvanceVertex();

		meshBuilder.Position3f( dist,-dist, dist);
		meshBuilder.TexCoord2f( 0, tMax,tMin );
		meshBuilder.Color4ub( 255, 255, 255, 255 );
		meshBuilder.AdvanceVertex();

		meshBuilder.Position3f(-dist,-dist, dist);
		meshBuilder.TexCoord2f( 0, tMin,tMin );
		meshBuilder.Color4ub( 255, 255, 255, 255 );
		meshBuilder.AdvanceVertex();

		meshBuilder.End();
		pMesh->Draw();
	}
}

void DrawHelpers()
{
	if (g_viewerSettings.mousedown && g_viewerSettings.showOrbitCircle )
	{
		CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
		pRenderContext->Bind( g_materialBones );

		pRenderContext->MatrixMode(MATERIAL_MODEL);
		pRenderContext->LoadIdentity();
		pRenderContext->MatrixMode(MATERIAL_VIEW);
		pRenderContext->LoadIdentity();

		IMesh* pMesh = pRenderContext->GetDynamicMesh();

		CMeshBuilder meshBuilder;
		meshBuilder.Begin( pMesh, MATERIAL_LINES, 360 / 5 );

		if (g_viewerSettings.rotating)
			meshBuilder.Color3ub( 255, 255, 0 );
		else
			meshBuilder.Color3ub(   0, 255, 0 );

		for (int i = 0; i < 360; i += 5)
		{
			float a = i * (3.151492653/180.0f);

			if (g_viewerSettings.rotating)
				meshBuilder.Color3ub( 255, 255, 0 );
			else
				meshBuilder.Color3ub(   0, 255, 0 );

			meshBuilder.Position3f( sin( a ), cos( a ), -3.0f );
			meshBuilder.AdvanceVertex();
		}
		meshBuilder.End();
		pMesh->Draw();
	}
}


void DrawGroundPlane()
{
	if (!g_viewerSettings.showGround)
		return;

	CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
	pRenderContext->Bind(g_materialFloor);
	pRenderContext->MatrixMode(MATERIAL_MODEL);
	pRenderContext->PushMatrix();;
	pRenderContext->LoadIdentity();
	pRenderContext->MatrixMode(MATERIAL_VIEW);
	pRenderContext->PushMatrix();;
	pRenderContext->LoadIdentity();

	pRenderContext->MatrixMode( MATERIAL_VIEW );
	pRenderContext->LoadIdentity( );

	pRenderContext->Rotate( -90,  1, 0, 0 );	    // put Z going up
	pRenderContext->Rotate( -90,  0, 0, 1 );

    pRenderContext->Translate( -g_pStudioModel->m_origin[0],  -g_pStudioModel->m_origin[1],  -g_pStudioModel->m_origin[2] );

	pRenderContext->Rotate( g_pStudioModel->m_angles[1],  0, 0, 1 );
    pRenderContext->Rotate( g_pStudioModel->m_angles[0],  0, 1, 0 );
    pRenderContext->Rotate( g_pStudioModel->m_angles[2],  1, 0, 0 );

	static Vector tMap( 0, 0, 0 );
	static Vector dxMap( 1, 0, 0 );
	static Vector dyMap( 0, 1, 0 );

	Vector deltaPos;
	QAngle deltaAngles;

	g_pStudioModel->GetMovement( g_pStudioModel->m_prevGroundCycles, deltaPos, deltaAngles );

	IMesh* pMesh = pRenderContext->GetDynamicMesh();
	CMeshBuilder meshBuilder;
	meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 );

	float scale = 10.0;
	float dist=-100.0f;

	float dpdd = scale / dist;

	tMap.x = tMap.x + dxMap.x * deltaPos.x * dpdd + dxMap.y * deltaPos.y * dpdd;	
	tMap.y = tMap.y + dyMap.x * deltaPos.x * dpdd + dyMap.y * deltaPos.y * dpdd;

	while (tMap.x < 0.0) tMap.x +=  1.0;
	while (tMap.x > 1.0) tMap.x += -1.0;
	while (tMap.y < 0.0) tMap.y +=  1.0;
	while (tMap.y > 1.0) tMap.y += -1.0;

	VectorYawRotate( dxMap, -deltaAngles.y, dxMap );
	VectorYawRotate( dyMap, -deltaAngles.y, dyMap );

	// ARRGHHH, this is right but I don't know what I've done
	meshBuilder.Position3f( -dist, dist, 0 );
	meshBuilder.TexCoord2f( 0, tMap.x + (-dxMap.x - dyMap.x) * scale, tMap.y + (dxMap.y + dyMap.y) * scale );
	meshBuilder.Color4ub( 128, 128, 128, 255 );
	meshBuilder.AdvanceVertex();

	meshBuilder.Position3f( dist, dist, 0 );
	meshBuilder.TexCoord2f( 0, tMap.x + (dxMap.x - dyMap.x) * scale, tMap.y + (-dxMap.y + dyMap.y) * scale );
	meshBuilder.Color4ub( 128, 128, 128, 255 );
	meshBuilder.AdvanceVertex();

	meshBuilder.Position3f( dist, -dist, 0 );
	meshBuilder.TexCoord2f( 0, tMap.x + (dxMap.x + dyMap.x) * scale, tMap.y + (-dxMap.y - dyMap.y) * scale );
	meshBuilder.Color4ub( 128, 128, 128, 255 );
	meshBuilder.AdvanceVertex();

	meshBuilder.Position3f( -dist, -dist, 0 );
	meshBuilder.TexCoord2f( 0, tMap.x - (dxMap.x - dyMap.x) * scale, tMap.y - (-dxMap.y + dyMap.y) * scale );
	meshBuilder.Color4ub( 128, 128, 128, 255 );
	meshBuilder.AdvanceVertex();

	// draw underside
	meshBuilder.Position3f( -dist, dist, 0 );
	meshBuilder.TexCoord2f( 0, tMap.x + (-dxMap.x - dyMap.x) * scale, tMap.y + (dxMap.y + dyMap.y) * scale );
	meshBuilder.Color4ub( 128, 128, 128, 128 );
	meshBuilder.AdvanceVertex();

	meshBuilder.Position3f( -dist, -dist, 0 );
	meshBuilder.TexCoord2f( 0, tMap.x - (dxMap.x - dyMap.x) * scale, tMap.y - (-dxMap.y + dyMap.y) * scale );
	meshBuilder.Color4ub( 128, 128, 128, 128 );
	meshBuilder.AdvanceVertex();

	meshBuilder.Position3f( dist, -dist, 0 );
	meshBuilder.TexCoord2f( 0, tMap.x + (dxMap.x + dyMap.x) * scale, tMap.y + (-dxMap.y - dyMap.y) * scale );
	meshBuilder.Color4ub( 128, 128, 128, 128 );
	meshBuilder.AdvanceVertex();

	meshBuilder.Position3f( dist, dist, 0 );
	meshBuilder.TexCoord2f( 0, tMap.x + (dxMap.x - dyMap.x) * scale, tMap.y + (-dxMap.y + dyMap.y) * scale );
	meshBuilder.Color4ub( 128, 128, 128, 128 );
	meshBuilder.AdvanceVertex();

	
	
	meshBuilder.End();
	pMesh->Draw();

	pRenderContext->MatrixMode(MATERIAL_MODEL);
	pRenderContext->PopMatrix();
	pRenderContext->MatrixMode(MATERIAL_VIEW);
	pRenderContext->PopMatrix();
}




void DrawMovementBoxes()
{
	if (!g_viewerSettings.showMovement)
		return;

	CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
	pRenderContext->Bind(g_materialFloor);
	pRenderContext->MatrixMode(MATERIAL_MODEL);
	pRenderContext->PushMatrix();
	pRenderContext->LoadIdentity();
	pRenderContext->MatrixMode(MATERIAL_VIEW);
	pRenderContext->PushMatrix();
	pRenderContext->LoadIdentity();

	pRenderContext->MatrixMode( MATERIAL_VIEW );
	pRenderContext->LoadIdentity( );

	pRenderContext->Rotate( -90,  1, 0, 0 );	    // put Z going up
	pRenderContext->Rotate( -90,  0, 0, 1 );

    pRenderContext->Translate( -g_pStudioModel->m_origin[0],  -g_pStudioModel->m_origin[1],  -g_pStudioModel->m_origin[2] );

	pRenderContext->Rotate( g_pStudioModel->m_angles[1],  0, 0, 1 );
    pRenderContext->Rotate( g_pStudioModel->m_angles[0],  0, 1, 0 );
    pRenderContext->Rotate( g_pStudioModel->m_angles[2],  1, 0, 0 );

	static matrix3x4_t mStart( 1, 0, 0, 0 ,  0, 1, 0, 0 ,  0, 0, 1, 0 );
	matrix3x4_t mTemp;
	static float prevframes[5];

	Vector deltaPos;
	QAngle deltaAngles;

	g_pStudioModel->GetMovement( prevframes, deltaPos, deltaAngles );

	AngleMatrix( deltaAngles, deltaPos, mTemp );
	MatrixInvert( mTemp, mTemp );
	ConcatTransforms( mTemp, mStart, mStart );

	Vector bboxMin, bboxMax;
	g_pStudioModel->ExtractBbox( bboxMin, bboxMax  );

	static float prevCycle = 0.0;

	if (fabs( g_pStudioModel->GetFrame( 0 ) - prevCycle) > 0.5)
	{
		SetIdentityMatrix( mStart );
	}
	prevCycle = g_pStudioModel->GetFrame( 0 );

	// starting position
	{
		float color[] = { 0.7, 1, 0, 0.5 };
		float wirecolor[] = { 1, 1, 0, 1.0 };
		g_pStudioModel->drawTransparentBox( bboxMin, bboxMax, mStart, color, wirecolor );
	}

	// current position
	{
		float color[] = { 1, 0.7, 0, 0.5 };
		float wirecolor[] = { 1, 0, 0, 1.0 };
		SetIdentityMatrix( mTemp );
		g_pStudioModel->drawTransparentBox( bboxMin, bboxMax, mTemp, color, wirecolor );
	}

	pRenderContext->MatrixMode(MATERIAL_MODEL);
	pRenderContext->PopMatrix();
	pRenderContext->MatrixMode(MATERIAL_VIEW);
	pRenderContext->PopMatrix();
}



char const *HLMV_TranslateSoundName( char const *soundname, StudioModel *model )
{
	if ( Q_stristr( soundname, ".wav" ) )
		return PSkipSoundChars( soundname );

	if ( model )
	{
		return PSkipSoundChars( g_pSoundEmitterBase->GetWavFileForSound( soundname, model->GetFileName() ) );
	}

	return PSkipSoundChars( g_pSoundEmitterBase->GetWavFileForSound( soundname, NULL ) );
}


void PlaySound( const char *pSoundName, StudioModel *pStudioModel )
{
	// Play Sound
	if (!g_viewerSettings.playSounds)
		return;
	
	if ( pSoundName == NULL || pSoundName[ 0 ] == '\0' )
		return;

	const char *pSoundFileName = HLMV_TranslateSoundName( pSoundName, pStudioModel );

	char filename[ 256 ];
	sprintf( filename, "sound/%s", pSoundFileName );
	CAudioSource *pAudioSource = g_pSoundSystem->FindOrAddSound( filename );
	if ( pAudioSource == NULL )
		return;

	float volume = VOL_NORM;
	gender_t gender = GENDER_NONE;
	if ( pStudioModel )
	{
		gender = g_pSoundEmitterBase->GetActorGender( pStudioModel->GetFileName() );
	}

	CSoundParameters params;
	if ( !Q_stristr( pSoundName, ".wav" ) &&
		g_pSoundEmitterBase->GetParametersForSound( pSoundName, params, gender ) )
	{
		volume = params.volume;
	}

	g_pSoundSystem->PlaySound( pAudioSource, volume, NULL );
}


// copied from baseentity.cpp
// HACK:  This must match the #define in cl_animevent.h in the client .dll code!!!
#define CL_EVENT_SOUND				5004
#define CL_EVENT_FOOTSTEP_LEFT		6004
#define CL_EVENT_FOOTSTEP_RIGHT		6005
#define CL_EVENT_MFOOTSTEP_LEFT		6006
#define CL_EVENT_MFOOTSTEP_RIGHT	6007

// copied from scriptevent.h
#define SCRIPT_EVENT_SOUND			1004		// Play named wave file (on CHAN_BODY)
#define SCRIPT_EVENT_SOUND_VOICE	1008		// Play named wave file (on CHAN_VOICE)


void PlaySounds( StudioModel *pStudioModel )
{
	if ( pStudioModel == NULL )
		return;

	int iLayer = g_ControlPanel->getFrameSelection();
	float flFrame = pStudioModel->GetFrame( iLayer );
	float flTime = flFrame / 30.0f; // pStudioModel->GetSequenceTime()

	float prevtime = flTime - pStudioModel->GetTimeDelta();
	float currtime = flTime;

	float duration = pStudioModel->GetDuration();

	prevtime = fmod( prevtime, duration );
	currtime = fmod( currtime, duration );

	float prevcycle = prevtime / duration;
	float currcycle = currtime / duration;

	CStudioHdr *pStudioHdr = pStudioModel->GetStudioHdr();
	if ( pStudioHdr == NULL )
		return;

	int seq = pStudioModel->GetSequence();
	mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( seq );

	for ( int i = 0; i < (int)seqdesc.numevents; ++i )
	{
		mstudioevent_t *pEvent = seqdesc.pEvent( i );
#if defined( _DEBUG )
		const char *pEventName = pEvent->pszEventName();
		NOTE_UNUSED( pEventName );
#endif
		if ( pEvent->cycle <= prevcycle || pEvent->cycle > currcycle )
			continue;

		// largely copied from BuildAnimationEventSoundList in baseentity.cpp
		switch ( pEvent->event )
		{
		case 0:
			if ( Q_strcmp( pEvent->pszEventName(), "AE_CL_PLAYSOUND" ) == 0 )
			{
				PlaySound( pEvent->pszOptions(), pStudioModel );
				continue;
			}
			break;

		case CL_EVENT_SOUND: // Old-style client .dll animation event
			// fall-through intentional
		case SCRIPT_EVENT_SOUND:
			// fall-through intentional
		case SCRIPT_EVENT_SOUND_VOICE:
			PlaySound( pEvent->pszOptions(), pStudioModel );
			break;

		case CL_EVENT_FOOTSTEP_LEFT:
		case CL_EVENT_FOOTSTEP_RIGHT:
			{
				char soundname[256];
				char const *options = pEvent->pszOptions();
				if ( !options || !options[0] )
				{
					options = "NPC_CombineS";
				}

				Q_snprintf( soundname, 256, "%s.RunFootstepLeft", options );
				PlaySound( soundname, pStudioModel );
				Q_snprintf( soundname, 256, "%s.RunFootstepRight", options );
				PlaySound( soundname, pStudioModel );
				Q_snprintf( soundname, 256, "%s.FootstepLeft", options );
				PlaySound( soundname, pStudioModel );
				Q_snprintf( soundname, 256, "%s.FootstepRight", options );
				PlaySound( soundname, pStudioModel );
			}
			break;
/*
		case AE_CL_PLAYSOUND:
			if ( !( pEvent->type & AE_TYPE_CLIENT ) )
				break;

			if ( pEvent->options[0] )
			{
				PlaySound( pEvent->options, pStudioModel );
			}
			else
			{
				Warning( "-- Error --:  empty soundname, .qc error on AE_CL_PLAYSOUND in model %s, sequence %s, animevent # %i\n", 
					pStudioHdr->name(), seqdesc.pszLabel(), i + 1 );
			}
			break;
*/
		default:
			break;
		}
	}
}


void
MatSysWindow::draw ()
{
	MDLCACHE_CRITICAL_SECTION_( g_pMDLCache );

	if ( g_bInError || !g_pStudioModel->GetStudioRender() )
		return;

	static bool bInDraw = false;
	if (bInDraw)
		return;

	bInDraw = true;

	UpdateSounds(); // need to call this multiple times per frame to avoid audio stuttering

	CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
	g_pMaterialSystem->BeginFrame( 0 );
	g_pStudioModel->GetStudioRender()->BeginFrame();

	pRenderContext->ClearColor3ub(g_viewerSettings.bgColor[0] * 255, g_viewerSettings.bgColor[1] * 255, g_viewerSettings.bgColor[2] * 255);
	// pRenderContext->ClearColor3ub(0, 0, 0 );
	pRenderContext->ClearBuffers(true, true);

	pRenderContext->Viewport( 0, 0, w(), h() );

	pRenderContext->MatrixMode( MATERIAL_PROJECTION );
	pRenderContext->LoadIdentity( );
	pRenderContext->PerspectiveX(g_viewerSettings.fov, (float)w() / (float)h(), 1.0f, 20000.0f);
	
	DrawBackground();
	DrawGroundPlane();
	DrawMovementBoxes();
	DrawHelpers();

	pRenderContext->MatrixMode( MATERIAL_VIEW );
	pRenderContext->LoadIdentity( );
	// FIXME: why is this needed?  Doesn't SetView() override this?
	pRenderContext->Rotate( -90,  1, 0, 0 );	    // put Z going up
	pRenderContext->Rotate( -90,  0, 0, 1 );

	g_pStudioModel->ClearLookTargets();
	g_pStudioModel->AddLookTarget( Vector( 0, 0, 0 ), g_pStudioModel->GetSolveHeadTurn() ? 1.0f : 0.0f );
	int polycount = g_pStudioModel->DrawModel( false, PASS_MODELONLY );

	g_pStudioModel->GetStudioRender()->EndFrame();

	UpdateSounds(); // need to call this multiple times per frame to avoid audio stuttering

	g_ControlPanel->setModelInfo();

	int lod;
	float metric;
	metric = g_pStudioModel->GetLodMetric();
	lod = g_pStudioModel->GetLodUsed();
	g_ControlPanel->setLOD( lod, true, false );
	g_ControlPanel->setLODMetric( metric );

	g_ControlPanel->setPolycount( polycount );
	g_ControlPanel->setTransparent( g_pStudioModel->m_bIsTransparent );

	g_ControlPanel->updatePoseParameters( );
	
	// draw what ever else is loaded
	int i;
	for (i = 0; i < HLMV_MAX_MERGED_MODELS; i++)
	{
		if (g_pStudioExtraModel[i] != NULL)
		{
			g_pStudioModel->GetStudioRender()->BeginFrame();
			g_pStudioExtraModel[i]->DrawModel( true );
			g_pStudioModel->GetStudioRender()->EndFrame();
		}
	}

	g_pStudioModel->GetStudioRender()->BeginFrame();
	g_pStudioModel->DrawModel( false, PASS_EXTRASONLY );
	g_pStudioModel->GetStudioRender()->EndFrame();

	g_pStudioModel->IncrementFramecounter();

	PlaySounds( g_pStudioModel );
	UpdateSounds(); // need to call this multiple times per frame to avoid audio stuttering

    g_pMaterialSystem->SwapBuffers();
	
	g_pMaterialSystem->EndFrame();

	bInDraw = false;
}



/*
int
MatSysWindow::loadTexture (const char *filename, int name)
{
	if (!filename || !strlen (filename))
	{
		if (d_textureNames[name])
		{
			glDeleteTextures (1, (const GLuint *) &d_textureNames[name]);
			d_textureNames[name] = 0;

			if (name == 0)
				strcpy (g_viewerSettings.backgroundTexFile, "");
			else
				strcpy (g_viewerSettings.groundTexFile, "");
		}

		return 0;
	}

	mxImage *image = 0;

	char ext[16];
	strcpy (ext, mx_getextension (filename));

	if (!mx_strcasecmp (ext, ".tga"))
		image = mxTgaRead (filename);
	else if (!mx_strcasecmp (ext, ".pcx"))
		image = mxPcxRead (filename);
	else if (!mx_strcasecmp (ext, ".bmp"))
		image = mxBmpRead (filename);

	if (image)
	{
		if (name == 0)
			strcpy (g_viewerSettings.backgroundTexFile, filename);
		else
			strcpy (g_viewerSettings.groundTexFile, filename);

		d_textureNames[name] = name + 1;

		if (image->bpp == 8)
		{
			mstudiotexture_t texture;
			texture.width = image->width;
			texture.height = image->height;

			g_pStudioModel->UploadTexture (&texture, (byte *) image->data, (byte *) image->palette, name + 1);
		}
		else
		{
			glBindTexture (GL_TEXTURE_2D, d_textureNames[name]);
			glTexImage2D (GL_TEXTURE_2D, 0, 3, image->width, image->height, 0, GL_RGB, GL_UNSIGNED_BYTE, image->data);
			glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
			glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		}

		delete image;

		return name + 1;
	}

	return 0;
}
*/

void MatSysWindow::dumpViewport (const char *filename)
{
	dumpViewportWithLabel( filename, "" );
}


Color MatSysWindow::getViewportPixelColor( int x, int y )
{
	redraw ();

	HDC hDC = GetDC ((HWND) getHandle ());
	COLORREF color = GetPixel(hDC, x, y);
	ReleaseDC ((HWND) getHandle (), hDC);
	
	Color temp;

	temp.SetColor( GetRValue( color ), GetGValue( color ), GetBValue( color ), 255 );

	return temp;
}

// Save HDC as a bitmap:
void MatSysWindow::dumpViewportWithLabel(const char *filename, const char *label)
{

	redraw ();
	//int w = w2 ();
	//int h = h2 ();

	RECT client;
	HDC hDC = GetDC ((HWND) getHandle ());
	RECT rcClient;
	bool bOk = false;
	HBITMAP hImage = NULL;

	GetClientRect ((HWND) getHandle (), &rcClient);
	client = rcClient;

	if ((hImage = CreateCompatibleBitmap (hDC, rcClient.right, rcClient.bottom)) != NULL)
	{
		HDC hMemDC;
		HBITMAP hDCBmp;

		if ((hMemDC = CreateCompatibleDC (hDC)) != NULL)
		{
			hDCBmp = (HBITMAP) SelectObject (hMemDC, hImage);
			BitBlt (hMemDC, 0, 0, rcClient.right, rcClient.bottom, hDC, 0, 0, SRCCOPY);

			if ( strlen(label) > 0 )
			{
				HFONT font = CreateFont( 24, 0, 0, 0, 700, false, false, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, "Arial" );
				SetBkColor(hMemDC,  RGB(60,60,60) );
				SetTextColor(hMemDC, RGB(255,255,255) );
				HFONT hFontOld = (HFONT)SelectObject( hMemDC, font );
				DrawText( hMemDC, label, strlen(label), &rcClient, DT_TOP|DT_CENTER);
				SelectObject( hMemDC, hFontOld );
			}

			SelectObject (hMemDC, hDCBmp);
			DeleteDC (hMemDC);
			bOk = true;
		}
	}

	ReleaseDC ((HWND) getHandle (), hDC);

	if (! bOk)
	{
		if (hImage)
		{
			DeleteObject (hImage);
			hImage = NULL;
		}
	}

	if ( !hImage )
	{
		mxMessageBox (this, "Screenshot failure: Couldn't capture the model window.", g_appTitle, MX_MB_OK | MX_MB_ERROR);
		return;
	}

	if ( hImage != NULL) {
		UINT uiBytesPerRow = 3 * client.right; // RGB takes 24 bits
		UINT uiRemainderForPadding;

		if ((uiRemainderForPadding = uiBytesPerRow % sizeof (DWORD)) > 0)
		{
			uiBytesPerRow += (sizeof (DWORD) - uiRemainderForPadding);
		}

		UINT uiBytesPerAllRows = uiBytesPerRow * client.bottom;
		PBYTE pDataBits;

		if ((pDataBits = new BYTE [uiBytesPerAllRows]) != NULL)
		{
			BITMAPINFOHEADER bmi = {0};
			BITMAPFILEHEADER bmf = {0};
			HDC hDC = GetDC ((HWND) getHandle ());

			// Prepare to get the data out of HBITMAP:
			bmi.biSize = sizeof (bmi);
			bmi.biPlanes = 1;
			bmi.biBitCount = 24;
			bmi.biHeight = client.bottom;
			bmi.biWidth = client.right;

			GetDIBits (hDC, hImage, 0, client.bottom, pDataBits, (BITMAPINFO*) &bmi, DIB_RGB_COLORS);

			ReleaseDC ((HWND) getHandle (), hDC);

			// Fill the file header:
			bmf.bfOffBits = sizeof (bmf) + sizeof (bmi);
			bmf.bfSize = bmf.bfOffBits + uiBytesPerAllRows;
			bmf.bfType = 0x4D42;

			FILE* pFile;
			if ((pFile = fopen (filename, "wb")) != NULL)
			{
				fwrite (&bmf, sizeof (bmf), 1, pFile);
				fwrite (&bmi, sizeof (bmi), 1, pFile);
				fwrite (pDataBits, sizeof (BYTE), uiBytesPerAllRows, pFile);
				fclose (pFile);
			}
			delete [] pDataBits;
		}
		DeleteObject (hImage);
	}

}