//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================//

#include <stdafx.h>
#include "hammer.h"
#include "IEditorTexture.h"
#include "FaceEditSheet.h"
#include "MapFace.h"
#include "MapWorld.h"
#include "MainFrm.h"
#include "History.h"
#include "GlobalFunctions.h"
#include "mathlib/vmatrix.h"
#include "MapSolid.h"
#include "TextureBrowser.h"
#include "TextureSystem.h"
#include "MapView3D.h"
#include "ReplaceTexDlg.h"
#include "WADTypes.h"
#include "FaceEdit_MaterialPage.h"
#include "Camera.h"
#include "MapDoc.h"
#include "MapDisp.h"
#include "ToolManager.h"
#include "Selection.h"

// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>


//=============================================================================

IMPLEMENT_DYNAMIC( CFaceEditMaterialPage, CPropertyPage )

BEGIN_MESSAGE_MAP( CFaceEditMaterialPage, CPropertyPage )
	//{{AFX_MSG_MAP( CFaceEditMaterialPage )
	ON_BN_CLICKED( ID_FACEEDIT_APPLY, OnButtonApply )
	ON_COMMAND_EX( IDC_ALIGN_WORLD, OnAlign )
	ON_COMMAND_EX( IDC_ALIGN_FACE, OnAlign )
	ON_BN_CLICKED( IDC_HIDEMASK, OnHideMask )
	ON_COMMAND_EX( IDC_JUSTIFY_TOP, OnJustify )
	ON_COMMAND_EX( IDC_JUSTIFY_BOTTOM, OnJustify )
	ON_COMMAND_EX( IDC_JUSTIFY_LEFT, OnJustify )
	ON_COMMAND_EX( IDC_JUSTIFY_CENTER, OnJustify )
	ON_COMMAND_EX( IDC_JUSTIFY_RIGHT, OnJustify )
	ON_COMMAND_EX( IDC_JUSTIFY_FITTOFACE, OnJustify )
	ON_BN_CLICKED( IDC_MODE, OnMode )
	ON_WM_VSCROLL()
	ON_NOTIFY( UDN_DELTAPOS, IDC_SPINSCALEX, OnDeltaPosFloatSpin )
	ON_NOTIFY( UDN_DELTAPOS, IDC_SPINSCALEY, OnDeltaPosFloatSpin )
	ON_WM_SIZE()
	ON_CBN_SELCHANGE( IDC_TEXTURES, OnSelChangeTexture )
	ON_BN_CLICKED( IDC_Q2_LIGHT, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_SLICK, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_SKY, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_WARP, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_TRANS33, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_TRANS66, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_FLOWING, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_NODRAW, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_SOLID, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_WINDOW, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_AUX, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_LAVA, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_SLIME, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_WATER, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_MIST, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_CURRENT90, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_CURRENT180, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_CURRENT270, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_CURRENTUP, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_CURRENTDN, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_ORIGIN, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_MONSTER, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_CORPSE, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_DETAIL, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_TRANSLUCENT, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_LADDER, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_PLAYERCLIP, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_MONSTERCLIP, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_CURRENT0, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_HINT, OnCheckUnCheck )
	ON_BN_CLICKED( IDC_Q2_SPLITTER, OnCheckUnCheck )
	ON_COMMAND( IDC_TREAT_AS_ONE, OnTreatAsOne )
	ON_BN_CLICKED( IDC_REPLACE, OnReplace )
	ON_COMMAND_EX_RANGE( CFaceEditSheet::id_SwitchModeStart, CFaceEditSheet::id_SwitchModeEnd, OnSwitchMode )
	ON_CBN_SELCHANGE( IDC_TEXTUREGROUPS, OnChangeTextureGroup )
	ON_BN_CLICKED( IDC_BROWSE, OnBrowse )
	ON_BN_CLICKED( ID_BUTTON_SMOOTHING_GROUPS, OnButtonSmoothingGroups )
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

//=============================================================================

#define	CONTENTS_AREAPORTAL		0x8000
#define	CONTENTS_PLAYERCLIP		0x10000
#define	CONTENTS_MONSTERCLIP	0x20000

// I don't think we need these currents.  We'll stick to triggers for this
#define	CONTENTS_CURRENT_0		0x40000
#define	CONTENTS_CURRENT_90		0x80000
#define	CONTENTS_CURRENT_180	0x100000
#define	CONTENTS_CURRENT_270	0x200000
#define	CONTENTS_CURRENT_UP		0x400000
#define	CONTENTS_CURRENT_DOWN	0x800000
#define	CONTENTS_ORIGIN			0x1000000	// removed before bsping an entity
#define	CONTENTS_MONSTER		0x2000000	// should never be on a brush, only in game
#define	CONTENTS_DEBRIS			0x4000000
#define	CONTENTS_DETAIL			0x8000000	// brushes to be added after vis leafs
#define	CONTENTS_TRANSLUCENT	0x10000000	// auto set if any surface has trans
#define	CONTENTS_LADDER			0x20000000

//=============================================================================

const int NOT_INIT = -99999;

unsigned int CFaceEditMaterialPage::m_FaceContents = 0;
unsigned int CFaceEditMaterialPage::m_FaceSurface = 0;

//=============================================================================

//-----------------------------------------------------------------------------
// This table defines the mapping of checkbox controls to flags which are set
// in certain face attributes values.
//-----------------------------------------------------------------------------
CFaceEditMaterialPage::FaceAttributeInfo_t FaceAttributes[] =
{
	//
	// Contents.
	//
	{ IDC_CONTENTS_AREAPORTAL,	&CFaceEditMaterialPage::m_FaceContents,	CONTENTS_AREAPORTAL },
	{ IDC_CONTENTS_PLAYERCLIP,	&CFaceEditMaterialPage::m_FaceContents,	CONTENTS_PLAYERCLIP },
	{ IDC_CONTENTS_MONSTERCLIP,	&CFaceEditMaterialPage::m_FaceContents,	CONTENTS_MONSTERCLIP },
	{ IDC_CONTENTS_ORIGIN,		&CFaceEditMaterialPage::m_FaceContents,	CONTENTS_ORIGIN },
	{ IDC_CONTENTS_DETAIL,		&CFaceEditMaterialPage::m_FaceContents,	CONTENTS_DETAIL },
	{ IDC_CONTENTS_TRANSLUCENT,	&CFaceEditMaterialPage::m_FaceContents,	CONTENTS_TRANSLUCENT },
	{ IDC_CONTENTS_LADDER,		&CFaceEditMaterialPage::m_FaceContents,	CONTENTS_LADDER },

	//
	// Surface attributes.
	//
	{ IDC_SURF_NODRAW,			&CFaceEditMaterialPage::m_FaceSurface,		SURF_NODRAW },
	{ IDC_SURF_HINT,			&CFaceEditMaterialPage::m_FaceSurface,		SURF_HINT },
	{ IDC_SURF_SKIP,			&CFaceEditMaterialPage::m_FaceSurface,		SURF_SKIP }
};


