//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Rendering and mouse handling in the logical view.
//
//===========================================================================//

#include "stdafx.h"
#include "MapViewLogical.h"
#include "Render2D.h"
#include "MapWorld.h"
#include "TitleWnd.h"
#include "MapDoc.h"
#include "ToolManager.h"
#include "history.h"

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


IMPLEMENT_DYNCREATE(CMapViewLogical, CMapView2DBase)


BEGIN_MESSAGE_MAP(CMapViewLogical, CMapView2DBase)
	//{{AFX_MSG_MAP(CMapViewLogical)
	ON_WM_TIMER()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


//-----------------------------------------------------------------------------
// Purpose: Logical View Look and feel constants
//-----------------------------------------------------------------------------

#define LOGICAL_CONN_VERT_SPACING 100
#define LOGICAL_CONN_HORIZ_SPACING 20
#define LOGICAL_CONN_SPREAD_DIST 50
#define LOGICAL_CONN_TEXT_LENGTH 450
#define LOGICAL_CONN_CROSS_SIZE 30
#define LOGICAL_CONN_MULTI_CIRCLE_RADIUS 15

// Broken connection blinking interval (in ms)
#define TIMER_BLINK_INTERVAL	512

// Unselected and selected color values for the connections palette against the background color

#define DARK	112
#define	MID		144

#define BACKGROUND	168

#define LITE	224
#define BRITE	255

#define LOGICAL_CONN_COLOR_COUNT		7
#define LOGICAL_CONN_SELECTION_STATES	2

static color32 s_pWireColors[LOGICAL_CONN_COLOR_COUNT][LOGICAL_CONN_SELECTION_STATES] = 
{
	{ { MID, MID, 0,	255 },		/* Mid Yellow */	{ BRITE, BRITE, 0,	255 },		/* Bright Yellow  */	},
	{ { MID, DARK, 0,	255 },		/* Dark Orange */	{ BRITE, MID, 0,	255 },		/* Bright Orange */		},
	{ { 0, DARK, 0,		255 },		/* Dark Green */	{ 0, BRITE, 0,		255 },		/* Bright Green */		},
	{ { 0, MID, MID,	255 },		/* Mid Cyan */		{ 0, BRITE, BRITE,	255 },		/* Bright Cyan */		},
	{ { 0, 0, MID,		255 },		/* Mid Blue */		{ 0, MID, BRITE,	255 },		/* Bright Baby Blue */	},
	{ { MID, 0, MID,	255 },		/* Mid Magenta */	{ BRITE, 0, BRITE,	255 },		/* Bright Magenta */	},
	{ { DARK, DARK, DARK, 255 },	/* Dark Gray */		{ BRITE, BRITE, BRITE, 255 },	/* Bright White */		},
};

static color32 s_pBrokenWireColor[LOGICAL_CONN_SELECTION_STATES] =
{
	{ DARK, 0, 0, 255 },			/* Dark Red */		{ BRITE, 0, 0, 255 },			/* Bright Red */	
};

//-----------------------------------------------------------------------------
// Purpose: Constructor. Initializes data members.
//	---------------------------------------------------------------------------
CMapViewLogical::CMapViewLogical(void) : m_RenderDict( 0, 1024, DefLessFunc( CMapClass* ) )
{
	m_bUpdateRenderObjects = true;
	SetAxes(AXIS_X, FALSE, AXIS_Y, TRUE);
	SetDrawType( VIEW_LOGICAL );
}


//-----------------------------------------------------------------------------
// Purpose: Destructor. Frees dynamically allocated resources.
//-----------------------------------------------------------------------------
CMapViewLogical::~CMapViewLogical(void)
{
}


