//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================

#include <stdafx.h>
#include "Worldcraft.h"
#include "ToolDisplace.h"
#include "MainFrm.h"
#include "FaceEditSheet.h"
#include "GlobalFunctions.h"
#include "MapAtom.h"
#include "MapSolid.h"
#include "MapView3D.h"
#include "History.h"
#include "Camera.h"
#include "MapDoc.h"
#include "ChunkFile.h"

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


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CToolDisplace::CToolDisplace()
{
    m_uiTool = DISPTOOL_SELECT;
	m_uiEffect = DISPPAINT_EFFECT_RAISELOWER;
	m_uiBrushType = DISPPAINT_BRUSHTYPE_SOFT;

	m_iPaintChannel = DISPPAINT_CHANNEL_POSITION;
	m_flPaintValueGeo = 5.0f;
	m_flPaintValueData = 25.0f;
	m_iPaintAxis = DISPPAINT_AXIS_FACE;
	m_vecPaintAxis.Init( 0.0f, 0.0f, 1.0f );

	m_bAutoSew = false;
	m_bSpatial = false;
	m_flSpatialRadius = 15.0f;
	m_bSpatialRadius = false;

	m_bSelectMaskTool = true;
	m_bGridMaskTool = false;

	m_bResubdivide = false;

	m_bLMBDown = false;
	m_bRMBDown = false;

	m_bNudge = false;
	m_bNudgeInit = false;
	m_EditDispHandle = EDITDISPHANDLE_INVALID;

	// load filters from file
	static char szProgramDir[MAX_PATH];
	APP()->GetDirectory( DIR_PROGRAM, szProgramDir );
	strcat( szProgramDir, "filters\\dispfilters.txt" );
	LoadFilters( szProgramDir );
	AddFiltersToManagers();
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CToolDisplace::~CToolDisplace()
{
	// destroy filters
	m_FilterRaiseLowerMgr.Destroy();
	m_FilterRaiseToMgr.Destroy();
	m_FilterSmoothMgr.Destroy();
}


//-----------------------------------------------------------------------------
// Purpose: Called when the tool is activated.
// Input  : eOldTool - The ID of the previously active tool.
//-----------------------------------------------------------------------------
void CToolDisplace::OnActivate(ToolID_t eOldTool)
{
	//
	// initialize masks
	//
	CMapDisp::SetSelectMask( m_bSelectMaskTool );
	CMapDisp::SetGridMask( m_bGridMaskTool );
}
 
   
//-----------------------------------------------------------------------------
// Purpose: Called when the tool is deactivated.
// Input  : eNewTool - The ID of the tool that is being activated.
//-----------------------------------------------------------------------------
void CToolDisplace::OnDeactivate(ToolID_t eNewTool)
{
	//
	// reset masks
	//
	CMapDisp::SetSelectMask( false );
	CMapDisp::SetGridMask( false );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
inline void CToolDisplace::UpdateMapViews( CMapView3D *pView )
{
	CMapDoc *pDoc = pView->GetDocument();
	if( pDoc )
	{
		pDoc->SetModifiedFlag();
		pDoc->Update3DViews();
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
inline void CToolDisplace::CalcViewCenter( CMapView3D *pView )
{
	CRect windowRect;
	pView->GetWindowRect( windowRect );
	m_viewCenter = windowRect.CenterPoint();
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CToolDisplace::OnLMouseDown3D( CMapView3D *pView, UINT nFlags, CPoint point )
{
	// Set down flags
	m_bLMBDown = true;

	// Selection.
	if( m_uiTool == DISPTOOL_SELECT || ( GetAsyncKeyState( VK_CONTROL ) & 0x8000 ) )
	{
		// handle selection at point
		HandleSelection( pView, point );

		// update
		UpdateMapViews( pView );
		return true;
	}

	// Tagging.
	if ( m_uiTool == DISPTOOL_TAG_WALKABLE || m_uiTool == DISPTOOL_TAG_BUILDABLE )
	{
		// Do tagging.
		HandleTagging( pView, point );
		return true;
	}

	// Resize the spatial painting sphere.
	if( ( m_uiTool == DISPTOOL_PAINT ) && ( IsSpatialPainting() ) &&
		( GetAsyncKeyState( VK_MENU ) & 0x8000 ) )
	{
		ResizeSpatialRadius_Activate( pView );
		return true;
	}

	// Nudging.
	if( ( m_uiTool == DISPTOOL_PAINT ) && ( GetAsyncKeyState( VK_SHIFT ) & 0x8000 ) )
	{
		// is the current effect raise/lower (nudge only works in raise/lower mode)
		if( m_uiEffect == DISPPAINT_EFFECT_RAISELOWER )
		{
			EditDispHandle_t handle = GetHitPos( pView, point );
			if( handle != EDITDISPHANDLE_INVALID )
			{
				m_EditDispHandle = handle;

				Nudge_Activate( pView, handle );
				UpdateMapViews( pView );
				return true;
			}
		}
	}

	// Painting.
	if( m_uiTool == DISPTOOL_PAINT )
	{
		// get hit info
		EditDispHandle_t handle = GetHitPos( pView, point );
		if( handle == EDITDISPHANDLE_INVALID )
			return false;
		m_EditDispHandle = handle;

		CMapDisp *pDisp = EditDispMgr()->GetDisp( handle );

		IWorldEditDispMgr *pDispMgr = GetActiveWorldEditDispManager();
		if( !pDispMgr )
			return false;

		pDispMgr->PreUndo( "Displacement Modifier" );

		// Paint using the correct mode.
		if ( m_bSpatial )
		{
			int nDispCount = pDispMgr->SelectCount();
			for ( int iDisp = 0; iDisp < nDispCount; iDisp++ )
			{
				CMapDisp *pDisp = pDispMgr->GetFromSelect( iDisp );
				if ( pDisp )
				{
					pDisp->Paint_Init( DISPPAINT_CHANNEL_POSITION );
				}
			}

			// setup for undo - modifying the displacement (painting)
			ApplySpatialPaintTool( nFlags, point, pDisp );
		}
		else
		{
			// setup for undo - modifying the displacement (painting)
			pDispMgr->Undo( handle, true );
			pDisp = EditDispMgr()->GetDisp( handle );
			ApplyPaintTool( nFlags, point, pDisp );    
		}

		// update
		UpdateMapViews( pView );
	}

	return true;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CToolDisplace::OnLMouseUp3D( CMapView3D *pView, UINT nFlags, CPoint point )
{
	// left button up
	m_bLMBDown = false;

	if ( m_bNudge )
	{
		Nudge_Deactivate();
	}

	if ( m_bSpatialRadius )
	{
		ResizeSpatialRadius_Deactivate();
	}

	IWorldEditDispMgr *pDispMgr = GetActiveWorldEditDispManager();
	if( pDispMgr )
	{
		pDispMgr->PostUndo();
	}

	return true;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CToolDisplace::OnRMouseDown3D( CMapView3D *pView, UINT nFlags, CPoint point )
{
	// left button down
    m_bRMBDown = true;

	//
	// lifting the face normal - painting with the axis set to "Face Normal"
	//
	if( ( m_uiTool == DISPTOOL_PAINT ) && ( m_iPaintAxis == DISPPAINT_AXIS_FACE ) &&
		( GetAsyncKeyState( VK_MENU ) & 0x8000 ) )
	{
		LiftFaceNormal( pView, point );
		return true;
	}

	// Tagging.
	if ( m_uiTool == DISPTOOL_TAG_WALKABLE || m_uiTool == DISPTOOL_TAG_BUILDABLE )
	{
		// Do tagging.
		HandleTaggingReset( pView, point );
		return true;
	}

	//
	// handle the normal paint procedure
	//
	if( m_uiTool == DISPTOOL_PAINT )
	{
		// get hit info
		EditDispHandle_t handle = GetHitPos( pView, point );
		if( handle == EDITDISPHANDLE_INVALID )
			return false;
		m_EditDispHandle = handle;

		CMapDisp *pDisp = EditDispMgr()->GetDisp( handle );

		IWorldEditDispMgr *pDispMgr = GetActiveWorldEditDispManager();
		if( !pDispMgr )
			return false;

		pDispMgr->PreUndo( "Displacement Modifier" );

		// apply the current displacement tool
		if ( m_bSpatial )
		{
			int nDispCount = pDispMgr->SelectCount();
			for ( int iDisp = 0; iDisp < nDispCount; iDisp++ )
			{
				CMapDisp *pDisp = pDispMgr->GetFromSelect( iDisp );
				if ( pDisp )
				{
					pDisp->Paint_Init( DISPPAINT_CHANNEL_POSITION );
				}
			}

			ApplySpatialPaintTool( nFlags, point, pDisp );
		}
		else
		{
			// setup for undo
			pDispMgr->Undo( handle, true );
			pDisp = EditDispMgr()->GetDisp( handle );
			ApplyPaintTool( nFlags, point, pDisp );    
		}

		// update
		UpdateMapViews( pView );
	}

	return true;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CToolDisplace::OnRMouseUp3D( CMapView3D *pView, UINT nFlags, CPoint point )
{
	// left button up
	m_bRMBDown = false;

	IWorldEditDispMgr *pDispMgr = GetActiveWorldEditDispManager();
	if( pDispMgr )
	{
		pDispMgr->PostUndo();
	}

	return true;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CToolDisplace::OnMouseMove3D( CMapView3D *pView, UINT nFlags, CPoint point )
{

	// nudging
	if ( ( m_uiTool == DISPTOOL_PAINT ) && ( GetAsyncKeyState( VK_SHIFT ) & 0x8000 ) && 
		m_bLMBDown && m_bNudge )
	{
		Nudge_Do();
	}
	// Resizing the spatial sphere.
	else if ( ( m_uiTool == DISPTOOL_PAINT ) && ( GetAsyncKeyState( VK_MENU ) & 0x8000 ) &&
		      m_bLMBDown && m_bSpatialRadius )
	{
		ResizeSpatialRadius_Do();
	}
	// painting
	else
	{
		// get hit info
		EditDispHandle_t handle = GetHitPos( pView, point );
		if( handle == EDITDISPHANDLE_INVALID )
			return false;
		m_EditDispHandle = handle;

		CMapDisp *pDisp = EditDispMgr()->GetDisp( handle );

		//
		// continue with tool operation?!
		//
		if( ( m_bLMBDown || m_bRMBDown ) && !( GetAsyncKeyState( VK_CONTROL ) & 0x8000 ) &&
			( m_uiTool == DISPTOOL_PAINT ) )
		{	
			if ( m_bSpatial )
			{
				ApplySpatialPaintTool( nFlags, point, pDisp );
			}
			else
			{
				ApplyPaintTool( nFlags, point, pDisp );
			}
		}

		// not nudging anymore -- if we were
		if( m_bNudge )
		{
			Nudge_Deactivate();
		}

		if ( m_bSpatialRadius )
		{
			ResizeSpatialRadius_Deactivate();
		}
	}

	// update
	UpdateMapViews( pView );

	return true;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CToolDisplace::LiftFaceNormal( CMapView3D *pView, CPoint point )
{
	//
	// check for closest solid object
	//
	ULONG		ulFace;
	CMapClass	*pObject;

	if( ( ( pObject = pView->NearestObjectAt( point, ulFace ) ) != NULL ) )
	{
		if( pObject->IsMapClass( MAPCLASS_TYPE( CMapSolid ) ) )
		{
			// get the solid
			CMapSolid *pSolid = ( CMapSolid* )pObject;
			if( !pSolid )
				return;

			// trace a line and get the normal -- will get a displacement normal
			// if one exists
			CMapFace *pFace = pSolid->GetFace( ulFace );
			if( !pFace )
				return;

			Vector vRayStart, vRayEnd;
			pView->BuildRay( point, vRayStart, vRayEnd );

			Vector vHitPos, vHitNormal;
			if( pFace->TraceLine( vHitPos, vHitNormal, vRayStart, vRayEnd ) )
			{
				// set the paint direction
				m_vecPaintAxis = vHitNormal;
			}
			else
			{
				// will default to z if no normal found
				m_vecPaintAxis.Init( 0.0f, 0.0f, 1.0f );
			}
		}
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CToolDisplace::Nudge_Activate( CMapView3D *pView, EditDispHandle_t dispHandle )
{
	IWorldEditDispMgr *pDispMgr = GetActiveWorldEditDispManager();
	if( !pDispMgr )
		return;

	pDispMgr->PreUndo( "Displacement Nudge" );

	// Setup paint (nudge) using the correct mode.
	if ( m_bSpatial )
	{
		int nDispCount = pDispMgr->SelectCount();
		for ( int iDisp = 0; iDisp < nDispCount; iDisp++ )
		{
			CMapDisp *pDisp = pDispMgr->GetFromSelect( iDisp );
			if ( pDisp )
			{
				pDisp->Paint_Init( DISPPAINT_CHANNEL_POSITION );
			}
		}
	}
	else
	{
		// setup for undo
		pDispMgr->Undo( dispHandle, true );
	}

	// setup the cursor for "nudging"
	CalcViewCenter( pView );
	SetCursorPos( m_viewCenter.x, m_viewCenter.y );
	pView->SetCapture();

	// set nudging
	m_bNudge = true;
	m_bNudgeInit = true;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CToolDisplace::Nudge_Deactivate( void )
{
	ReleaseCapture();
	m_bNudge = false;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CToolDisplace::Nudge_Do( void )
{
	CMapDisp *pNudgeDisp = GetEditDisp();
	if (pNudgeDisp == NULL)
	{
		return;
	}

	//
	// find the greatest delta and "nudge"
	//
	// NOTE: using get cursor position, because it is different than the 
	//       "point" incoming into mouse move????
	//
	CPoint nudgePos;
	GetCursorPos( &nudgePos );

	CPoint nudgeDelta;
	nudgeDelta.x = nudgePos.x - m_viewCenter.x;
	nudgeDelta.y = nudgePos.y - m_viewCenter.y;

	float delta;
	if( fabs( nudgeDelta.x ) < fabs( nudgeDelta.y ) )
	{
		delta = nudgeDelta.y;
	}
	else
	{
		delta = nudgeDelta.x;
	}
	delta = -delta;

	if ( !IsSpatialPainting() )
	{
		CDispMapImageFilter *pFilter = m_FilterRaiseLowerMgr.GetActiveFilter();
		if( !pFilter )
			return;
		
		// set the dynamic filter data
		pFilter->m_DataType = DISPPAINT_CHANNEL_POSITION;
		pFilter->m_Scale = ( delta * 0.25 ) * ( float )( ( int )( m_flPaintValueGeo / 10.0f ) + 1 ) ;
		
		// apply the filter to the displacement surface(s)
		m_FilterRaiseLowerMgr.Apply( pFilter, pNudgeDisp, m_iPaintAxis, m_vecPaintAxis, m_bAutoSew );
	}
	else
	{
		// Get the hit index and check for validity.
		int iHit = pNudgeDisp->GetTexelHitIndex();
		if ( iHit != -1 )
		{
			// Initialize the spatial paint data.
			SpatialPaintData_t spatialData;
			spatialData.m_nEffect = DISPPAINT_EFFECT_RAISELOWER;
			spatialData.m_uiBrushType = m_uiBrushType;
			spatialData.m_flRadius = m_flSpatialRadius;
			spatialData.m_flScalar = delta;
			spatialData.m_bNudge = true;
			spatialData.m_bNudgeInit = m_bNudgeInit;
			pNudgeDisp->GetVert( iHit, spatialData.m_vCenter );
			VectorCopy( m_vecPaintAxis, spatialData.m_vPaintAxis );
			
			m_DispPaintMgr.Paint( spatialData, m_bAutoSew );

			// Done with the init.
			m_bNudgeInit = false;
		}
	}

	// reset the cursor pos
	SetCursorPos( m_viewCenter.x, m_viewCenter.y );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CToolDisplace::ApplyPaintTool( UINT nFlags, CPoint point, CMapDisp *pDisp )
{
	switch( m_uiEffect )
	{ 
	case DISPPAINT_EFFECT_RAISELOWER:
		{
			CDispMapImageFilter *pFilter = m_FilterRaiseLowerMgr.GetActiveFilter();
			if( pFilter )
			{
				pFilter->m_DataType = m_iPaintChannel;
				if ( m_iPaintChannel == DISPPAINT_CHANNEL_POSITION )
				{
					pFilter->m_Scale = m_flPaintValueGeo;
				}
				else if ( m_iPaintChannel == DISPPAINT_CHANNEL_ALPHA )
				{
					pFilter->m_Scale = m_flPaintValueData;
				}

				if( m_bRMBDown )
				{
					pFilter->m_Scale = -pFilter->m_Scale;
				}
				
				// apply the filter to the displacement surface(s)
				m_FilterRaiseLowerMgr.Apply( pFilter, pDisp, m_iPaintAxis, m_vecPaintAxis, m_bAutoSew );
			}
			return;
		}
	case DISPPAINT_EFFECT_MODULATE:
		{
			// no modulate filters or filter manager currently!
			return;
		}
	case DISPPAINT_EFFECT_SMOOTH:
		{
			CDispMapImageFilter *pFilter = m_FilterSmoothMgr.GetActiveFilter();
			if( pFilter )
			{
				pFilter->m_DataType = m_iPaintChannel;
				pFilter->m_Scale = 1.0f;
				
				int areaValue = 3;
				if ( m_iPaintChannel == DISPPAINT_CHANNEL_POSITION )
				{
					areaValue = ( m_flPaintValueGeo * 2 ) + 1;
				}
				else if ( m_iPaintChannel == DISPPAINT_CHANNEL_ALPHA )
				{
					areaValue = ( m_flPaintValueData * 2 ) + 1;
				}				
				if( areaValue < 3 ) { areaValue = 3; }
				if( areaValue > 7 ) { areaValue = 7; }
				
				pFilter->m_AreaHeight = areaValue;
				pFilter->m_AreaWidth = areaValue;

				// apply the filter to the displacement surface(s)
				m_FilterSmoothMgr.Apply( pFilter, pDisp, m_iPaintAxis, m_vecPaintAxis, m_bAutoSew );
			}
			return;
		}
	case DISPPAINT_EFFECT_RAISETO:
		{
			CDispMapImageFilter *pFilter = m_FilterRaiseToMgr.GetActiveFilter();
			if( pFilter )
			{
				pFilter->m_DataType = m_iPaintChannel;
				if ( m_iPaintChannel == DISPPAINT_CHANNEL_POSITION )
				{
					pFilter->m_Scale = m_flPaintValueGeo;
				}
				else if ( m_iPaintChannel == DISPPAINT_CHANNEL_ALPHA )
				{
					pFilter->m_Scale = m_flPaintValueData;
				}

				// apply the filter to the displacement surface(s)
				m_FilterRaiseToMgr.Apply( pFilter, pDisp, m_iPaintAxis, m_vecPaintAxis, m_bAutoSew );
			}
			return;
		}
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CToolDisplace::ApplySpatialPaintTool( UINT nFlags, CPoint point, CMapDisp *pDisp )
{
	// Right mouse button only used to paint in a Raise/Lower situation.
	if ( ( m_uiEffect != DISPPAINT_EFFECT_RAISELOWER ) && m_bRMBDown )
		return;

	// Get the hit index and check for validity.
	int iHit = pDisp->GetTexelHitIndex();
	if ( iHit == -1 )
		return;

	// Initialize the spatial paint data.
	SpatialPaintData_t spatialData;
	spatialData.m_nEffect = m_uiEffect;
	spatialData.m_uiBrushType = m_uiBrushType;
	spatialData.m_flRadius = m_flSpatialRadius;
	spatialData.m_flScalar = m_flPaintValueGeo;
	spatialData.m_bNudge = false;
	if ( m_bRMBDown )
	{
		spatialData.m_flScalar = -spatialData.m_flScalar;
	}
	pDisp->GetVert( iHit, spatialData.m_vCenter );
	VectorCopy( m_vecPaintAxis, spatialData.m_vPaintAxis );

	m_DispPaintMgr.Paint( spatialData, m_bAutoSew );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CToolDisplace::ResizeSpatialRadius_Activate( CMapView3D *pView )
{
	// Calculate the center of the view and capture the mouse cursor.
	CalcViewCenter( pView );
	SetCursorPos( m_viewCenter.x, m_viewCenter.y );
	pView->SetCapture();

	m_bSpatialRadius = true;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CToolDisplace::ResizeSpatialRadius_Deactivate( void )
{
	ReleaseCapture();
	m_bSpatialRadius = false;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CToolDisplace::ResizeSpatialRadius_Do( void )
{
	CPoint cursorPos;
	GetCursorPos( &cursorPos );

	// Calculate the delta between the cursor from last frame and this one.
	CPoint cursorDelta;
	cursorDelta.x = cursorPos.x - m_viewCenter.x;
	cursorDelta.y = cursorPos.y - m_viewCenter.y;

	float flDelta;
	if( fabs( cursorDelta.x ) < fabs( cursorDelta.y ) )
	{
		flDelta = cursorDelta.y;
	}
	else
	{
		flDelta = cursorDelta.x;
	}
	flDelta = -flDelta;

	// Adjust the sphere radius.
	m_flSpatialRadius += flDelta;

	// reset the cursor pos
	SetCursorPos( m_viewCenter.x, m_viewCenter.y );

	//
	// Update the paint dialog.
	//
	CFaceEditSheet *pSheet = GetMainWnd()->GetFaceEditSheet();
	if( pSheet )
	{
		pSheet->m_DispPage.UpdatePaintDialogs();
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CToolDisplace::HandleSelection( CMapView3D *pView, CPoint point )
{
	//
	// check for closest solid object
	//
	ULONG		ulFace;
	CMapClass	*pObject;

	bool bShift = ( ( GetAsyncKeyState( VK_SHIFT ) & 0x8000 ) != 0 );

	if( ( ( pObject = pView->NearestObjectAt( point, ulFace ) ) != NULL ) )
	{
		if( pObject->IsMapClass( MAPCLASS_TYPE( CMapSolid ) ) )
		{
			// get the solid
			CMapSolid *pSolid = ( CMapSolid* )pObject;
			
			// setup initial command state
			int cmd = CMapDoc::scToggle | CMapDoc::scClear | CMapDoc::scUpdateDisplay;
			
			//
			// don't "clear" if CTRL is pressed
			//
			if( GetAsyncKeyState( VK_CONTROL ) & 0x8000 )
			{
				cmd &= ~CMapDoc::scClear;
			}
			
			CMapDoc *pDoc = pView->GetDocument();
			if( !pDoc )
				return;
			
			// If they are holding down SHIFT, select the entire solid.
			if ( bShift )
			{
				pDoc->SelectFace( pSolid, -1, cmd );
			}
			// Otherwise, select a single face.
			else
			{
				pDoc->SelectFace( pSolid, ulFace, cmd );
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Handle the overriding of displacement triangle tag.
//-----------------------------------------------------------------------------
void CToolDisplace::HandleTagging( CMapView3D *pView, CPoint point )
{
	// Get the displacement face (if any) at the 2d point.
	ULONG ulFace;
	CMapClass *pObject = NULL;

	if( ( ( pObject = pView->NearestObjectAt( point, ulFace ) ) != NULL ) )
	{
		if( pObject->IsMapClass( MAPCLASS_TYPE( CMapSolid ) ) )
		{
			// Get the face and check for a displacement.
			CMapSolid *pSolid = ( CMapSolid* )pObject;
			CMapFace *pFace = pSolid->GetFace( ( int )ulFace );
			if ( pFace && pFace->HasDisp() )
			{
				EditDispHandle_t hDisp = pFace->GetDisp();
				CMapDisp *pDisp = EditDispMgr()->GetDisp( hDisp );

				Vector vecStart, vecEnd;
				GetRay( pView, point, vecStart, vecEnd );

				int iTri = pDisp->CollideWithDispTri( vecStart, vecEnd );
				if ( iTri != -1 )
				{
					if ( m_uiTool == DISPTOOL_TAG_WALKABLE )
					{
						if ( pDisp->IsTriTag( iTri, COREDISPTRI_TAG_FORCE_WALKABLE_BIT ) )
						{
							pDisp->ToggleTriTag( iTri, COREDISPTRI_TAG_FORCE_WALKABLE_VAL );
						}
						else
						{
							pDisp->SetTriTag( iTri, COREDISPTRI_TAG_FORCE_WALKABLE_BIT );
							if ( !pDisp->IsTriTag( iTri, COREDISPTRI_TAG_WALKABLE ) )
							{
								pDisp->SetTriTag( iTri, COREDISPTRI_TAG_FORCE_WALKABLE_VAL );
							}
							else
							{
								pDisp->ResetTriTag( iTri, COREDISPTRI_TAG_FORCE_WALKABLE_VAL );
							}
						}

						pDisp->UpdateWalkable();
					}
					else if ( m_uiTool == DISPTOOL_TAG_BUILDABLE )
					{
						if ( pDisp->IsTriTag( iTri, COREDISPTRI_TAG_FORCE_BUILDABLE_BIT ) )
						{
							pDisp->ToggleTriTag( iTri, COREDISPTRI_TAG_FORCE_BUILDABLE_VAL );
						}
						else
						{
							pDisp->SetTriTag( iTri, COREDISPTRI_TAG_FORCE_BUILDABLE_BIT );
							if ( !pDisp->IsTriTag( iTri, COREDISPTRI_TAG_BUILDABLE ) )
							{
								pDisp->SetTriTag( iTri, COREDISPTRI_TAG_FORCE_BUILDABLE_VAL );
							}
							else
							{
								pDisp->ResetTriTag( iTri, COREDISPTRI_TAG_FORCE_BUILDABLE_VAL );
							}
						}

						pDisp->UpdateBuildable();
					}
				}
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Handle the overriding of displacement triangle tag.
//-----------------------------------------------------------------------------
void CToolDisplace::HandleTaggingReset( CMapView3D *pView, CPoint point )
{
	// Get the displacement face (if any) at the 2d point.
	ULONG ulFace;
	CMapClass *pObject = NULL;

	if( ( ( pObject = pView->NearestObjectAt( point, ulFace ) ) != NULL ) )
	{
		if( pObject->IsMapClass( MAPCLASS_TYPE( CMapSolid ) ) )
		{
			// Get the face and check for a displacement.
			CMapSolid *pSolid = ( CMapSolid* )pObject;
			CMapFace *pFace = pSolid->GetFace( ( int )ulFace );
			if ( pFace && pFace->HasDisp() )
			{
				EditDispHandle_t hDisp = pFace->GetDisp();
				CMapDisp *pDisp = EditDispMgr()->GetDisp( hDisp );

				Vector vecStart, vecEnd;
				GetRay( pView, point, vecStart, vecEnd );

				int iTri = pDisp->CollideWithDispTri( vecStart, vecEnd );
				if ( iTri != -1 )
				{
					if ( m_uiTool == DISPTOOL_TAG_WALKABLE )
					{
						pDisp->ResetTriTag( iTri, COREDISPTRI_TAG_FORCE_WALKABLE_BIT );
						pDisp->UpdateWalkable();
					}
					else if ( m_uiTool == DISPTOOL_TAG_BUILDABLE )
					{
						pDisp->ResetTriTag( iTri, COREDISPTRI_TAG_FORCE_BUILDABLE_BIT );
						pDisp->UpdateBuildable();
					}
				}
			}
		}
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
EditDispHandle_t CToolDisplace::GetHitPos( CMapView3D *pView, CPoint point )
{
	//
	// get ray info
	//
	Vector rayStart, rayEnd;
	GetRay( pView, point, rayStart, rayEnd );

	// generate selected displacement list
	int dispCount = GetSelectedDisps();
	if( dispCount == 0 )
		return NULL;

	// collide against all "active" displacements and set texel hit data
	return CollideWithSelectedDisps( rayStart, rayEnd );	
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CToolDisplace::GetRay( CMapView3D *pView, CPoint point, Vector& rayStart, Vector& rayEnd )
{
	//
	// get screen coordinates from client point
	//
	Vector2D clientPt;
	clientPt[0] = point.x;
	clientPt[1] = point.y;

	Vector2D screenPt;
	pView->ClientToScreen( screenPt, clientPt );

	//
	// get world coordinate from screen point
	//
	Vector clickPt;
	pView->ScreenToWorld( clickPt, screenPt );

	//
	// build a ray from view point through "click" point (on near clipping plane)
	//
	const CCamera *pCamera = pView->GetCamera();
	pCamera->GetViewPoint( rayStart );

	//
	// set ray start and end
	//
	Vector ray;
	VectorSubtract( clickPt, rayStart, ray );

	VectorNormalize( ray );
	VectorScale( ray, 8192.0f, ray );
	VectorAdd( rayStart, ray, rayEnd );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CToolDisplace::GetSelectedDisps( void )
{
	//
	// get a valid displacement manager
	//
	IWorldEditDispMgr *pDispMgr = GetActiveWorldEditDispManager();
	if( !pDispMgr )
		return 0;

	// clear the selection list
	pDispMgr->SelectClear();

	//
	// add all selected displacements to "displacement manager"'s selection list
	//
	CFaceEditSheet *pSheet = GetMainWnd()->GetFaceEditSheet();
	if( !pSheet )
		return 0;

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

		if( pFace->HasDisp() )
		{
			EditDispHandle_t handle = pFace->GetDisp();
			CMapDisp *pDisp = EditDispMgr()->GetDisp( handle );
			pDisp->ResetTexelHitIndex();
			pDispMgr->AddToSelect( handle );
		}
	}

	// return the number of displacements in list
	return pDispMgr->SelectCount();
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
EditDispHandle_t CToolDisplace::CollideWithSelectedDisps( const Vector &rayStart, const Vector &rayEnd )
{
	EditDispHandle_t handle = EDITDISPHANDLE_INVALID;
	float			 minDist = 99999.9f;
	int				 minIndex = -1;

	//
	// get a valid displacement manager
	//
	IWorldEditDispMgr *pDispMgr = GetActiveWorldEditDispManager();
	if( !pDispMgr )
		return NULL;

	int dispCount = pDispMgr->SelectCount();
	for( int i = 0; i < dispCount; i++ )
	{
		// get the current displacement
		CMapDisp *pDisp = pDispMgr->GetFromSelect( i );
		if( !pDisp )
			continue;

		bool bCollide = RayAABBTest( pDisp, rayStart, rayEnd );
		if( bCollide )
		{
			Vector point;
			int size = pDisp->GetSize();
			for( int j = 0; j < size; j++ )
			{
				// get current point
				pDisp->GetVert( j, point );

				// find point closest to ray
				float dist = DistFromPointToRay( rayStart, rayEnd, point );
				if( dist < minDist )
				{
					CMapFace *pFace = ( CMapFace* )pDisp->GetParent();
					handle = pFace->GetDisp();
					minDist = dist;
					minIndex = j;
				}
			}
		}
	}

	//
	// set the texel hit index
	//
	if( handle != EDITDISPHANDLE_INVALID )
	{
		CMapDisp *pDisp = EditDispMgr()->GetDisp( handle );
		pDisp->SetTexelHitIndex( minIndex );
	}

	return handle;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CToolDisplace::RayAABBTest( CMapDisp *pDisp, const Vector &rayStart, const Vector &rayEnd )
{
	//
	// make planes
	//
	PLANE planes[6];
	Vector boxMin, boxMax;
	pDisp->GetBoundingBox( boxMin, boxMax );
	BuildParallelepiped( boxMin, boxMax, planes );

	bool bCollide = false;
	for( int planeIndex = 0; planeIndex < 6; planeIndex++ )
	{
		bCollide = RayPlaneTest( &planes[planeIndex], rayStart, rayEnd );
		if( !bCollide )
			return false;
	}

	return true;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CToolDisplace::BuildParallelepiped( const Vector &boxMin, const Vector &boxMax,
							             PLANE planes[6] )
{
	int planeIndex = 0;
	for( int axis = 0; axis < 3; axis++ )
	{
		for( int direction = -1; direction < 2; direction += 2 )
		{
			// clear the current plane info
			VectorClear( planes[planeIndex].normal );
			planes[planeIndex].normal[axis] = direction;
			if( direction == 1 )
			{
				planes[planeIndex].dist = boxMax[axis];
			}
			else
			{
				planes[planeIndex].dist = -boxMin[axis];
			}

			planeIndex++;
		}
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CToolDisplace::RayPlaneTest( PLANE *pPlane, const Vector& rayStart, const Vector& rayEnd /*, float *fraction*/ )
{
	//
	// get the distances both trace start and end from the bloated plane
	//
	float distStart = DotProduct( rayStart, pPlane->normal ) - pPlane->dist;
	float distEnd = DotProduct( rayEnd, pPlane->normal ) - pPlane->dist;

	//
	// no collision - both points are in front or behind of the given plane
	//
	if( ( distStart > 0.0f ) && ( distEnd > 0.0f ) )
		return false;

	if( ( distStart > 0.0f ) && ( distEnd > 0.0f ) )
		return false;

	// calculate the parameterized "t" component along the ray
	//*fraction = distStart / ( distStart - distEnd );

	// collision
	return true;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
float CToolDisplace::DistFromPointToRay( const Vector& rayStart, const Vector& rayEnd,
										 const Vector& point )
{
	//
	// calculate the ray
	//
	Vector ray;
	VectorSubtract( rayEnd, rayStart, ray );
	VectorNormalize( ray );

	//
	// get a ray to point
	//
	Vector seg;
	VectorSubtract( point, rayStart, seg );

	//
	// project point segment onto ray - get perpendicular point
	//
	float value = DotProduct( ray, seg );
	VectorScale( ray, value, ray );
	VectorAdd( rayStart, ray, ray );

	//
	// find the distance between the perpendicular point and point
	//
	VectorSubtract( ray, point, seg );
	float dist = VectorLength( seg );

	return dist;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CToolDisplace::AddFiltersToManagers( void )
{
	int count = m_FilterLoaderMgr.GetFilterCount();
	for( int ndxFilter = 0; ndxFilter < count; ndxFilter++ )
	{
		CDispMapImageFilter *pFilter = m_FilterLoaderMgr.GetFilter( ndxFilter );
		if( pFilter )
		{
			switch( pFilter->m_Type )
			{
			case DISPPAINT_EFFECT_RAISELOWER:
				{
					m_FilterRaiseLowerMgr.Add( pFilter );
					break;
				}
			case DISPPAINT_EFFECT_RAISETO:
				{
					m_FilterRaiseToMgr.Add( pFilter );
					break;
				}
			case DISPPAINT_EFFECT_SMOOTH:
				{
					m_FilterSmoothMgr.Add( pFilter );
					break;
				}
			}
		}
	}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CToolDisplace::LoadFilters( const char *filename )
{
	//
	// Open the file.
	//
	CChunkFile File;
	ChunkFileResult_t eResult = File.Open( filename, ChunkFile_Read );
	
	if( eResult != ChunkFile_Ok )
	{
		Msg( mwError, "Couldn't load filter file %s!\n", filename );
	}

	//
	// Read the file.
	//
	if (eResult == ChunkFile_Ok)
	{
		//
		// Set up handlers for the subchunks that we are interested in.
		//
		CChunkHandlerMap Handlers;
		Handlers.AddHandler( "Filter", ( ChunkHandler_t )CToolDisplace::LoadFiltersCallback, this );
		File.PushHandlers( &Handlers );

		//
		// Read the sub-chunks. We ignore keys in the root of the file, so we don't pass a
		// key value callback to ReadChunk.
		//
		while (eResult == ChunkFile_Ok)
		{
			eResult = File.ReadChunk();
		}

		if (eResult == ChunkFile_EOF)
		{
			eResult = ChunkFile_Ok;
		}

		File.PopHandlers();
	}

	return( eResult == ChunkFile_Ok );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
ChunkFileResult_t CToolDisplace::LoadFiltersCallback( CChunkFile *pFile, CToolDisplace *pDisplaceTool )
{
	//
	// allocate a new filter
	//
	CDispMapImageFilter *pFilter = pDisplaceTool->m_FilterLoaderMgr.Create();
	if( !pFilter )
		return ChunkFile_Fail;

	// load the filter data
	ChunkFileResult_t eResult = pFilter->LoadFilter( pFile );
	return( eResult );	
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pRender - 
//			*pTool - 
//-----------------------------------------------------------------------------
void CToolDisplace::RenderPaintSphere( CRender3D *pRender )
{
	CMapDisp *pDisp = GetEditDisp();
	if (pDisp == NULL)
		return;
	
	// Get the sphere center.
	int iHit = pDisp->GetTexelHitIndex();
	if ( iHit == -1 )
		return;

	// Get the sphere center and radius.
	Vector vCenter;
	pDisp->GetVert( iHit, vCenter );
	float flRadius = GetSpatialRadius();

	int size = ( int )( flRadius * 0.05f );
	if ( size < 6 ) { size = 6; }
	if ( size > 12 ) { size = 12; }

	// Render the sphere.
	if ( !IsNudging() )
	{
		pRender->RenderWireframeSphere( vCenter, flRadius, size, size, 0, 255, 0 );
	}
	else
	{
		pRender->RenderWireframeSphere( vCenter, flRadius, size, size, 255, 255, 0 );
	}

	// Render the displacement axis (as an arrow).
	int nPaintAxis;
	Vector vPaintAxis;
	GetPaintAxis( nPaintAxis, vPaintAxis );
	if( nPaintAxis == DISPPAINT_AXIS_SUBDIV )
	{
		pDisp->GetSubdivNormal( iHit, vPaintAxis );
	}
	float flBloat = flRadius * 0.15f;
	pRender->RenderArrow( vCenter, vCenter + ( vPaintAxis * ( flRadius + flBloat ) ), 255, 255, 0 );

	// Render cube at center point.
 	Vector	vBoxMin, vBoxMax;
	for ( int iAxis = 0; iAxis < 3; iAxis++ )
	{
		vBoxMin[iAxis] = vCenter[iAxis] - ( flBloat * 0.25f );
		vBoxMax[iAxis] = vCenter[iAxis] + ( flBloat * 0.25f );
	}
	pRender->RenderBox( vBoxMin, vBoxMax, 255, 255, 0, SELECT_NONE );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pRender - 
//			bNudge - 
//-----------------------------------------------------------------------------
void CToolDisplace::RenderHitBox( CRender3D *pRender )
{
	CMapDisp *pDisp = GetEditDisp();
	if (pDisp == NULL)
		return;

    //
    // get selection
    //
    int index = pDisp->GetTexelHitIndex();
    if( index == -1 )
        return;

    //
    // get the displacement map width and height
    //
    int width = pDisp->GetWidth();
    int height = pDisp->GetHeight();

	Vector seg[2];
	Vector points[2];

	pDisp->GetVert( 0, points[0] );
	pDisp->GetVert( ( width - 1 ), points[1] );
	VectorSubtract( points[1], points[0], seg[0] );
	pDisp->GetVert( ( ( width - 1 ) * height ), points[1] );
	VectorSubtract( points[1], points[0], seg[1] );

	VectorAdd( seg[0], seg[1], seg[0] );
	VectorScale( seg[0], 0.5f, seg[0] );
    
    //
    // determine a good size to make the "box" surrounding the selected point
    //
	float length = VectorLength( seg[0] );
    length *= 0.025f;

    //
    // render the box
    //
	pDisp->GetVert( index, points[0] );

 	Vector	minb, maxb;
    minb[0] = points[0][0] - length;
    minb[1] = points[0][1] - length;
    minb[2] = points[0][2] - length;

    maxb[0] = points[0][0] + length;
    maxb[1] = points[0][1] + length;
    maxb[2] = points[0][2] + length;

	if( !IsNudging() )
	{
	    pRender->RenderWireframeBox( minb, maxb, 0, 255, 0 );
	}
	else
	{
	    pRender->RenderWireframeBox( minb, maxb, 255, 255, 0 );
	}

	//
	// render the normal
	//
	// get hit box origin
	Vector hbOrigin;
	pDisp->GetVert( index, hbOrigin );

	// get 4x length
	float length4 = length * 4.0f;

	int paintAxis;
	Vector vecPaint;
	GetPaintAxis( paintAxis, vecPaint );
	if( paintAxis == DISPPAINT_AXIS_SUBDIV )
	{
		pDisp->GetSubdivNormal( index, vecPaint );
	}

	//
	// render the normal -- just a yellow line at this point
	//
	pRender->RenderArrow( hbOrigin, hbOrigin + ( vecPaint * length4 ), 255, 255, 0 );
#if 0
	CMeshBuilder meshBuilder;
	IMesh *pMesh = MaterialSystemInterface()->GetDynamicMesh();
	
	meshBuilder.Begin( pMesh, MATERIAL_LINES, 1 );
	meshBuilder.Position3f( hbOrigin.x, hbOrigin.y, hbOrigin.z );
	meshBuilder.Color3ub( 255, 255, 0 );
	meshBuilder.AdvanceVertex();
	meshBuilder.Position3f( hbOrigin.x + ( normal.x * length4 ), 
		                    hbOrigin.y + ( normal.y * length4 ), 
							hbOrigin.z + ( normal.z * length4 ) );
	meshBuilder.Color3ub( 255, 255, 0 );
	meshBuilder.AdvanceVertex();
	meshBuilder.End();
	
	pMesh->Draw();
#endif
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pRender - 
//-----------------------------------------------------------------------------
void CToolDisplace::RenderTool3D(CRender3D *pRender)
{
	unsigned int uiTool = GetTool();
	if ( uiTool == DISPTOOL_PAINT )
	{
		if ( IsSpatialPainting() )
		{
			RenderPaintSphere( pRender );
		}
		else
		{
			RenderHitBox( pRender );
		}
	}
}