//=============================================================================

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CFaceEditMaterialPage::CFaceEditMaterialPage() : CPropertyPage( IDD )
{
	m_bHideMask = FALSE;
	m_bInitialized = FALSE;
	m_bIgnoreResize = FALSE;
	m_bTreatAsOneFace = FALSE;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CFaceEditMaterialPage::~CFaceEditMaterialPage()
{
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
BOOL CFaceEditMaterialPage::PreTranslateMessage( MSG *pMsg )
{
	HACCEL hAccel = GetMainWnd()->GetAccelTable();
	if( !(hAccel && ::TranslateAccelerator( GetMainWnd()->m_hWnd, hAccel, pMsg ) ) )
	{
		return CPropertyPage::PreTranslateMessage( pMsg );
	}
	else
	{
		return TRUE;
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::Init( void )
{
	//
	// Connect dialog control objects to their control IDs.
	//
	m_shiftX.SubclassDlgItem( IDC_SHIFTX, this );
	m_shiftY.SubclassDlgItem( IDC_SHIFTY, this );
	m_scaleX.SubclassDlgItem( IDC_SCALEX, this );
	m_scaleY.SubclassDlgItem( IDC_SCALEY, this );
	m_rotate.SubclassDlgItem( IDC_ROTATE, this );
	m_cHideMask.SubclassDlgItem( IDC_HIDEMASK, this );
	m_cExpand.SubclassDlgItem( IDC_EXPAND, this );
	m_cLightmapScale.SubclassDlgItem( IDC_LIGHTMAP_SCALE, this );

	//
	// Set spin ranges.
	//
	CWnd *pWnd = GetDlgItem(IDC_SPINSHIFTX);
	::PostMessage(pWnd->m_hWnd, UDM_SETRANGE, 0, MAKELONG(MAX_TEXTUREWIDTH, -MAX_TEXTUREWIDTH));

	pWnd = GetDlgItem(IDC_SPINSHIFTY);
	::PostMessage(pWnd->m_hWnd, UDM_SETRANGE, 0, MAKELONG(MAX_TEXTUREHEIGHT, -MAX_TEXTUREHEIGHT));

	pWnd = GetDlgItem(IDC_SPINROTATE);
	::PostMessage(pWnd->m_hWnd, UDM_SETRANGE, 0, MAKELONG(359, -359));

	pWnd = GetDlgItem(IDC_SPINSCALEX);
	::PostMessage(pWnd->m_hWnd, UDM_SETRANGE, 0, MAKELONG(UD_MAXVAL, UD_MINVAL));

	pWnd = GetDlgItem(IDC_SPINSCALEY);
	::PostMessage(pWnd->m_hWnd, UDM_SETRANGE, 0, MAKELONG(UD_MAXVAL, UD_MINVAL));

	pWnd = GetDlgItem(IDC_SPIN_LIGHTMAP_SCALE);
	::PostMessage(pWnd->m_hWnd, UDM_SETRANGE, 0, MAKELONG(UD_MAXVAL, 1));

	// set the initial switch mode
	OnSwitchMode( CFaceEditSheet::ModeLiftSelect );

	//
	// set up controls
	//
	m_TextureGroupList.SubclassDlgItem( IDC_TEXTUREGROUPS, this );
	m_TextureList.SubclassDlgItem( IDC_TEXTURES, this );
	m_TexturePic.SubclassDlgItem( IDC_TEXTUREPIC, this );

	m_pCurTex = NULL;

	//
	// initially update the texture controls
	//
	NotifyGraphicsChanged();
	UpdateTexture();

	// initialized!
	m_bInitialized = TRUE;
}


//-----------------------------------------------------------------------------
// NOTE: clean this up and make a global function!!!
// Purpose: 
// Input  : fValue - 
//			*pSpin - 
//			bMantissa - 
// Output : static void
//-----------------------------------------------------------------------------
void FloatToSpin(float fValue, CSpinButtonCtrl *pSpin, BOOL bMantissa)
{
	char szNew[128];

	CWnd *pEdit = pSpin->GetBuddy();

	if (fValue == NOT_INIT)
	{
		szNew[0] = 0;
	}
	else
	{
		if(bMantissa)
			sprintf(szNew, "%.2f", fValue);
		else
			sprintf(szNew, "%.0f", fValue);
	}

	pSpin->SetPos(atoi(szNew));

	char szCurrent[128];
	pEdit->GetWindowText(szCurrent, 128);
	if (strcmp(szNew, szCurrent))
	{
		pEdit->SetWindowText(szNew);
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : nValue - 
//			pSpin - 
// Output : static void
//-----------------------------------------------------------------------------
void IntegerToSpin(int nValue, CSpinButtonCtrl *pSpin)
{
	char szNew[128];

	CWnd *pEdit = pSpin->GetBuddy();

	if (nValue == NOT_INIT)
	{
		szNew[0] = 0;
	}
	else
	{
		sprintf(szNew, "%d", abs(nValue));
	}

	pSpin->SetPos(atoi(szNew));

	char szCurrent[128];
	pEdit->GetWindowText(szCurrent, 128);
	if (strcmp(szNew, szCurrent) != 0)
	{
		pEdit->SetWindowText(szNew);
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : fValue - 
//			*pWnd - 
// Output : static void
//-----------------------------------------------------------------------------
void FloatToWnd(float fValue, CWnd *pWnd)
{
	char szCurrent[128];
	char szNew[128];

	if(fValue == NOT_INIT)
	{
		szNew[0] = 0;
	}
	else
	{
		sprintf(szNew, "%g", fValue);
	}

	pWnd->GetWindowText(szCurrent, 128);
	if(strcmp(szNew, szCurrent))
		pWnd->SetWindowText(szNew);
}


//-----------------------------------------------------------------------------
// Purpose: Fetches a string value from a window and places it in a float. The
//			float is only modified if there is text in the window.
// Input  : pWnd - Window from which to get text.
//			fValue - Float in which to place value.
//-----------------------------------------------------------------------------
void TransferToFloat( CWnd *pWnd, float &fValue )
{
	CString str;
	pWnd->GetWindowText( str );

	if( !str.IsEmpty() )
	{
		fValue = ( float )atof( str );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Fetches a string value from a window and places it in an integer. The
//			integer is only modified if there is text in the window.
// Input  : pWnd - Window from which to get text.
//			nValue - Float in which to place value.
//-----------------------------------------------------------------------------
void TransferToInteger( CWnd *pWnd, int &nValue )
{
	CString str;
	pWnd->GetWindowText( str );

	if( !str.IsEmpty() )
	{
		nValue = abs( atoi( str ) );
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::ClickFace( CMapSolid *pSolid, int faceIndex, int cmd, int clickMode )
{
	// get the face
	CMapFace	*pFace = pSolid->GetFace( faceIndex );
	bool		bIsEditable = pSolid->IsEditable();

	//
	// are updates enabled?
	//
	CFaceEditSheet *pSheet = ( CFaceEditSheet* )GetParent();
	bool bEnableUpdate = pSheet->HasUpdateEnabled();


	SetReadOnly( !bIsEditable );

	//
	// find the behavior of the page based on the "click mode"
	//
	switch( clickMode )
	{
		case CFaceEditSheet::ModeAlignToView:
		{
			if ( bIsEditable )
			{
				AlignToView( pFace );
			}
			break;
		}

		case CFaceEditSheet::ModeLift:
		{
			if( bEnableUpdate )
			{
				UpdateDialogData( pFace );			
			}
			break;
		}

		case CFaceEditSheet::ModeLiftSelect:
		{
			if ( bEnableUpdate )
			{
				UpdateDialogData();			
			}
			break;
		}

		case CFaceEditSheet::ModeApplyLightmapScale:
		{
			// Apply the lightmap scale only. Leave everything else alone.
			if ( bIsEditable )
			{
				Apply(pFace, FACE_APPLY_LIGHTMAP_SCALE);
			}
			break;
		}

		case CFaceEditSheet::ModeApply:
		case CFaceEditSheet::ModeApplyAll:
		{
			if ( bIsEditable )
			{
				int flags = 0;

				if (cmd & CFaceEditSheet::cfEdgeAlign)
				{
					// Adust the mapping to align with a reference face.
					flags |= FACE_APPLY_ALIGN_EDGE;
				}

				if (clickMode == CFaceEditSheet::ModeApplyAll)
				{
					// Apply the material, mapping, lightmap scale, etc.
					flags |= FACE_APPLY_ALL;
				}
				else
				{
					// Apply the material only. Leave everything else alone.
					flags |= FACE_APPLY_MATERIAL;
				}

				Apply(pFace, flags);
			}
			break;
		}
	}
}


//-----------------------------------------------------------------------------
// Maps the texture onto the face using the 3D view's up and right vectors.
// This can be useful for mapping curvy things like hills because if you don't
// move the 3D view, the texture will line up on any polies you map this way.
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::AlignToView( CMapFace *pFace )
{
	CView		*pActiveView;
	CMapView3D	*pView3D;
	CFrameWnd	*pFrame;
	Vector		vView;
	
	if((pFrame = GetMainWnd()->GetActiveFrame()) != NULL)
	{
		if((pActiveView = pFrame->GetActiveView()) != NULL)
		{
			if(pActiveView->IsKindOf(RUNTIME_CLASS(CMapView3D)))
			{
				pView3D = dynamic_cast<CMapView3D*>(pActiveView);
				if(pView3D)
				{
					const CCamera *pCamera = pView3D->GetCamera();
					if(pCamera)
					{
						Vector right, up;
						pCamera->GetViewRight(right);
						pCamera->GetViewUp(up);
						pCamera->GetViewPoint(vView);

						pFace->texture.UAxis.AsVector3D() = right;
						pFace->texture.VAxis.AsVector3D() = up;
						pFace->texture.UAxis[3] = DotProduct( right, vView);
						pFace->texture.VAxis[3] = DotProduct( up, vView);
						pFace->NormalizeTextureShifts();

						pFace->texture.rotate = 0.0f;
						pFace->texture.scale[0] = g_pGameConfig->GetDefaultTextureScale();
						pFace->texture.scale[1] = g_pGameConfig->GetDefaultTextureScale();

						pFace->CalcTextureCoords();
					}
				}
			}	
		}
	}
} 


//-----------------------------------------------------------------------------
// Copies the texture coordinate system from pFrom into pTo. Then it rotates 
// the texture around the edge until it's as close to pTo's normal as possible.
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::CopyTCoordSystem( const CMapFace *pFrom, CMapFace *pTo )
{
	Vector		axis[2], vEdge, vEdgePt, vOrigin;
	Vector		vFromPt, vNextFromPt;
	Vector		vToPt, vPrevToPt;
	Vector		vTestTextureNormal, vTextureNormal;
	VMatrix		mEdgeRotation, mOriginRotation, mTranslation;
	float		fAngle, fDot;
	bool		bRotate;
	float		fShift[2];
	Vector		vProjTexNormal;
	Vector		vProjPolyNormal;

	// The edge vector lies on both planes.
	vEdge = pFrom->plane.normal.Cross(pTo->plane.normal);
	VectorNormalize( vEdge );

	// To find a point on the plane, we make a plane from the edge vector and find the intersection
	// between the three planes (without the third plane, there are an infinite number of solutions).
	if( PlaneIntersection( VPlane(pFrom->plane.normal, pFrom->plane.dist),
		                   VPlane(pTo->plane.normal, pTo->plane.dist),
		                   VPlane(vEdge, 0.0f), vEdgePt ) )
	{
		bRotate = true;
	}
	else
	{
		// Ok, in this case, the planes are parallel so we don't need to rotate around the edge anyway!
		bRotate = false;
	}

	// Copy the texture coordinate system.
	axis[0] = pFrom->texture.UAxis.AsVector3D();
	axis[1] = pFrom->texture.VAxis.AsVector3D();
	fShift[0] = pFrom->texture.UAxis[3];
	fShift[1] = pFrom->texture.VAxis[3];
	vOrigin = axis[0]*fShift[0]*pFrom->texture.scale[0] + axis[1]*fShift[1]*pFrom->texture.scale[1];
	
	vTextureNormal = axis[0].Cross(axis[1]);
	VectorNormalize(vTextureNormal);
	if(bRotate)
	{
		// Project texture normal and poly normal into the plane of rotation
		// to get the angle between them.
		vProjTexNormal = vTextureNormal - vEdge * vEdge.Dot(vTextureNormal);
		vProjPolyNormal = pTo->plane.normal - vEdge * vEdge.Dot(pTo->plane.normal);

		VectorNormalize( vProjTexNormal );
		VectorNormalize( vProjPolyNormal );

		fDot = vProjTexNormal.Dot(vProjPolyNormal);
		fAngle = (float)(acos(fDot) * (180.0f / M_PI));
		if(fDot < 0.0f)
			fAngle = 180.0f - fAngle;

		// Ok, rotate them for the final values.
		mEdgeRotation = SetupMatrixAxisRot(vEdge, fAngle);
		axis[0] = mEdgeRotation.ApplyRotation(axis[0]);
		axis[1] = mEdgeRotation.ApplyRotation(axis[1]);

		// Origin needs translation and rotation to rotate around the edge.
		mTranslation = SetupMatrixTranslation(vEdgePt);
		mOriginRotation = ~mTranslation * mEdgeRotation * mTranslation;
		vOrigin = mOriginRotation * vOrigin;
	}

	pTo->texture.UAxis.AsVector3D() = axis[0];
	pTo->texture.VAxis.AsVector3D() = axis[1];

	pTo->texture.UAxis[3] = axis[0].Dot(vOrigin) / pFrom->texture.scale[0];
	pTo->texture.VAxis[3] = axis[1].Dot(vOrigin) / pFrom->texture.scale[1];
	pTo->NormalizeTextureShifts();

	pTo->texture.scale[0] = pFrom->texture.scale[0];
	pTo->texture.scale[1] = pFrom->texture.scale[1];

	// rotate is only for UI purposes, it doesn't actually do anything.
	pTo->texture.rotate = 0.0f;

	pTo->CalcTextureCoords();
}


//-----------------------------------------------------------------------------
// Purpose: Applies dialog data to the list of selected faces.
// Input  : *pOnlyFace - 
//			bAll - 
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::Apply( CMapFace *pOnlyFace, int flags )
{
	int			i;
	CString		str;
	float		fshiftX = NOT_INIT;
	float		fshiftY = NOT_INIT;
	float		fscaleX = NOT_INIT;
	float		fscaleY = NOT_INIT;
	float		frotate = NOT_INIT;
	int			material = NOT_INIT;
	int			nLightmapScale = NOT_INIT;
	IEditorTexture	*pTex = m_TexturePic.GetTexture();
	CMapDoc		*pMapDoc = CMapDoc::GetActiveMapDoc();

	//
	// Get numeric data.
	//
	if (flags & FACE_APPLY_MAPPING)
	{
		TransferToFloat( &m_shiftX, fshiftX );
		TransferToFloat( &m_shiftY, fshiftY );
		TransferToFloat( &m_scaleX, fscaleX );
		TransferToFloat( &m_scaleY, fscaleY );
		TransferToFloat( &m_rotate, frotate );
	}

	if (flags & FACE_APPLY_LIGHTMAP_SCALE)
	{
		TransferToInteger( &m_cLightmapScale, nLightmapScale );
	}

	if ( !pOnlyFace )
	{
		GetHistory()->MarkUndoPosition( NULL, "Apply Face Attributes" );

		// make sure we apply everything in this case.
		flags |= FACE_APPLY_ALL;

		// Keep the solids that we are about to change.
		// In the pOnlyFace case we do the Keep before calling ClickFace. Why?
		CUtlVector<CMapSolid *> kept;
		CFaceEditSheet *pSheet = ( CFaceEditSheet* )GetParent();
		for( i = 0; i < pSheet->GetFaceListCount(); i++ )
		{
			CMapSolid *pSolid = pSheet->GetFaceListDataSolid( i );
			if ( kept.Find( pSolid ) == -1 )
			{
				GetHistory()->Keep( pSolid );
				kept.AddToTail( pSolid );
			}
		}
	}
		
	//
	// Run thru stored faces & apply.
	//
	CFaceEditSheet *pSheet = ( CFaceEditSheet* )GetParent();
	int faceCount = pSheet->GetFaceListCount();
	for( i = 0; i < faceCount || pOnlyFace; i++ )
	{
		CMapFace *pFace;
		if( pOnlyFace )
		{
			pFace = pOnlyFace;
		}
		else
		{
			pFace = pSheet->GetFaceListDataFace( i );
		}

		//
		// Get values for texture shift, scale, rotate, and material.
		//
		if ((flags & FACE_APPLY_MAPPING) && (!(flags & FACE_APPLY_ALIGN_EDGE)))
		{
			if ( fshiftX != NOT_INIT )
			{
				pFace->texture.UAxis[3] = fshiftX;
			}

			if ( fshiftY != NOT_INIT )
			{
				pFace->texture.VAxis[3] = fshiftY;
			}

			if ( fscaleX != NOT_INIT )
			{
				pFace->texture.scale[0] = fscaleX;
			}
			
			if ( fscaleY != NOT_INIT )
			{
				pFace->texture.scale[1] = fscaleY;
			}

			if ( frotate != NOT_INIT )
			{
				pFace->RotateTextureAxes( frotate - pFace->texture.rotate );
				pFace->texture.rotate = frotate;
			}
		}

		if (flags & FACE_APPLY_CONTENTS_DATA)
		{
			if ( material != NOT_INIT )
			{
				pFace->texture.material = material;
			}
		}

		if (flags & FACE_APPLY_LIGHTMAP_SCALE)
		{
			if (nLightmapScale != NOT_INIT)
			{
				pFace->texture.nLightmapScale = max( nLightmapScale, 1 );
			}
		}

		//
		// Update the texture and recalculate texture coordinates.
		//
		if ((flags & FACE_APPLY_MATERIAL) && (pTex != NULL))
		{
			char szCurrentTexName[MAX_PATH];
			char szNewTexName[MAX_PATH];

			pFace->GetTextureName( szCurrentTexName );
			pTex->GetShortName( szNewTexName );

			if( stricmp( szCurrentTexName, szNewTexName ) != 0 )
			{
				pFace->SetTexture( szNewTexName );

				CMapClass	*pParent = dynamic_cast< CMapClass * >( pFace->GetParent() );
				if ( pParent )
				{
					pMapDoc->RemoveFromAutoVisGroups( pParent );
					pMapDoc->AddToAutoVisGroup( pParent );
				}
			}
		}

		//
		// Copy texture coordinate system.
		//
		if ((flags & FACE_APPLY_ALIGN_EDGE) && (faceCount >= 1))
		{
			CopyTCoordSystem( pSheet->GetFaceListDataFace( faceCount - 1 ), pFace );
		}

		//
		// Recalculate texture coordinates.
		//
		pFace->CalcTextureCoords();

		//
		// Update the face flags.
		//
		if (flags & FACE_APPLY_CONTENTS_DATA)
		{
			//
			// Copy the bits from this face into our variables.
			//
			m_FaceContents = pFace->texture.q2contents;
			m_FaceSurface = pFace->texture.q2surface;

			//
			// Update our variables based on the state of the checkboxes.
			//
			for( int nItem = 0; nItem < sizeof( FaceAttributes ) / sizeof( FaceAttributes[0] ); nItem++ )
			{
				CButton *pButton = ( CButton* )GetDlgItem( FaceAttributes[nItem].uControlID );
				if( pButton != NULL )
				{
					int nSet = pButton->GetCheck();

					if (nSet == 0)
					{
						*FaceAttributes[nItem].puAttribute &= ~FaceAttributes[nItem].uFlag;
					}
					else if (nSet == 1)
					{
						*FaceAttributes[nItem].puAttribute |= FaceAttributes[nItem].uFlag;
					}
				}	
			}

			//
			// Copy our variables back into this face.
			//
			pFace->texture.q2contents = m_FaceContents;
			pFace->texture.q2surface = m_FaceSurface;
		}

		if( pOnlyFace )
		{
			break;
		}
	}

	pMapDoc->SetModifiedFlag();
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pOnlyFace - 
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::UpdateDialogData( CMapFace *pOnlyFace )
{
	BOOL	bFirst;
	int		nFaceAlignCount;
	int		nWorldAlignCount;
	float	fshiftX = NOT_INIT;
	float	fshiftY = NOT_INIT;
	float	fscaleX = NOT_INIT;
	float	fscaleY = NOT_INIT;
	float	frotate = NOT_INIT;
	//float fsmooth = NOT_INIT;
	int		material = NOT_INIT;
	int		nLightmapScale = NOT_INIT;
	CString strTexture;

	bFirst = TRUE;
	nFaceAlignCount = 0;
	nWorldAlignCount = 0;

	CFaceEditSheet *pSheet = ( CFaceEditSheet* )GetParent();
	int faceCount = pSheet->GetFaceListCount();

	for( int i = 0; i < faceCount || pOnlyFace; i++ )
	{
		CMapFace *pFace;

		if( pOnlyFace )
		{
			pFace = pOnlyFace;
		}
		else
		{
			pFace = pSheet->GetFaceListDataFace( i );
		}
		
		TEXTURE &t = pFace->texture;

		//
		// Gather statistics about the texture alignment of all the selected faces.
		// This is used later to set the state of the alignment checkboxes.
		//
		int nAlignment = pFace->GetTextureAlignment();
		if (nAlignment & TEXTURE_ALIGN_FACE)
		{
			nFaceAlignCount++;
		}

		if (nAlignment & TEXTURE_ALIGN_WORLD)
		{
			nWorldAlignCount++;
		}

		//
		// First update - copy first face's stuff into edit fields.
		//
		if (bFirst)
		{
			fshiftX = t.UAxis[3];
			fshiftY = t.VAxis[3];
			fscaleX = t.scale[0];
			fscaleY = t.scale[1];
			frotate = t.rotate;
			material = t.material;
			strTexture = t.texture;
			nLightmapScale = t.nLightmapScale;

			//
			// Get the face's orientation. This is used by Apply to make intelligent decisions.
			//
			m_eOrientation = pFace->GetOrientation();
			Assert(m_eOrientation != FACE_ORIENTATION_INVALID);

			//
			// Set the appropriate checkbox state for the face attributes.
			//
			m_FaceContents = t.q2contents;
			m_FaceSurface = t.q2surface;

			for (int nItem = 0; nItem < sizeof(FaceAttributes) / sizeof(FaceAttributes[0]); nItem++)
			{
				int nSet = ((*FaceAttributes[nItem].puAttribute & FaceAttributes[nItem].uFlag) != 0);
				CButton *pButton = (CButton *)GetDlgItem(FaceAttributes[nItem].uControlID);
				if (pButton != NULL)
				{
					pButton->SetCheck(nSet);
				}
			}

			bFirst = FALSE;
	
			if (pOnlyFace)	// use one face - now break
			{	
				break;
			}
		}
		else
		{
			// update fields with face's data
			if (t.UAxis[3] != fshiftX)
			{
				fshiftX = NOT_INIT;
			}

			if (t.VAxis[3] != fshiftY)
			{
				fshiftY = NOT_INIT;
			}

			if (t.scale[0] != fscaleX)
			{
				fscaleX = NOT_INIT;
			}

			if (t.scale[1] != fscaleY)
			{
				fscaleY = NOT_INIT;
			}

			if (t.rotate != frotate)
			{
				frotate = NOT_INIT;
			}

			if (t.material != material)
			{
				material = NOT_INIT;
			}

			if (t.nLightmapScale != nLightmapScale)
			{
				nLightmapScale = NOT_INIT;
			}

			if (!strTexture.IsEmpty() && strTexture != t.texture)
			{
				strTexture = "";
			}

			//
			// Update the checkbox state for the face attributes. If any of this face's
			// attributes are different from the current checkbox state, set the checkbox
			// to the undefined state.
			//
			m_FaceContents = t.q2contents;
			m_FaceSurface = t.q2surface;

			for (int nItem = 0; nItem < sizeof(FaceAttributes) / sizeof(FaceAttributes[0]); nItem++)
			{
				int nSet = ((*FaceAttributes[nItem].puAttribute & FaceAttributes[nItem].uFlag) != 0);
				CButton *pButton = (CButton *)GetDlgItem(FaceAttributes[nItem].uControlID);
				if (pButton != NULL)
				{
					if (pButton->GetCheck() != nSet)
					{
						pButton->SetButtonStyle(BS_AUTO3STATE);
						pButton->SetCheck(2);
					}
				}
			}
		}
	}

	//
	// Set the state of the face alignment checkbox.
	//
	CButton *pFaceAlign = (CButton *)GetDlgItem(IDC_ALIGN_FACE);

	if (nFaceAlignCount == 0)
	{
		pFaceAlign->SetCheck(0);
	}
	else if (nFaceAlignCount == faceCount)
	{
		pFaceAlign->SetCheck(1);
	}
	else
	{
		pFaceAlign->SetCheck(2);
	}

	//
	// Set the state of the world alignment checkbox.
	//
	CButton *pWorldAlign = (CButton *)GetDlgItem(IDC_ALIGN_WORLD);

	if (nWorldAlignCount == 0)
	{
		pWorldAlign->SetCheck(0);
	}
	else if (nWorldAlignCount == faceCount)
	{
		pWorldAlign->SetCheck(1);
	}
	else
	{
		pWorldAlign->SetCheck(2);
	}

	//
	// Set up fields.
	//
	FloatToSpin(fshiftX, (CSpinButtonCtrl*)GetDlgItem(IDC_SPINSHIFTX), FALSE);
	FloatToSpin(fshiftY, (CSpinButtonCtrl*)GetDlgItem(IDC_SPINSHIFTY), FALSE);
	IntegerToSpin(nLightmapScale, (CSpinButtonCtrl *)GetDlgItem(IDC_SPIN_LIGHTMAP_SCALE));

	FloatToWnd(fscaleX, &m_scaleX);
	FloatToWnd(fscaleY, &m_scaleY);

	FloatToSpin(frotate, (CSpinButtonCtrl*)GetDlgItem(IDC_SPINROTATE), TRUE);

	if (!strTexture.IsEmpty())
	{
		SelectTexture( strTexture );
	}
	else
	{
		// make empty
		m_TextureList.SetCurSel( -1 );
	}

	//
	// if no faces selected -- get selection from texture bar
	//
	if( faceCount == 0 )
	{
		CString strTexName = GetDefaultTextureName();
		SelectTexture( strTexName );
	}

	//
	// Call ctexturebar implementation because OUR implementation sets the 
	// q2 checkboxes, which flashes the screen a bit (cuz we change them
	// again three lines down.)
	//
	UpdateTexture();

	// Update the smoothing group data.
	if ( GetMaterialPageTool() == MATERIALPAGETOOL_SMOOTHING_GROUP )
	{
		m_FaceSmoothDlg.UpdateControls();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : uCmd - 
// Output : Returns TRUE on success, FALSE on failure.
//-----------------------------------------------------------------------------
BOOL CFaceEditMaterialPage::OnAlign( UINT uCmd )
{
	// Set the material tool current.
	SetMaterialPageTool( MATERIALPAGETOOL_MATERIAL );

	// mark position in undo stack
	GetHistory()->MarkUndoPosition(NULL, "Align texture");

	CFaceEditSheet *pSheet = ( CFaceEditSheet* )GetParent();
	int faceCount = pSheet->GetFaceListCount();

	for( int i = 0; i < faceCount; i++ )
	{
		CMapFace *pFace = pSheet->GetFaceListDataFace( i );

		CMapSolid *pSolid = pSheet->GetFaceListDataSolid( i );
		GetHistory()->Keep( pSolid );

		switch( uCmd )
		{
			case IDC_ALIGN_WORLD:
			{
				pFace->InitializeTextureAxes( TEXTURE_ALIGN_WORLD, INIT_TEXTURE_AXES | INIT_TEXTURE_FORCE );
				break;
			}

			case IDC_ALIGN_FACE:
			{
				pFace->InitializeTextureAxes( TEXTURE_ALIGN_FACE, INIT_TEXTURE_AXES | INIT_TEXTURE_FORCE );
				break;
			}
		}
	}

	CMapDoc::GetActiveMapDoc()->SetModifiedFlag();

	UpdateDialogData();

	return ( TRUE );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::OnHideMask(void)
{
	m_bHideMask = m_cHideMask.GetCheck();

	CMapFace::SetShowSelection( m_bHideMask == FALSE );

	CMapDoc::GetActiveMapDoc()->UpdateAllViews( MAPVIEW_UPDATE_ONLY_3D | MAPVIEW_UPDATE_OBJECTS | MAPVIEW_UPDATE_COLOR );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : Extents - 
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::GetAllFaceExtents( Extents_t Extents )
{
	BOOL		bFirst = TRUE;
	Extents_t	FaceExtents;

	CFaceEditSheet *pSheet = ( CFaceEditSheet* )GetParent();
	int faceCount = pSheet->GetFaceListCount();	

	for( int nFace = 0; nFace < faceCount; nFace++ )
	{
		CMapFace *pFace = pSheet->GetFaceListDataFace( nFace );
		pFace->GetFaceExtents(FaceExtents);

		if ((FaceExtents[EXTENTS_XMIN][0] < Extents[EXTENTS_XMIN][0]) || (bFirst))
		{
			Extents[EXTENTS_XMIN] = FaceExtents[EXTENTS_XMIN];
		}

		if ((FaceExtents[EXTENTS_XMAX][0] > Extents[EXTENTS_XMAX][0]) || (bFirst))
		{
			Extents[EXTENTS_XMAX] = FaceExtents[EXTENTS_XMAX];
		}

		if ((FaceExtents[EXTENTS_YMIN][1] < Extents[EXTENTS_YMIN][1]) || (bFirst))
		{
			Extents[EXTENTS_YMIN] = FaceExtents[EXTENTS_YMIN];
		}

		if ((FaceExtents[EXTENTS_YMAX][1] > Extents[EXTENTS_YMAX][1]) || (bFirst))
		{
			Extents[EXTENTS_YMAX] = FaceExtents[EXTENTS_YMAX];
		}

		if ((FaceExtents[EXTENTS_ZMIN][2] < Extents[EXTENTS_ZMIN][2]) || (bFirst))
		{
			Extents[EXTENTS_ZMIN] = FaceExtents[EXTENTS_ZMIN];
		}

		if ((FaceExtents[EXTENTS_ZMAX][2] > Extents[EXTENTS_ZMAX][2]) || (bFirst))
		{
			Extents[EXTENTS_ZMAX] = FaceExtents[EXTENTS_ZMAX];
		}

		bFirst = FALSE;
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : uCmd - 
// Output : Returns TRUE on success, FALSE on failure.
//-----------------------------------------------------------------------------
BOOL CFaceEditMaterialPage::OnJustify( UINT uCmd )
{
	// Set the material tool current.
	SetMaterialPageTool( MATERIALPAGETOOL_MATERIAL );

	BOOL		bTreatManyAsOneFace;
	Extents_t	Extents;

	// mark undo position
	GetHistory()->MarkUndoPosition( NULL, "Justify texture" );

	CFaceEditSheet *pSheet = ( CFaceEditSheet* )GetParent();
	int faceCount = pSheet->GetFaceListCount();	

	// If multiple faces are selected, use the m_bTreatManyAsOneFace variable to determine
	// how to perform the justification.
	if( faceCount > 1 )
	{
		bTreatManyAsOneFace = m_bTreatAsOneFace;
		if( bTreatManyAsOneFace )
		{
			GetAllFaceExtents( Extents );
		}
	}
	// If only one face is selected, treat it singly.
	else
	{
		bTreatManyAsOneFace = FALSE;
	}

	for( int i = 0; i < faceCount; i++ )
	{
		CMapFace *pFace = pSheet->GetFaceListDataFace( i );

		CMapSolid *pSolid = pSheet->GetFaceListDataSolid( i );
		GetHistory()->Keep( pSolid );

		if( !bTreatManyAsOneFace )
		{
			pFace->GetFaceExtents( Extents );
		}

		switch (uCmd)
		{
			case IDC_JUSTIFY_TOP:
			{
				pFace->JustifyTextureUsingExtents(TEXTURE_JUSTIFY_TOP, Extents);
				break;
			}

			case IDC_JUSTIFY_BOTTOM:
			{
				pFace->JustifyTextureUsingExtents(TEXTURE_JUSTIFY_BOTTOM, Extents);
				break;
			}

			case IDC_JUSTIFY_LEFT:
			{
				pFace->JustifyTextureUsingExtents(TEXTURE_JUSTIFY_LEFT, Extents);
				break;
			}

			case IDC_JUSTIFY_RIGHT:
			{
				pFace->JustifyTextureUsingExtents(TEXTURE_JUSTIFY_RIGHT, Extents);
				break;
			}

			case IDC_JUSTIFY_CENTER:
			{
				pFace->JustifyTextureUsingExtents(TEXTURE_JUSTIFY_CENTER, Extents);
				break;
			}

			case IDC_JUSTIFY_FITTOFACE:
			{
				pFace->JustifyTextureUsingExtents(TEXTURE_JUSTIFY_FIT, Extents);
				break;
			}
		}
	}

	CMapDoc::GetActiveMapDoc()->SetModifiedFlag();

	UpdateDialogData();

	return(TRUE);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : id - 
// Output : Returns TRUE on success, FALSE on failure.
//-----------------------------------------------------------------------------
BOOL CFaceEditMaterialPage::OnSwitchMode( UINT id )
{
	CWnd *pButton = GetDlgItem( IDC_MODE );

	CFaceEditSheet *pSheet = ( CFaceEditSheet* )GetParent();
	pSheet->SetClickMode( id );

	switch( id )
	{
	case CFaceEditSheet::ModeLiftSelect:	// set 
		pButton->SetWindowText( "Mode: Lift+Select" );
		break;
	case CFaceEditSheet::ModeLift:
		pButton->SetWindowText( "Mode: Lift" );
		break;
	case CFaceEditSheet::ModeSelect:
		pButton->SetWindowText( "Mode: Select" );
		break;
	case CFaceEditSheet::ModeApply:
		pButton->SetWindowText( "Mode: Apply (texture)" );
		break;
	case CFaceEditSheet::ModeApplyAll:
		pButton->SetWindowText( "Mode: Apply (all)" );
		break;
	case CFaceEditSheet::ModeAlignToView:
		pButton->SetWindowText( "Align To View" );
		break;
	}

	return TRUE;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::OnMode()
{
	// Set the material tool current.
	SetMaterialPageTool( MATERIALPAGETOOL_MATERIAL );

	// switch mode - 
	//  LIFT - lift texture from clicked face
	//	APPLY - apply selected texture to clicked face
	//	SELECT - mark each face
	//	LIFT/SELECT - mark clicked faces & lift textures

	CMenu menu;
	menu.CreatePopupMenu();
	menu.AppendMenu( MF_STRING, CFaceEditSheet::ModeLiftSelect, "Lift+Select" );
	menu.AppendMenu( MF_STRING, CFaceEditSheet::ModeLift, "Lift" );
	menu.AppendMenu( MF_STRING, CFaceEditSheet::ModeSelect, "Select" );
	menu.AppendMenu( MF_STRING, CFaceEditSheet::ModeApply, "Apply (texture only)" );
	menu.AppendMenu( MF_STRING, CFaceEditSheet::ModeApplyAll, "Apply (texture + values)" );
	menu.AppendMenu( MF_STRING, CFaceEditSheet::ModeAlignToView, "Align To View" );

	// track menu
	CWnd *pButton = GetDlgItem( IDC_MODE );
	CRect r;
	pButton->GetWindowRect( r );
	menu.TrackPopupMenu( TPM_LEFTALIGN | TPM_LEFTBUTTON, r.left, r.bottom, this, NULL );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : nSBCode - 
//			nPos - 
//			*pScrollBar - 
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::OnVScroll( UINT nSBCode, UINT nPos, CScrollBar *pScrollBar )
{
	Apply(NULL, FACE_APPLY_MAPPING);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pNMHDR - 
//			pResult - 
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::OnDeltaPosFloatSpin( NMHDR *pNMHDR, LRESULT *pResult ) 
{
	NM_UPDOWN *pNMUpDown = ( NM_UPDOWN* )pNMHDR;

	CEdit *pEdit = NULL;
	switch( pNMUpDown->hdr.idFrom )
	{
		case IDC_SPINSCALEY:
		{
			pEdit = &m_scaleY;
			break;
		}

		case IDC_SPINSCALEX:
		{
			pEdit = &m_scaleX;
			break;
		}
	}

	if( pEdit != NULL )
	{
		CString str;
		pEdit->GetWindowText(str);
		float fTmp = atof(str);
		fTmp += 0.1f * float( pNMUpDown->iDelta );
		str.Format( "%.2f", fTmp );
		pEdit->SetWindowText( str );

		*pResult = 0;
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : nType - 
//			cx - 
//			cy - 
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::OnSize( UINT nType, int cx, int cy )
{
	return;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::OnSelChangeTexture( void )
{
	// Set the material tool current.
	SetMaterialPageTool( MATERIALPAGETOOL_MATERIAL );

	if( !m_bInitialized )
	{
		return;
	}

	UpdateTexture();

	if( m_pCurTex != NULL )
	{
		m_TextureList.AddMRU( m_pCurTex );
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::OnCheckUnCheck( void )
{
	Apply(NULL, FACE_APPLY_MAPPING);
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::OnTreatAsOne( void )
{
	// Set the material tool current.
	SetMaterialPageTool( MATERIALPAGETOOL_MATERIAL );

	CButton *pCheck = ( CButton* )GetDlgItem( IDC_TREAT_AS_ONE );
	Assert( pCheck != NULL );
	m_bTreatAsOneFace = pCheck->GetCheck();
}


//-----------------------------------------------------------------------------
// Purpose: Invokes the texture replace dialog.
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::OnReplace( void )
{
	// Set the material tool current.
	SetMaterialPageTool( MATERIALPAGETOOL_MATERIAL );

	//
	// get active map doc
	//
	CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();
	if( !pDoc )
		return;

	// ready the replace dialog
	CReplaceTexDlg dlg( pDoc->GetSelection()->GetCount() );

	// get the texture to replace -- the default texture?!
	dlg.m_strFind = GetDefaultTextureName();

	//
	// open replace dialog -- modal
	//
	if( dlg.DoModal() != IDOK )
		return;
	
	// mark undo position
	GetHistory()->MarkUndoPosition( pDoc->GetSelection()->GetList(), "Replace Textures" );

	if( dlg.m_bMarkOnly )
	{
		pDoc->SelectObject( NULL, scClear );	// clear selection first
	}

	dlg.DoReplaceTextures();
}


//-----------------------------------------------------------------------------
// Purpose: Updates the m_pTexture data member based on the current selection.
//			Also updates the window text and the texture picture.
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::UpdateTexture( void )
{
	int iSel = m_TextureList.GetCurSel();

	if( iSel == LB_ERR )
	{
		m_TexturePic.SetTexture( NULL );
		m_pCurTex = NULL;
		return;
	}

	m_pCurTex = ( IEditorTexture* )m_TextureList.GetItemDataPtr( iSel );
	m_TexturePic.SetTexture( m_pCurTex );

	if( m_pCurTex )
	{
		char szBuf[128];
		sprintf( szBuf, "%dx%d", m_pCurTex->GetWidth(), m_pCurTex->GetHeight() );
		GetDlgItem( IDC_TEXTURESIZE )->SetWindowText( szBuf );

		char szTexName[128];
		m_pCurTex->GetShortName( szTexName );
		SetDefaultTextureName( szTexName );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Selects a texture by name.
// Input  : pszTextureName - Texture name to select.
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::SelectTexture( LPCSTR pszTextureName )
{
	int nIndex = m_TextureList.SelectString( -1, pszTextureName );

	//
	// If the texture is not in the list, add it to the list.
	//
	if( nIndex == LB_ERR )
	{
		IEditorTexture *pTex = g_Textures.FindActiveTexture( pszTextureName );
		if( pTex != NULL )
		{
			nIndex = m_TextureList.AddString( pszTextureName );
			m_TextureList.SetItemDataPtr( nIndex, pTex );
			m_TextureList.SetCurSel( nIndex );
		}
	}

	UpdateTexture();

	if( nIndex != LB_ERR )
	{
		IEditorTexture *pTex = ( IEditorTexture* )m_TextureList.GetItemDataPtr( nIndex );
		m_TextureList.AddMRU( pTex );
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::NotifyGraphicsChanged( void )
{
	if( !IsWindow( m_hWnd ) )
	{
		return;
	}

	// load groups into group list
	CString str;
	int iCurSel = m_TextureGroupList.GetCurSel();
	if (iCurSel != LB_ERR)
	{
		m_TextureGroupList.GetLBText(iCurSel, str);
	}

	m_TextureGroupList.SetRedraw(FALSE);
	m_TextureGroupList.ResetContent();
	m_TextureGroupList.AddString("All Textures");

	int nCount = g_Textures.GroupsGetCount();
	if (nCount > 1)
	{
		//
		// Skip first group ("All Textures").
		//
		for (int i = 1; i < nCount; i++)
		{
			CTextureGroup *pGroup = g_Textures.GroupsGet(i);
			if (pGroup->GetTextureFormat() == g_pGameConfig->GetTextureFormat())
			{
				const char *p = strstr(pGroup->GetName(), "textures\\");
				if (p)
				{
					p += strlen("textures\\");
				}
				else
				{
					p = pGroup->GetName();
				}

				m_TextureGroupList.AddString(p);
			}
		}
	}
	m_TextureGroupList.SetRedraw(TRUE);

	if (iCurSel == LB_ERR || m_TextureGroupList.SelectString(-1, str) == LB_ERR)
	{
		m_TextureGroupList.SetCurSel(0);
	}
	
	m_TextureGroupList.Invalidate();

	char szName[MAX_PATH];
	m_TextureGroupList.GetLBText(m_TextureGroupList.GetCurSel(), szName);
	g_Textures.SetActiveGroup(szName);

	//
	// This is called when the loaded graphics list is changed,
	// or on first init by this->Create().
	//
	m_TextureList.LoadGraphicList();
	UpdateTexture();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::OnBrowse( void )
{
	// Set the material tool current.
	SetMaterialPageTool( MATERIALPAGETOOL_MATERIAL );

	CTextureBrowser *pBrowser = GetMainWnd()->pTextureBrowser;

	int iSel = m_TextureList.GetCurSel();

	if (iSel != LB_ERR)
	{
		IEditorTexture *pTex = (IEditorTexture *)m_TextureList.GetItemDataPtr(iSel);
		if (pTex != NULL)
		{
			char sz[128];
	
			pTex->GetShortName(sz);
			pBrowser->SetInitialTexture(sz);
		}
	}

	if (pBrowser->DoModal() == IDOK)
	{
		IEditorTexture *pTex = g_Textures.FindActiveTexture(pBrowser->m_cTextureWindow.szCurTexture);
		if (pTex != NULL)
		{
			int iCount = m_TextureList.GetCount();
			for (int i = 0; i < iCount; i++)
			{
				if (pTex == (IEditorTexture *)m_TextureList.GetItemDataPtr(i))
				{
					m_TextureList.SetCurSel(i);
					UpdateTexture();
					m_TextureList.AddMRU(pTex);
					break;
				}
			}
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::OnChangeTextureGroup( void )
{
	// Set the material tool current.
	SetMaterialPageTool( MATERIALPAGETOOL_MATERIAL );

	int iGroup = m_TextureGroupList.GetCurSel();

	//
	// Set the active texture group by name.
	//
	char szName[MAX_PATH];
	m_TextureGroupList.GetLBText(iGroup, szName);
	g_Textures.SetActiveGroup(szName);

	//
	// Refresh the texture list contents.
	//
	m_TextureList.LoadGraphicList();
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::OnButtonApply( void )
{
	// Set the material tool current.
	SetMaterialPageTool( MATERIALPAGETOOL_MATERIAL );

	Apply(NULL, FACE_APPLY_ALL);
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
BOOL CFaceEditMaterialPage::OnSetActive( void )
{
	CMainFrame *pMainFrm = GetMainWnd();
	if( !pMainFrm )
		return FALSE;

	ToolManager()->SetTool( TOOL_FACEEDIT_MATERIAL );

	// Set the initial face edit tool state.
	SetMaterialPageTool( MATERIALPAGETOOL_MATERIAL );

	return CPropertyPage::OnSetActive();
}

//-----------------------------------------------------------------------------
// Purpose: Brings up the smoothing group dialog.
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::OnButtonSmoothingGroups( void )
{
	if( !m_FaceSmoothDlg.Create( IDD_SMOOTHING_GROUPS, this ) )
		return;	

	m_FaceSmoothDlg.ShowWindow( SW_SHOW );

	// Set the initial face edit tool state.
	SetMaterialPageTool( MATERIALPAGETOOL_SMOOTHING_GROUP );

	return;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::SetMaterialPageTool( unsigned short iMaterialTool )
{
	if ( m_iMaterialTool == MATERIALPAGETOOL_SMOOTHING_GROUP )
	{
		// Close the window.
		m_FaceSmoothDlg.DestroyWindow();
	}

	// Set the new material tool.
	m_iMaterialTool = iMaterialTool;
}


//-----------------------------------------------------------------------------
// Purpose: Called when a new material (.vmt file) is detected.
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::NotifyNewMaterial( IEditorTexture *pTex )
{
	m_TextureList.LoadGraphicList();
	UpdateTexture();
}


//-----------------------------------------------------------------------------
// Purpose: Called to set the enabled state of the dialog controls
//-----------------------------------------------------------------------------
void CFaceEditMaterialPage::SetReadOnly( bool bIsReadOnly )
{
	BOOL	State = ( bIsReadOnly ? FALSE : TRUE );

	m_shiftX.EnableWindow( State );
	m_shiftY.EnableWindow( State );
	m_scaleX.EnableWindow( State );
	m_scaleY.EnableWindow( State );
	m_rotate.EnableWindow( State );
	m_cLightmapScale.EnableWindow( State );
	m_cHideMask.EnableWindow( State );
	m_cExpand.EnableWindow( State );
	m_TextureList.EnableWindow( State );
	m_TextureGroupList.EnableWindow( State );

	::EnableWindow( ::GetDlgItem( m_hWnd, IDC_JUSTIFY_LEFT ), State );
	::EnableWindow( ::GetDlgItem( m_hWnd, IDC_JUSTIFY_RIGHT ), State );
	::EnableWindow( ::GetDlgItem( m_hWnd, IDC_JUSTIFY_FITTOFACE ), State );
	::EnableWindow( ::GetDlgItem( m_hWnd, IDC_JUSTIFY_TOP ), State );
	::EnableWindow( ::GetDlgItem( m_hWnd, IDC_JUSTIFY_BOTTOM ), State );
	::EnableWindow( ::GetDlgItem( m_hWnd, IDC_JUSTIFY_CENTER ), State );
	::EnableWindow( ::GetDlgItem( m_hWnd, IDC_TREAT_AS_ONE ), State );
	::EnableWindow( ::GetDlgItem( m_hWnd, IDC_ALIGN_WORLD ), State );
	::EnableWindow( ::GetDlgItem( m_hWnd, IDC_ALIGN_FACE ), State );
	::EnableWindow( ::GetDlgItem( m_hWnd, IDC_BROWSE ), State );
	::EnableWindow( ::GetDlgItem( m_hWnd, IDC_REPLACE ), State );
	::EnableWindow( ::GetDlgItem( m_hWnd, ID_FACEEDIT_APPLY ), State );
	::EnableWindow( ::GetDlgItem( m_hWnd, IDC_MODE ), State );
	::EnableWindow( ::GetDlgItem( m_hWnd, ID_BUTTON_SMOOTHING_GROUPS ), State );
}