//-----------------------------------------------------------------------------
// Purpose: First-time initialization of this view.
//-----------------------------------------------------------------------------
void CMapViewLogical::OnInitialUpdate(void)
{
	CreateTitleWindow();
	GetTitleWnd()->SetTitle("Logical");
	SetZoom(0);  // Zoom out as far as possible.
	UpdateClientView();
	CMapView2DBase::OnInitialUpdate();
	// FIXME: Hardcoded light gray background - should be from a new "Logical View" options settings dialog
	m_ClearColor.SetColor( BACKGROUND, BACKGROUND, BACKGROUND, 255 );  
	m_clrGrid.SetColor( MID, MID, MID, 255 );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : point - Point in client coordinates.
//			bMakeFirst - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CMapViewLogical::SelectAtCascading( const Vector2D &ptClient, bool bMakeFirst )
{
	CMapDoc *pDoc = GetMapDoc();
	CSelection *pSelection = pDoc->GetSelection();

	pSelection->ClearHitList();

	GetHistory()->MarkUndoPosition(pSelection->GetList(), "Selection");

	//
	// Check all the objects in the world for a hit at this point.
	//

	HitInfo_t HitData[MAX_PICK_HITS];
	int nHits = ObjectsAt(ptClient, HitData, MAX_PICK_HITS);
	
	// If there were no hits at the given point, clear selection.
	if ( nHits == 0 )
	{
		if (bMakeFirst)
		{
			pDoc->SelectFace(NULL, 0, scClear|scSaveChanges);
			pDoc->SelectObject(NULL, scClear|scSaveChanges);
		}

		return false;
	}

	SelectMode_t eSelectMode = pSelection->GetMode();

	for ( int i=0; i<nHits; ++i )
	{
		CMapClass *pSelObject = HitData[i].pObject->PrepareSelection( eSelectMode );
		if ( !pSelObject )
			continue;
		
		pSelection->AddHit( pSelObject );
	}

	//
	// Select a single object.
	//
	if ( bMakeFirst )
	{
		pDoc->SelectFace( NULL, 0, scClear|scSaveChanges );
		pDoc->SelectObject( NULL, scClear|scSaveChanges );
	}

	pSelection->SetCurrentHit( hitFirst, true );

	return true;
}


//-----------------------------------------------------------------------------
// Base class calls this when render lists need rebuilding
//-----------------------------------------------------------------------------
void CMapViewLogical::OnRenderListDirty()
{
	m_bUpdateRenderObjects = true;
}

	
//-----------------------------------------------------------------------------
// Purpose: Builds up list of mapclasses to render
//-----------------------------------------------------------------------------
void CMapViewLogical::AddToRenderLists( CMapClass *pObject )
{
#if _DEBUG && 0
	CMapEntity	*pEntity = dynamic_cast<CMapEntity *>(pObject);
	if (pEntity)
	{
		LPCTSTR	pszTargetName = pEntity->GetKeyValue("targetname");
		if ( pszTargetName && !strcmp(pszTargetName, "relay_cancelVCDs") )
		{
			// Set breakpoint here for debugging this entity's visiblity
			int foo = 0;
		}
	}
#endif

	if ( !pObject->IsVisibleLogical() )
		return;
	
	// Don't render groups, render their children instead.
	if ( !pObject->IsGroup() && pObject->IsLogical() )
	{
		Vector2D vecMins, vecMaxs;
		pObject->GetRenderLogicalBox( vecMins, vecMaxs );

// Always paint all the entities to ensure that any inter-entity connections are visible
//		if ( !IsValidBox( vecMins, vecMaxs ) || IsInClientView( vecMins, vecMaxs ) )
		{
			// Make sure the object is in the update region.
			m_RenderList.AddToTail( pObject );
			m_ConnectionList.AddToTail( pObject );
			m_RenderDict.Insert( pObject );
		}
	}

	// Recurse into children and add them.
	const CMapObjectList *pChildren = pObject->GetChildren();
	FOR_EACH_OBJ( *pChildren, pos )
	{
		AddToRenderLists( pChildren->Element(pos) );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Builds up list of mapclasses to render
//-----------------------------------------------------------------------------
void CMapViewLogical::PopulateConnectionList( )
{			  
	while ( m_ConnectionUpdate.Count() )
	{
		CMapClass *pObject;
		m_ConnectionUpdate.Pop( pObject );
		if ( !pObject->IsVisibleLogical() )
			continue;

		// Don't render groups, render their children instead.
		if ( !pObject->IsGroup() && pObject->IsLogical() )
		{
			// Don't add it if it's visible already
			if ( m_RenderDict.Find( pObject ) == m_RenderDict.InvalidIndex() )
			{
				CEditGameClass *pClass = dynamic_cast< CEditGameClass * >( pObject );
				if ( pClass )
				{
					int nCount = pClass->Connections_GetCount();
					for ( int i = 0; i < nCount; ++i )
					{
						CEntityConnection *pConn = pClass->Connections_Get( i );
						 
						// Find the input entity associated with this connection
						CMapEntityList *pEntityList = pConn->GetTargetEntityList();

						int j;
						int nInputCount = pEntityList->Count();
						for ( j = 0; j < nInputCount; ++j )
						{
							CMapEntity *pEntity = pEntityList->Element(j);
							if ( m_RenderDict.Find( pEntity ) != m_RenderDict.InvalidIndex() )
							{ 
								m_ConnectionList.AddToTail( pObject );
								break;
							}
						}
						if ( j != nInputCount )
							break;
					}
				}
			}
		}

		// Recurse into children and add them.
		const CMapObjectList *pChildren = pObject->GetChildren();
		FOR_EACH_OBJ( *pChildren, pos )
		{
			m_ConnectionUpdate.Push( pChildren->Element(pos) );
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : nIDEvent - 
//-----------------------------------------------------------------------------
void CMapViewLogical::OnTimer(UINT nIDEvent) 
{
	if ( nIDEvent == TIMER_CONNECTIONUPDATE )
	{
		// Make sure we don't blink too fast
		static unsigned int	nLastUpdate = 0;
		if ( GetTickCount() >= (nLastUpdate+TIMER_BLINK_INTERVAL/2) )
		{
			nLastUpdate = GetTickCount();
            UpdateView( 0 ); // Force the view to redraw for blinking errors
		}
	}
	else
		CView::OnTimer(nIDEvent);
}


const color32 & CMapViewLogical::GetWireColor(const char *pszName, const bool bSelected, const bool bError, const bool bAnySelected)
{
	// Select the connecting color based on the string passed in 
	// (using OutputName for instance, gives varying "wire colors" by entity output type).

	Assert( LOGICAL_CONN_COLOR_COUNT == (sizeof(s_pWireColors) / sizeof(color32) ) / LOGICAL_CONN_SELECTION_STATES );

	if ( !bError )
	{

		int nIndex = 0;

		// Has the named passed in
		while ( *pszName )
			nIndex += *pszName++;

		// Index based on the number of colors available
		nIndex %= LOGICAL_CONN_COLOR_COUNT;

		// Only blink non-errors if the drawing object is selected 
//		bool bBlinkingState = bSelected ? (GetTickCount() / TIMER_BLINK_INTERVAL) & 1 : 0;
		return s_pWireColors[nIndex][bSelected];
	}
	else
	{
		// Only blink errors if nothing is selected, or this is selected
		bool	bBlinkingState =  bSelected || !bAnySelected ? (GetTickCount() / TIMER_BLINK_INTERVAL) & 1 : bSelected;
		return s_pBrokenWireColor[bBlinkingState];
	}
}


//-----------------------------------------------------------------------------
// Draws a wire from a particular point to a target
//-----------------------------------------------------------------------------
#define BACKWARD_WIRE_OVERSHOOT 50
#define BACKWARD_WIRE_Y_DISTANCE 150

void CMapViewLogical::DrawConnectingWire( float x, float y, CMapEntity *pSource, CEntityConnection *pConnection, CMapEntity *pTarget )
{
	CRender2D *pRender = GetRender();
	  
	// FIXME: Deal with bad input type
	Vector2D vecEndPosition, vecConnector;
	pTarget->GetLogicalConnectionPosition( LOGICAL_CONNECTION_INPUT, vecConnector );
	vecEndPosition = vecConnector;

	int		nInputs = pTarget->Upstream_GetCount();

	// Compensate for multiple inputs -- fan-in the connections from the various pSource entities
	BOOL bFound = false;
	if ( nInputs )
	{
		int nInput;
		for ( nInput = 0; nInput < nInputs; nInput++ )
		{
			CEntityConnection *pInputConnection = pTarget->Upstream_Get(nInput);
			if ( pInputConnection )
			{
				if ( pInputConnection == pConnection )
				{
					bFound = true;
					break;
				}
			}
		}
		if (bFound)
		{
			vecEndPosition.x -= LOGICAL_CONN_SPREAD_DIST;
			vecEndPosition.y += ( (nInputs - 1) * LOGICAL_CONN_VERT_SPACING / 2 ) / 2 - (nInput * LOGICAL_CONN_VERT_SPACING / 2);

			pRender->MoveTo( Vector( vecEndPosition.x, vecEndPosition.y, 0.0f) );
			pRender->DrawLineTo( Vector( vecConnector.x, vecConnector.y, 0.0f) );
		}
		else
		{
			Assert(0);
		}
	}

	pRender->MoveTo( Vector( x, y, 0.0f ) );
	if ( x < vecEndPosition.x )
	{
		// Do direct connection
		pRender->DrawLineTo( Vector( x, vecEndPosition.y, 0.0f ) );
		pRender->DrawLineTo( Vector( vecEndPosition.x, vecEndPosition.y, 0.0f ) );
		return;
	}

	Vector2D vecTargetMins, vecTargetMaxs;
	pTarget->GetRenderLogicalBox( vecTargetMins, vecTargetMaxs );
	vecTargetMins.y -= BACKWARD_WIRE_Y_DISTANCE;
	vecTargetMaxs.y += BACKWARD_WIRE_Y_DISTANCE;

	float flHalfY = ( y + vecEndPosition.y ) * 0.5f;
	if ( flHalfY > vecTargetMins.y && flHalfY < vecTargetMaxs.y )
	{
		flHalfY = ( flHalfY < vecEndPosition.y ) ? vecTargetMins.y : vecTargetMaxs.y; 
	}

	pRender->DrawLineTo( Vector( x, flHalfY, 0.0f ) );
	pRender->DrawLineTo( Vector( vecEndPosition.x - BACKWARD_WIRE_OVERSHOOT, flHalfY, 0.0f ) );
	pRender->DrawLineTo( Vector( vecEndPosition.x - BACKWARD_WIRE_OVERSHOOT, vecEndPosition.y, 0.0f ) );
	pRender->DrawLineTo( Vector( vecEndPosition.x, vecEndPosition.y, 0.0f ) );
}

void CMapViewLogical::RenderConnections(const bool bDrawSelected, const bool bAnySelected)
{
	Vector2D pt, pt2;
	WorldToClient( pt, Vector( 0.0f, 0.0f, 0.0f ) );
	WorldToClient( pt2, Vector( 0.0f, LOGICAL_CONN_VERT_SPACING, 0.0f ) );
	
	int nCount = m_ConnectionList.Count();

	for ( int i = 0; i < nCount ; ++i )
	{
		CMapEntity *pMapClass = dynamic_cast<CMapEntity*>( m_ConnectionList[i] );
		if ( !pMapClass )
			continue;

		CEditGameClass *pEditClass = dynamic_cast<CEditGameClass*>( pMapClass );
		if ( !pEditClass )
			continue;

		int nConnectionCount = pEditClass->Connections_GetCount();
		if ( nConnectionCount == 0 )
			continue;

		Vector2D vecStartPosition;
		pMapClass->GetLogicalConnectionPosition( LOGICAL_CONNECTION_OUTPUT, vecStartPosition );

		CRender2D *pRender = GetRender();

		float x = vecStartPosition.x + LOGICAL_CONN_SPREAD_DIST + LOGICAL_CONN_TEXT_LENGTH;
		float y = vecStartPosition.y + ( nConnectionCount - 1 ) * LOGICAL_CONN_VERT_SPACING / 2;

		for ( int j = 0; j < nConnectionCount; ++j )
		{
			CEntityConnection *pConn = pEditClass->Connections_Get( j );

			// Find the input entity associated with this connection
			CMapEntityList *pEntityList = pConn->GetTargetEntityList();
			int nInputCount = pEntityList->Count();
			bool bBadInput = !MapEntityList_HasInput( pEntityList, pConn->GetInputName() );
			bool bBadConnection = (nInputCount == 0);

			// Stop drawing entity connection text once the entity itself gets too small.
			bool bDrawOutput = ( fabs( pt.y - pt2.y ) >= 16 );
			bool bDrawDelay = ( fabs( pt.y - pt2.y ) >= 20 );
			bool bDrawInput = ( fabs( pt.y - pt2.y ) >= 24 );
			bool bDrawTarget = ( fabs( pt.y - pt2.y ) >= 28 );
	  
			bool bEntitySelected = ( pMapClass->GetSelectionState() != SELECT_NONE );
			bool bInputSelected = false;

			for ( int k = 0; k < nInputCount; ++k )
			{
				if ( pEntityList->Element( k )->GetSelectionState() != SELECT_NONE )
					bInputSelected = true;

				// Make sure all the connected entities are all visible
				if ( pEntityList->Element( k )->IsVisibleLogical() == false )
					bBadConnection = true;
			}

			// Make sure we only draw the selected OR unselected connections as requested
			if ( bDrawSelected == (bEntitySelected || bInputSelected) )
			{
				color32 c = GetWireColor( pConn->GetOutputName(), 
										  bEntitySelected || bInputSelected, 
										  bBadConnection || bBadInput, 
										  bAnySelected );

				if ( bDrawDelay || bDrawOutput || bDrawInput || bDrawTarget )
				{
					char pBuf[1024];
					
 					pRender->SetTextColor( c.r, c.g, c.b );

					int nChars = 0;
					if ( bDrawOutput )
						nChars += Q_snprintf( pBuf+nChars, sizeof(pBuf), "%s", pConn->GetOutputName() );
					if ( bDrawDelay )
						nChars += Q_snprintf( pBuf+nChars, sizeof(pBuf), "(%.2f)", pConn->GetDelay() );

					if ( nChars )
						pRender->DrawText( pBuf, Vector2D( vecStartPosition.x + LOGICAL_CONN_SPREAD_DIST, y ), 2, 1, CRender2D::TEXT_JUSTIFY_TOP | CRender2D::TEXT_JUSTIFY_RIGHT );
					
					nChars = 0;
					if ( bDrawInput )
						nChars += Q_snprintf( pBuf+nChars, sizeof(pBuf), "%s", pConn->GetInputName() );
					if ( bDrawTarget )
						nChars += Q_snprintf( pBuf+nChars, sizeof(pBuf), "[%s] ", pConn->GetTargetName() );

					if ( nChars )
						pRender->DrawText( pBuf, Vector2D( vecStartPosition.x + LOGICAL_CONN_SPREAD_DIST, y ), 2, -1, CRender2D::TEXT_JUSTIFY_BOTTOM | CRender2D::TEXT_JUSTIFY_RIGHT );
				}

				pRender->SetDrawColor( c.r, c.g, c.b );
				pRender->MoveTo( Vector( vecStartPosition.x, vecStartPosition.y, 0.0f ) );
				pRender->DrawLineTo( Vector( vecStartPosition.x + LOGICAL_CONN_SPREAD_DIST, y, 0.0f ) );
				pRender->DrawLineTo( Vector( x, y, 0.0f ) );
			    
				if ( bBadConnection )
				{
					// Draw an X for a bogus connection.
 					pRender->MoveTo( Vector( x - LOGICAL_CONN_CROSS_SIZE, y - LOGICAL_CONN_CROSS_SIZE, 0.0f ) );
					pRender->DrawLineTo( Vector( x + LOGICAL_CONN_CROSS_SIZE, y + LOGICAL_CONN_CROSS_SIZE, 0.0f ) );
					pRender->MoveTo( Vector( x - LOGICAL_CONN_CROSS_SIZE, y + LOGICAL_CONN_CROSS_SIZE, 0.0f ) );
					pRender->DrawLineTo( Vector( x + LOGICAL_CONN_CROSS_SIZE, y - LOGICAL_CONN_CROSS_SIZE, 0.0f ) );
				}
				else if ( nInputCount == 1 )
				{
					DrawConnectingWire( x, y, pMapClass, pConn, pEntityList->Element( 0 ) );
				}
				else
				{
					// Draw a circle for multiple connections
					pRender->DrawCircle( Vector( x + LOGICAL_CONN_MULTI_CIRCLE_RADIUS, y, 0.0f ), LOGICAL_CONN_MULTI_CIRCLE_RADIUS );

					float mx = x + LOGICAL_CONN_SPREAD_DIST;
					float my = y + ( nInputCount / 2 ) * LOGICAL_CONN_VERT_SPACING/2;

					Vector vecStart( x + LOGICAL_CONN_MULTI_CIRCLE_RADIUS, y, 0.0f );
					for ( int k = 0; k < nInputCount; ++k )
					{ 
						// bBadInput = false; // This should be based on whether downstream entity has the specificied named input
						bInputSelected = ( pEntityList->Element( k )->GetSelectionState() != SELECT_NONE );
						color32 col = GetWireColor( pConn->GetOutputName(), 
												  bEntitySelected || bInputSelected,
												  bBadInput,
												  bAnySelected );

						pRender->SetDrawColor( col.r, col.g, col.b );

						Vector vecEnd( mx, my, 0.0f );
						Vector vecDelta;
						VectorSubtract( vecEnd, vecStart, vecDelta );
						VectorNormalize( vecDelta );
						vecDelta *= LOGICAL_CONN_MULTI_CIRCLE_RADIUS;
						vecDelta += vecStart;

						pRender->MoveTo( vecDelta );
						pRender->DrawLineTo( Vector( mx, my, 0.0f ) );
						DrawConnectingWire( mx, my, pMapClass, pConn, pEntityList->Element( k ) );

						mx += LOGICAL_CONN_HORIZ_SPACING;
						my -= LOGICAL_CONN_VERT_SPACING/2;
					}
				}
			}

			x += LOGICAL_CONN_HORIZ_SPACING * (nInputCount+1) + 2*LOGICAL_CONN_MULTI_CIRCLE_RADIUS;
			y -= LOGICAL_CONN_VERT_SPACING;
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CMapViewLogical::Render()
{
	CMapDoc *pDoc = GetMapDoc();
	CMapWorld *pWorld = pDoc->GetMapWorld();

	GetRender()->StartRenderFrame();
	
	// Draw grid if enabled.
	if ( pDoc->m_bShowLogicalGrid )
	{
		DrawGridLogical( GetRender() );
	}

	// Draw the world if we have one.
	if (pWorld == NULL)
		return;
			   
	// Traverse the entire world, sorting visible elements into two arrays:
	// Normal objects and selected objects, so that we can render the selected
	// objects last.
	if ( m_bUpdateRenderObjects )
	{
		m_bUpdateRenderObjects = false;

		m_RenderList.RemoveAll();
		m_RenderDict.RemoveAll();
		m_ConnectionList.RemoveAll();
		m_ConnectionUpdate.Clear();

		// fill render lists with visible objects
		AddToRenderLists( pWorld );

		// Add the connections too
		m_ConnectionUpdate.Push( pWorld );
		PopulateConnectionList();

		// Make sure we have a timer running to drive error animations
		SetTimer( TIMER_CONNECTIONUPDATE, TIMER_BLINK_INTERVAL, NULL);
	}

	// Assume we are blinking, unless something is selected.
	bool bAnySelected = FALSE;

	CUtlVector<CMapClass *> selectedObjects;
	CUtlVector<CMapClass *> helperObjects;
	CUtlVector<CMapClass *> unselectedObjects;

	// Render normal (nonselected) objects first
	for (int i = 0; i < m_RenderList.Count(); i++)
	{
		CMapClass *pObject = m_RenderList[i];

		if ( pObject->IsSelected() )
		{
			bAnySelected = TRUE;

			// render later
			if ( pObject->GetToolObject( 0, false ) )
			{
				helperObjects.AddToTail( pObject );
			}
			else
			{
				selectedObjects.AddToTail( pObject );
			}
		}
		else
		{
			unselectedObjects.AddToTail( pObject );
		}
	}
	
	// Render unselected connections first
	RenderConnections(false, bAnySelected);

	// render unselected objects next, on top of the connections
	for ( int j = 0; j < unselectedObjects.Count(); j++ )
	{
		unselectedObjects[j]->RenderLogical( GetRender() );
	}

	if ( bAnySelected )
	{
		// Render selected objects in second batch, so they overdraw normal object
		for (int i = 0; i < selectedObjects.Count(); i++)
		{
			selectedObjects[i]->RenderLogical( GetRender() );
		}

		// Render selected connections on top of everything else
		RenderConnections(true, bAnySelected);
	}

	// render all tools
	CBaseTool *pCurTool = pDoc->GetTools()->GetActiveTool();

	int nToolCount = pDoc->GetTools()->GetToolCount();
	for (int i = 0; i < nToolCount; i++)
	{
		CBaseTool *pTool = pDoc->GetTools()->GetTool(i);
		if ((pTool != NULL) && (pTool != pCurTool))
		{
			pTool->RenderToolLogical( GetRender() );
		}
	}

	// render active tool over all other tools
	if ( pCurTool )
	{
		pCurTool->RenderToolLogical( GetRender() );
	}

	// render map helpers at last
	for (int i = 0; i < helperObjects.Count(); i++)
	{
		helperObjects[i]->RenderLogical( GetRender() );
	}

	GetRender()->EndRenderFrame();
}


//-----------------------------------------------------------------------------
// convert client view space to map world coordinates (2D versions for convenience) 
//-----------------------------------------------------------------------------
void CMapViewLogical::WorldToClient( Vector2D &ptClient, const Vector2D &vWorld )
{
	Vector vWorld3D( vWorld.x, vWorld.y, 0.0f );
	CMapView2DBase::WorldToClient( ptClient, vWorld3D );
}

void CMapViewLogical::ClientToWorld( Vector2D &vWorld, const Vector2D &vClient )
{
	Vector vWorld3D;
	CMapView2DBase::ClientToWorld( vWorld3D, vClient );
	vWorld.x = vWorld3D.x;
	vWorld.y = vWorld3D.y;
}

void CMapViewLogical::WorldToClient( Vector2D &ptClient, const Vector &vWorld )
{
	CMapView2DBase::WorldToClient( ptClient, vWorld );
}

void CMapViewLogical::ClientToWorld( Vector &vWorld, const Vector2D &vClient )
{
	CMapView2DBase::ClientToWorld( vWorld, vClient );
}