//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//
// nav_generate.cpp
// Auto-generate a Navigation Mesh by sampling the current map
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003

#include "cbase.h"
#include "util_shared.h"
#include "nav_mesh.h"
#include "nav_node.h"
#include "nav_pathfind.h"
#include "viewport_panel_names.h"
//#include "terror/TerrorShared.h"
#include "fmtstr.h"

#ifdef TERROR
#include "func_simpleladder.h"
#endif

// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"


enum { MAX_BLOCKED_AREAS = 256 };
static unsigned int blockedID[ MAX_BLOCKED_AREAS ];
static int blockedIDCount = 0;
static float lastMsgTime = 0.0f;

bool TraceAdjacentNode( int depth, const Vector& start, const Vector& end, trace_t *trace, float zLimit = DeathDrop );
bool StayOnFloor( trace_t *trace, float zLimit = DeathDrop );

ConVar nav_slope_limit( "nav_slope_limit", "0.7", FCVAR_CHEAT, "The ground unit normal's Z component must be greater than this for nav areas to be generated." );
ConVar nav_slope_tolerance( "nav_slope_tolerance", "0.1", FCVAR_CHEAT, "The ground unit normal's Z component must be this close to the nav area's Z component to be generated." );
ConVar nav_displacement_test( "nav_displacement_test", "10000", FCVAR_CHEAT, "Checks for nodes embedded in displacements (useful for in-development maps)" );
ConVar nav_generate_fencetops( "nav_generate_fencetops", "1", FCVAR_CHEAT, "Autogenerate nav areas on fence and obstacle tops" );
ConVar nav_generate_fixup_jump_areas( "nav_generate_fixup_jump_areas", "1", FCVAR_CHEAT, "Convert obsolete jump areas into 2-way connections" );
ConVar nav_generate_jump_connections( "nav_generate_jump_connections", "1", FCVAR_CHEAT, "If disabled, don't generate jump connections from jump areas" );
ConVar nav_generate_incremental_range( "nav_generate_incremental_range", "2000", FCVAR_CHEAT );
ConVar nav_generate_incremental_tolerance( "nav_generate_incremental_tolerance", "0", FCVAR_CHEAT, "Z tolerance for adding new nav areas." );
ConVar nav_area_max_size( "nav_area_max_size", "50", FCVAR_CHEAT, "Max area size created in nav generation" );

// Common bounding box for traces
Vector NavTraceMins( -0.45, -0.45, 0 );
Vector NavTraceMaxs( 0.45, 0.45, HumanCrouchHeight );
bool FindGroundForNode( Vector *pos, Vector *normal );	// find a ground Z for pos that is clear for NavTraceMins -> NavTraceMaxs

const float MaxTraversableHeight = StepHeight;		// max internal obstacle height that can occur between nav nodes and safely disregarded
const float MinObstacleAreaWidth = 10.0f;			// min width of a nav area we will generate on top of an obstacle

//--------------------------------------------------------------------------------------------------------------
/**
 * Shortest path cost, paying attention to "blocked" areas
 */
class ApproachAreaCost
{
public:
	float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator )
	{
		// check if this area is "blocked"
		for( int i=0; i<blockedIDCount; ++i )
		{
			if (area->GetID() == blockedID[i])
			{
				return -1.0f;
			}
		}

		if (fromArea == NULL)
		{
			// first area in path, no cost
			return 0.0f;
		}
		else
		{
			// compute distance traveled along path so far
			float dist;

			if (ladder)
			{
				dist = ladder->m_length;
			}
			else
			{
				dist = (area->GetCenter() - fromArea->GetCenter()).Length();
			}

			float cost = dist + fromArea->GetCostSoFar();

			return cost;
		}
	}
};


//--------------------------------------------------------------------------------------------------------------
/**
 * Start at given position and find first area in given direction
 */
inline CNavArea *findFirstAreaInDirection( const Vector *start, NavDirType dir, float range, float beneathLimit, CBaseEntity *traceIgnore = NULL, Vector *closePos = NULL )
{
	CNavArea *area = NULL;

	Vector pos = *start;

	int end = (int)((range / GenerationStepSize) + 0.5f);

	for( int i=1; i<=end; i++ )
	{
		AddDirectionVector( &pos, dir, GenerationStepSize );

		// make sure we dont look thru the wall
		trace_t result;

		UTIL_TraceHull( *start, pos, NavTraceMins, NavTraceMaxs, TheNavMesh->GetGenerationTraceMask(), traceIgnore, COLLISION_GROUP_NONE, &result );

		if (result.fraction < 1.0f)
			break;

		area = TheNavMesh->GetNavArea( pos, beneathLimit );
		if (area)
		{
			if (closePos)
			{
				closePos->x = pos.x;
				closePos->y = pos.y;
				closePos->z = area->GetZ( pos.x, pos.y );
			}

			break;
		}
	}

	return area;
}


//--------------------------------------------------------------------------------------------------------------
/**
 * For each ladder in the map, create a navigation representation of it.
 */
void CNavMesh::BuildLadders( void )
{
	// remove any left-over ladders
	DestroyLadders();

#ifdef TERROR
	CFuncSimpleLadder *ladder = NULL;
	while( (ladder = dynamic_cast< CFuncSimpleLadder * >(gEntList.FindEntityByClassname( ladder, "func_simpleladder" ))) != NULL )
	{
		Vector mins, maxs;
		ladder->CollisionProp()->WorldSpaceSurroundingBounds( &mins, &maxs );
		CreateLadder( mins, maxs, 0.0f );
	}
#endif
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Create a navigation representation of a ladder.
 */
void CNavMesh::CreateLadder( const Vector& absMin, const Vector& absMax, float maxHeightAboveTopArea )
{
	CNavLadder *ladder = new CNavLadder;

	// compute top & bottom of ladder
	ladder->m_top.x = (absMin.x + absMax.x) / 2.0f;
	ladder->m_top.y = (absMin.y + absMax.y) / 2.0f;
	ladder->m_top.z = absMax.z;

	ladder->m_bottom.x = ladder->m_top.x;
	ladder->m_bottom.y = ladder->m_top.y;
	ladder->m_bottom.z = absMin.z;

	// determine facing - assumes "normal" runged ladder
	float xSize = absMax.x - absMin.x;
	float ySize = absMax.y - absMin.y;
	trace_t result;
	if (xSize > ySize)
	{
		// ladder is facing north or south - determine which way
		// "pull in" traceline from bottom and top in case ladder abuts floor and/or ceiling
		Vector from = ladder->m_bottom + Vector( 0.0f, GenerationStepSize, GenerationStepSize/2 );
		Vector to = ladder->m_top + Vector( 0.0f, GenerationStepSize, -GenerationStepSize/2 );

		UTIL_TraceLine( from, to, GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );

		if (result.fraction != 1.0f || result.startsolid)
			ladder->SetDir( NORTH );
		else
			ladder->SetDir( SOUTH );

		ladder->m_width = xSize;
	}
	else
	{
		// ladder is facing east or west - determine which way
		Vector from = ladder->m_bottom + Vector( GenerationStepSize, 0.0f, GenerationStepSize/2 );
		Vector to = ladder->m_top + Vector( GenerationStepSize, 0.0f, -GenerationStepSize/2 );

		UTIL_TraceLine( from, to, GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );

		if (result.fraction != 1.0f || result.startsolid)
			ladder->SetDir( WEST );
		else
			ladder->SetDir( EAST );

		ladder->m_width = ySize;
	}

	// adjust top and bottom of ladder to make sure they are reachable
	// (cs_office has a crate right in front of the base of a ladder)
	Vector along = ladder->m_top - ladder->m_bottom;
	float length = along.NormalizeInPlace();
	Vector on, out;
	const float minLadderClearance = 32.0f;

	// adjust bottom to bypass blockages
	const float inc = 10.0f;
	float t;		
	for( t = 0.0f; t <= length; t += inc )
	{
		on = ladder->m_bottom + t * along;

		out = on + ladder->GetNormal() * minLadderClearance;

		UTIL_TraceLine( on, out, GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );

		if (result.fraction == 1.0f && !result.startsolid)
		{
			// found viable ladder bottom
			ladder->m_bottom = on;
			break;
		}
	}

	// adjust top to bypass blockages
	for( t = 0.0f; t <= length; t += inc )
	{
		on = ladder->m_top - t * along;

		out = on + ladder->GetNormal() * minLadderClearance;

		UTIL_TraceLine( on, out, GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );

		if (result.fraction == 1.0f && !result.startsolid)
		{
			// found viable ladder top
			ladder->m_top = on;
			break;
		}
	}

	ladder->m_length = (ladder->m_top - ladder->m_bottom).Length();

	ladder->SetDir( ladder->GetDir() );	// now that we've adjusted the top and bottom, re-check the normal

	ladder->m_bottomArea = NULL;
	ladder->m_topForwardArea = NULL;
	ladder->m_topLeftArea = NULL;
	ladder->m_topRightArea = NULL;
	ladder->m_topBehindArea = NULL;
	ladder->ConnectGeneratedLadder( maxHeightAboveTopArea );

	// add ladder to global list
	m_ladders.AddToTail( ladder );		
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Create a navigation representation of a ladder.
 */
void CNavMesh::CreateLadder( const Vector &top, const Vector &bottom, float width, const Vector2D &ladderDir, float maxHeightAboveTopArea )
{
	CNavLadder *ladder = new CNavLadder;

	ladder->m_top = top;
	ladder->m_bottom = bottom;
	ladder->m_width = width;
	if ( fabs( ladderDir.x ) > fabs( ladderDir.y ) )
	{
		if ( ladderDir.x > 0.0f )
		{
			ladder->SetDir( EAST );
		}
		else
		{
			ladder->SetDir( WEST );
		}
	}
	else
	{
		if ( ladderDir.y > 0.0f )
		{
			ladder->SetDir( SOUTH );
		}
		else
		{
			ladder->SetDir( NORTH );
		}
	}

	// adjust top and bottom of ladder to make sure they are reachable
	// (cs_office has a crate right in front of the base of a ladder)
	Vector along = ladder->m_top - ladder->m_bottom;
	float length = along.NormalizeInPlace();
	Vector on, out;
	const float minLadderClearance = 32.0f;

	// adjust bottom to bypass blockages
	const float inc = 10.0f;
	float t;
	trace_t result;
	for( t = 0.0f; t <= length; t += inc )
	{
		on = ladder->m_bottom + t * along;

		out = on + ladder->GetNormal() * minLadderClearance;

		UTIL_TraceLine( on, out, GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );

		if (result.fraction == 1.0f && !result.startsolid)
		{
			// found viable ladder bottom
			ladder->m_bottom = on;
			break;
		}
	}

	// adjust top to bypass blockages
	for( t = 0.0f; t <= length; t += inc )
	{
		on = ladder->m_top - t * along;

		out = on + ladder->GetNormal() * minLadderClearance;

		UTIL_TraceLine( on, out, GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );

		if (result.fraction == 1.0f && !result.startsolid)
		{
			// found viable ladder top
			ladder->m_top = on;
			break;
		}
	}

	ladder->m_length = (ladder->m_top - ladder->m_bottom).Length();

	ladder->SetDir( ladder->GetDir() );	// now that we've adjusted the top and bottom, re-check the normal

	ladder->m_bottomArea = NULL;
	ladder->m_topForwardArea = NULL;
	ladder->m_topLeftArea = NULL;
	ladder->m_topRightArea = NULL;
	ladder->m_topBehindArea = NULL;
	ladder->ConnectGeneratedLadder( maxHeightAboveTopArea );

	// add ladder to global list
	m_ladders.AddToTail( ladder );
}


//--------------------------------------------------------------------------------------------------------------
void CNavLadder::ConnectGeneratedLadder( float maxHeightAboveTopArea )
{
	const float nearLadderRange = 75.0f;		// 50

	//
	// Find naviagtion area at bottom of ladder
	//

	// get approximate postion of player on ladder
	Vector center = m_bottom + Vector( 0, 0, GenerationStepSize );
	AddDirectionVector( &center, m_dir, HalfHumanWidth );

	m_bottomArea = TheNavMesh->GetNearestNavArea( center, true );
	if (!m_bottomArea)
	{
		DevMsg( "ERROR: Unconnected ladder bottom at ( %g, %g, %g )\n", m_bottom.x, m_bottom.y, m_bottom.z );
	}
	else
	{
		// store reference to ladder in the area
		m_bottomArea->AddLadderUp( this );
	}

	//
	// Find adjacent navigation areas at the top of the ladder
	//

	// get approximate postion of player on ladder
	center = m_top + Vector( 0, 0, GenerationStepSize );
	AddDirectionVector( &center, m_dir, HalfHumanWidth );

	float beneathLimit = MIN( 120.0f, m_top.z - m_bottom.z + HalfHumanWidth );

	// find "ahead" area
	m_topForwardArea = findFirstAreaInDirection( &center, OppositeDirection( m_dir ), nearLadderRange, beneathLimit, NULL );
	if (m_topForwardArea == m_bottomArea)
		m_topForwardArea = NULL;

	// find "left" area
	m_topLeftArea = findFirstAreaInDirection( &center, DirectionLeft( m_dir ), nearLadderRange, beneathLimit, NULL );
	if (m_topLeftArea == m_bottomArea)
		m_topLeftArea = NULL;

	// find "right" area
	m_topRightArea = findFirstAreaInDirection( &center, DirectionRight( m_dir ), nearLadderRange, beneathLimit, NULL );
	if (m_topRightArea == m_bottomArea)
		m_topRightArea = NULL;

	// find "behind" area - must look farther, since ladder is against the wall away from this area
	m_topBehindArea = findFirstAreaInDirection( &center, m_dir, 2.0f*nearLadderRange, beneathLimit, NULL );
	if (m_topBehindArea == m_bottomArea)
		m_topBehindArea = NULL;

	// can't include behind area, since it is not used when going up a ladder
	if (!m_topForwardArea && !m_topLeftArea && !m_topRightArea)
		DevMsg( "ERROR: Unconnected ladder top at ( %g, %g, %g )\n", m_top.x, m_top.y, m_top.z );

	// store reference to ladder in the area(s)
	if (m_topForwardArea)
		m_topForwardArea->AddLadderDown( this );

	if (m_topLeftArea)
		m_topLeftArea->AddLadderDown( this );

	if (m_topRightArea)
		m_topRightArea->AddLadderDown( this );

	if (m_topBehindArea)
	{
		m_topBehindArea->AddLadderDown( this );
		Disconnect( m_topBehindArea );
	}

	// adjust top of ladder to highest connected area
	float topZ = m_bottom.z + 5.0f;
	bool topAdjusted = false;
	CNavArea *topAreaList[4];
	topAreaList[0] = m_topForwardArea;
	topAreaList[1] = m_topLeftArea;
	topAreaList[2] = m_topRightArea;
	topAreaList[3] = m_topBehindArea;

	for( int a=0; a<4; ++a )
	{
		CNavArea *topArea = topAreaList[a];
		if (topArea == NULL)
			continue;

		Vector close;
		topArea->GetClosestPointOnArea( m_top, &close );
		if (topZ < close.z)
		{
			topZ = close.z;
			topAdjusted = true;
		}
	}

	if (topAdjusted)
	{
		if ( maxHeightAboveTopArea > 0.0f )
		{
			m_top.z = MIN( topZ + maxHeightAboveTopArea, m_top.z );
		}
		else
		{
			m_top.z = topZ;	// not manually specifying a top, so snap exactly
		}
	}

	//
	// Determine whether this ladder is "dangling" or not
	// "Dangling" ladders are too high to go up
	//
	if (m_bottomArea)
	{
		Vector bottomSpot;
		m_bottomArea->GetClosestPointOnArea( m_bottom, &bottomSpot );
		if (m_bottom.z - bottomSpot.z > HumanHeight)
		{
			m_bottomArea->Disconnect( this );
		}
	}
}


//--------------------------------------------------------------------------------------------------------
class JumpConnector
{
public:
	bool operator()( CNavArea *jumpArea )
	{
		if ( !nav_generate_jump_connections.GetBool() )
		{
			return true;
		}

		if ( !(jumpArea->GetAttributes() & NAV_MESH_JUMP) )
		{
			return true;
		}

		for ( int i=0; i<NUM_DIRECTIONS; ++i )
		{
			NavDirType incomingDir = (NavDirType)i;
			NavDirType outgoingDir = OppositeDirection( incomingDir );

			const NavConnectVector *incoming = jumpArea->GetIncomingConnections( incomingDir );
			const NavConnectVector *from = jumpArea->GetAdjacentAreas( incomingDir );
			const NavConnectVector *dest = jumpArea->GetAdjacentAreas( outgoingDir );

			TryToConnect( jumpArea, incoming, dest, outgoingDir );
			TryToConnect( jumpArea, from, dest, outgoingDir );
		}

		return true;
	}

private:
	struct Connection
	{
		CNavArea *source;
		CNavArea *dest;
		NavDirType direction;
	};

	void TryToConnect( CNavArea *jumpArea, const NavConnectVector *source, const NavConnectVector *dest, NavDirType outgoingDir )
	{
		FOR_EACH_VEC( (*source), sourceIt )
		{
			CNavArea *sourceArea = const_cast< CNavArea * >( (*source)[ sourceIt ].area );
			if ( !sourceArea->IsConnected( jumpArea, outgoingDir ) )
			{
				continue;
			}

			if ( sourceArea->HasAttributes( NAV_MESH_JUMP ) )
			{
				NavDirType incomingDir = OppositeDirection( outgoingDir );
				const NavConnectVector *in1 = sourceArea->GetIncomingConnections( incomingDir );
				const NavConnectVector *in2 = sourceArea->GetAdjacentAreas( incomingDir );

				TryToConnect( jumpArea, in1, dest, outgoingDir );
				TryToConnect( jumpArea, in2, dest, outgoingDir );

				continue;
			}

			TryToConnect( jumpArea, sourceArea, dest, outgoingDir );
		}
	}

	void TryToConnect( CNavArea *jumpArea, CNavArea *sourceArea, const NavConnectVector *dest, NavDirType outgoingDir )
	{
		FOR_EACH_VEC( (*dest), destIt )
		{
			CNavArea *destArea = const_cast< CNavArea * >( (*dest)[ destIt ].area );
			if ( destArea->HasAttributes( NAV_MESH_JUMP ) )
			{
				// Don't connect areas across 2 jump areas.  This means we'll have some missing links due to sampling errors.
				// This is preferable to generating incorrect links across multiple jump areas, which is far more common.
				continue;
			}

			Vector center;
			float halfWidth;
			sourceArea->ComputePortal( destArea, outgoingDir, &center, &halfWidth );

			// Don't create corner-to-corner connections
			if ( halfWidth <= 0.0f )
			{
				continue;
			}

			Vector dir( vec3_origin );
			AddDirectionVector( &dir, outgoingDir, 5.0f );

			if ( halfWidth > 0.0f )
			{
				Vector sourcePos, destPos;
				sourceArea->GetClosestPointOnArea( center, &sourcePos );
				destArea->GetClosestPointOnArea( center, &destPos );

				// No jumping up from stairs.
				if ( sourceArea->HasAttributes( NAV_MESH_STAIRS ) && sourcePos.z + StepHeight < destPos.z )
				{
					continue;
				}

				if ( (sourcePos-destPos).AsVector2D().IsLengthLessThan( GenerationStepSize * 3 ) )
				{
					sourceArea->ConnectTo( destArea, outgoingDir );
//					DevMsg( "Connected %d->%d via %d (len %f)\n",
//						sourceArea->GetID(), destArea->GetID(), jumpArea->GetID(), sourcePos.DistTo( destPos ) );
				}
			}
		}
	}
};

//--------------------------------------------------------------------------------------------------------------
void CNavMesh::MarkPlayerClipAreas( void )
{
#ifdef TERROR
	FOR_EACH_VEC( TheNavAreas, it )
	{
		TerrorNavArea *area = static_cast< TerrorNavArea * >(TheNavAreas[it]);

		// Trace upward a bit from our center point just colliding wtih PLAYERCLIP to see if we're in one, if we are, mark us as accordingly.
		trace_t trace;
		Vector start = area->GetCenter() + Vector(0.0f, 0.0f, 16.0f );
		Vector end = area->GetCenter() + Vector(0.0f, 0.0f, 32.0f );
		UTIL_TraceHull( start, end, Vector(0,0,0), Vector(0,0,0), CONTENTS_PLAYERCLIP, NULL, &trace);

		if ( trace.fraction < 1.0 )
		{
			area->SetAttributes( area->GetAttributes() | TerrorNavArea::NAV_PLAYERCLIP );
		}
	}
#endif
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Mark all areas that require a jump to get through them.
 * This currently relies on jump areas having extreme slope.
 */
void CNavMesh::MarkJumpAreas( void )
{
	FOR_EACH_VEC( TheNavAreas, it )
	{
		CNavArea *area = TheNavAreas[ it ];
		if ( !area->HasNodes() )
			continue;

		Vector normal, otherNormal;
		area->ComputeNormal( &normal );
		area->ComputeNormal( &otherNormal, true );

		float lowestNormalZ = MIN( normal.z, otherNormal.z );
		if (lowestNormalZ < nav_slope_limit.GetFloat())
		{
			// The area is a jump area, and we don't merge jump areas together
			area->SetAttributes( area->GetAttributes() | NAV_MESH_JUMP | NAV_MESH_NO_MERGE );
		}
		else if ( lowestNormalZ < nav_slope_limit.GetFloat() + nav_slope_tolerance.GetFloat() )
		{
			Vector testPos = area->GetCenter();
			testPos.z += HalfHumanHeight;
			Vector groundNormal;
			float dummy;
			if ( GetSimpleGroundHeight( testPos, &dummy, &groundNormal ) )
			{
				// If the ground normal is divergent from the area's normal, mark it as a jump area - it's not
				// really representative of the ground.
				float deltaNormalZ = fabs( groundNormal.z - lowestNormalZ );
				if ( deltaNormalZ > nav_slope_tolerance.GetFloat() )
				{
					// The area is a jump area, and we don't merge jump areas together
					area->SetAttributes( area->GetAttributes() | NAV_MESH_JUMP | NAV_MESH_NO_MERGE );
				}
			}
		}
	}
}

//--------------------------------------------------------------------------------------------------------------
/**
* Remove all areas marked as jump areas and connect the areas connecting to them 
*
*/
void CNavMesh::StichAndRemoveJumpAreas( void )
{
	// Now, go through and remove jump areas, connecting areas to make up for it
	JumpConnector connector;
	ForAllAreas( connector );
	RemoveJumpAreas();
}


//--------------------------------------------------------------------------------------------------------------
/**
* Adjusts obstacle start and end distances such that obstacle width (end-start) is not less than MinObstacleAreaWidth,
* and end distance is not greater than maxAllowedDist
*/
void AdjustObstacleDistances( float *pObstacleStartDist, float *pObstacleEndDist, float maxAllowedDist )
{
	float obstacleWidth = *pObstacleEndDist - *pObstacleStartDist;
	// is the obstacle width too narrow?
	if ( obstacleWidth < MinObstacleAreaWidth )
	{		
		float halfDelta = ( MinObstacleAreaWidth - obstacleWidth ) /2;
		// move start so it's half of min width from center, but no less than zero
		*pObstacleStartDist = MAX( *pObstacleStartDist - halfDelta, 0 );
		// move end so it's min width from start
		*pObstacleEndDist = *pObstacleStartDist + MinObstacleAreaWidth;

		// if this pushes the end past max allowed distance, pull start and end back so that end is within allowed distance
		if ( *pObstacleEndDist > maxAllowedDist )
		{
			float delta = *pObstacleEndDist - maxAllowedDist;
			*pObstacleStartDist -= delta;
			*pObstacleEndDist -= delta;
		}
	}								
}

//--------------------------------------------------------------------------------------------------------------
/**
* Makes sure tall, slim obstacles like fencetops, railings and narrow walls have nav areas placed on top of them
* to allow climbing & traversal
*/
void CNavMesh::HandleObstacleTopAreas( void )
{
	if ( !nav_generate_fencetops.GetBool() )
		return;

	// For any 1x1 area that is internally blocked by an obstacle, raise it on top of the obstacle and size to fit.
	RaiseAreasWithInternalObstacles();

	// Create new areas as required
	CreateObstacleTopAreas();

	// It's possible for obstacle top areas to wind up overlapping one another, fix any such cases
	RemoveOverlappingObstacleTopAreas();
}

//--------------------------------------------------------------------------------------------------------------
/**
* For any nav area that has internal obstacles between its corners of greater than traversable height,
* raise that nav area to sit at the top of the obstacle, and shrink it to fit the obstacle.  Such nav
* areas are already restricted to be 1x1 so this will only be performed on areas that are already small.
*/
void CNavMesh::RaiseAreasWithInternalObstacles()
{
	// obstacle areas next to stairs are bad - delete them
	CUtlVector< CNavArea * > areasToDelete;

	FOR_EACH_VEC( TheNavAreas, it )
	{
		CNavArea *area = TheNavAreas[ it ];

		// any nav area with internal obstacles will be 1x1 (width and height = GenerationStepSize), so
		// only need to consider areas of that size
		if ( ( area->GetSizeX() != GenerationStepSize ) || (area->GetSizeY() != GenerationStepSize ) )
			continue;

		float obstacleZ[2] = { -FLT_MAX, -FLT_MAX };
		float obstacleZMax = -FLT_MAX;
		NavDirType obstacleDir = NORTH;
		float obstacleStartDist = GenerationStepSize;
		float obstacleEndDist = 0;

		bool isStairNeighbor = false;

		// Look at all 4 directions and determine if there are obstacles in that direction.  Find the direction with the highest obstacle, if any.
		for ( int i = 0; i < NUM_DIRECTIONS; i++ )
		{
			NavDirType dir = (NavDirType) i;

			// For this direction, look at the left and right edges of the nav area relative to this direction and determined if they are both blocked
			// by obstacles.  We only consider this area obstructed if both edges are blocked (e.g. fence runs all the way through it).

			NavCornerType corner[2];
			int iEdgesBlocked = 0;
			corner[0] = (NavCornerType) ( ( i + 3 ) % NUM_CORNERS );					// lower left-hand corner relative to current direction
			corner[1] = (NavCornerType) ( ( i + 2 ) % NUM_CORNERS );				// lower right-hand corner relative to current direction			
			float obstacleZThisDir[2] = { -FLT_MAX, -FLT_MAX };						// absolute Z pos of obstacle for left and right edge in this direction
			float obstacleStartDistThisDir = GenerationStepSize;					// closest obstacle start distance in this direction
			float obstacleEndDistThisDir = 0;										// farthest obstacle end distance in this direction

			// consider left and right edges of nav area relative to current direction
			for ( int iEdge = 0; iEdge < 2; iEdge++ )
			{
				NavCornerType cornerType = corner[iEdge];
				CNavNode *nodeFrom = area->m_node[cornerType];
				if ( nodeFrom )
				{
					// is there an obstacle going from corner to corner along this edge?
					float obstacleHeight = nodeFrom->m_obstacleHeight[dir];
					if ( obstacleHeight > MaxTraversableHeight )
					{
						// yes, this edge is blocked
						iEdgesBlocked++;
						// keep track of obstacle height and start and end distance for this edge
						float obstacleZ = nodeFrom->GetPosition()->z + obstacleHeight;
						if ( obstacleZ > obstacleZThisDir[iEdge] )
						{							
							obstacleZThisDir[iEdge] = obstacleZ;
						}
						obstacleStartDistThisDir = MIN( nodeFrom->m_obstacleStartDist[dir], obstacleStartDistThisDir );
						obstacleEndDistThisDir = MAX( nodeFrom->m_obstacleEndDist[dir], obstacleEndDistThisDir );
					}
				}
			}

			int BlockedEdgeCutoff = 2;
			const NavConnectVector *connections = area->GetAdjacentAreas( dir );
			if ( connections )
			{
				for ( int conIndex=0; conIndex<connections->Count(); ++conIndex )
				{
					const CNavArea *connectedArea = connections->Element( conIndex ).area;
					if ( connectedArea && connectedArea->HasAttributes( NAV_MESH_STAIRS ) )
					{
						isStairNeighbor = true;
						BlockedEdgeCutoff = 1;	// one blocked edge is already too much when we're next to a stair
						break;
					}
				}
			}

			// are both edged blocked in this direction, and is the obstacle height in this direction the tallest we've seen?
			if ( (iEdgesBlocked >= BlockedEdgeCutoff ) && ( MAX( obstacleZThisDir[0], obstacleZThisDir[1] ) ) > obstacleZMax )
			{
				// this is the tallest obstacle we've encountered so far, remember its details
				obstacleZ[0] = obstacleZThisDir[0];
				obstacleZ[1] = obstacleZThisDir[1];
				obstacleZMax = MAX( obstacleZ[0], obstacleZ[1] );
				obstacleDir = dir;
				obstacleStartDist = obstacleStartDistThisDir;
				obstacleEndDist = obstacleStartDistThisDir;
			}
		}

		if ( isStairNeighbor && obstacleZMax > -FLT_MAX )
		{
			areasToDelete.AddToTail( area );
			continue;
		}

		// if we found an obstacle, raise this nav areas and size it to fit
		if ( obstacleZMax > -FLT_MAX )
		{
			// enforce minimum obstacle width so we don't shrink to become a teensy nav area
			AdjustObstacleDistances( &obstacleStartDist, &obstacleEndDist, GenerationStepSize );
			Assert( obstacleEndDist - obstacleStartDist >= MinObstacleAreaWidth );

			// get current corner coords
			Vector corner[4];
			for ( int i = NORTH_WEST; i < NUM_CORNERS; i++ )
			{
				corner[i] = area->GetCorner( (NavCornerType) i );
			}

			// adjust our size to fit the obstacle
			switch ( obstacleDir )
			{
			case NORTH:
				corner[NORTH_WEST].y = corner[SOUTH_WEST].y - obstacleEndDist;
				corner[NORTH_EAST].y = corner[SOUTH_EAST].y - obstacleEndDist;				
				corner[SOUTH_WEST].y -= obstacleStartDist;
				corner[SOUTH_EAST].y -= obstacleStartDist;				
				break;
			case SOUTH:
				corner[SOUTH_WEST].y = corner[NORTH_WEST].y + obstacleEndDist;
				corner[SOUTH_EAST].y = corner[NORTH_EAST].y + obstacleEndDist;
				corner[NORTH_WEST].y += obstacleStartDist;
				corner[NORTH_EAST].y += obstacleStartDist;
				::V_swap( obstacleZ[0], obstacleZ[1] );			// swap left and right Z heights for obstacle so we can run common code below
				break;
			case EAST:
				corner[NORTH_EAST].x = corner[NORTH_WEST].x + obstacleEndDist;
				corner[SOUTH_EAST].x = corner[SOUTH_WEST].x + obstacleEndDist;				
				corner[NORTH_WEST].x += obstacleStartDist;
				corner[SOUTH_WEST].x += obstacleStartDist;
			case WEST:
				corner[NORTH_WEST].x = corner[NORTH_EAST].x - obstacleEndDist;
				corner[SOUTH_WEST].x = corner[SOUTH_EAST].x - obstacleEndDist;				
				corner[NORTH_EAST].x -= obstacleStartDist;
				corner[SOUTH_EAST].x -= obstacleStartDist;
				::V_swap( obstacleZ[0], obstacleZ[1] );			// swap left and right Z heights for obstacle so we can run common code below
				break;
			}

			// adjust Z positions to be z pos of obstacle top
			corner[NORTH_WEST].z = obstacleZ[0];
			corner[NORTH_EAST].z = obstacleZ[1];
			corner[SOUTH_EAST].z = obstacleZ[1];
			corner[SOUTH_WEST].z = obstacleZ[0];
			
			// move the area
			RemoveNavArea( area );
			area->Build( corner[NORTH_WEST], corner[NORTH_EAST], corner[SOUTH_EAST], corner[SOUTH_WEST] );
			Assert( !area->IsDegenerate() );
			AddNavArea( area );

			// remove side-to-side connections if there are any so AI does try to do things like run along fencetops
			area->RemoveOrthogonalConnections( obstacleDir );
			area->SetAttributes( area->GetAttributes() | NAV_MESH_NO_MERGE | NAV_MESH_OBSTACLE_TOP );
			area->SetAttributes( area->GetAttributes() & ( ~NAV_MESH_JUMP ) );
			// clear out the nodes associated with this area's corners -- corners don't match the node positions any more
			for ( int i = 0; i < NUM_CORNERS; i++ )
			{
				area->m_node[i] = NULL;		
			}
		}
	}

	for ( int i=0; i<areasToDelete.Count(); ++i )
	{
		TheNavAreas.FindAndRemove( areasToDelete[i] );
		DestroyArea( areasToDelete[i] );
	}
}

//--------------------------------------------------------------------------------------------------------------
/**
* For any two nav areas that have an obstacle between them such as a fence, railing or small wall, creates a new 
* nav area on top of the obstacle and connects it between the areas
*/
void CNavMesh::CreateObstacleTopAreas()
{
	// enumerate all areas
	FOR_EACH_VEC( TheNavAreas, it )
	{
		CNavArea *area = TheNavAreas[ it ];

		// if this is a jump node (which will ultimately get removed) or is an obstacle top, ignore it
		if ( area->GetAttributes() & ( NAV_MESH_JUMP | NAV_MESH_OBSTACLE_TOP ) )
			return;

		// Look in all directions
		for ( int i = NORTH; i < NUM_DIRECTIONS; i++ )
		{
			NavDirType dir = (NavDirType) i;

			// Look at all adjacent areas in this direction
			int iConnections = area->GetAdjacentCount( dir );
			for ( int j = 0; j < iConnections; j++ )
			{
				CNavArea *areaOther = area->GetAdjacentArea( dir, j );
				// if this is a jump node (which will ultimately get removed) or is an obstacle top, ignore it
				if ( areaOther->GetAttributes() & ( NAV_MESH_JUMP | NAV_MESH_OBSTACLE_TOP ) )
					continue;

				// create an obstacle top if there is a one-node separation between the areas and there is an intra-node obstacle within that separation
				if ( !CreateObstacleTopAreaIfNecessary( area, areaOther, dir, false ) )
				{
					// if not, create an obstacle top if there is a two-node separation between the areas and the intermediate node is significantly
					// higher than the two areas, which means there's some geometry there that causes the middle node to be higher
					CreateObstacleTopAreaIfNecessary( area, areaOther, dir, true );
				}
			}
		}
	}
}


//--------------------------------------------------------------------------------------------------------------
/**
* Creates a new nav area if an obstacle exists between the two nav areas.  If bMultiNode is false, this checks
* if there's a one-node separation between the areas, and if so if there is an obstacle detected between the nodes.
* If bMultiNode is true, checks if there is a two-node separation between the areas, and if so if the middle node is
* higher than the two areas, suggesting an obstacle in the middle.
*/
bool CNavMesh::CreateObstacleTopAreaIfNecessary( CNavArea *area, CNavArea *areaOther, NavDirType dir, bool bMultiNode )
{
	float obstacleHeightMin = FLT_MAX;
	float obstacleHeightMax = 0; 
	float obstacleHeightStart = 0; 
	float obstacleHeightEnd = 0;
	float obstacleDistMin = GenerationStepSize;
	float obstacleDistMax = 0;

	Vector center;
	float halfPortalWidth;

	area->ComputePortal( areaOther, dir, &center, &halfPortalWidth );

	if ( halfPortalWidth > 0 )
	{
		// get the corners to left and right of direction toward other area
		NavCornerType cornerStart = (NavCornerType) dir;
		NavCornerType cornerEnd = (NavCornerType) ( ( dir + 1 ) % NUM_CORNERS );
		CNavNode *node = area->m_node[cornerStart];
		CNavNode *nodeEnd = area->m_node[cornerEnd];
		NavDirType dirEdge = (NavDirType) ( ( dir + 1 ) % NUM_DIRECTIONS );
		obstacleHeightMin = FLT_MAX;
		float zStart = 0, zEnd = 0;
		// along the edge of this area that faces the other area, look at every node that's in the portal between the two
		while ( node )
		{
			Vector vecToPortalCenter = *node->GetPosition() - center;
			vecToPortalCenter.z = 0;
			if ( vecToPortalCenter.IsLengthLessThan( halfPortalWidth + 1.0f ) )
			{
				// this node is in the portal 

				float obstacleHeight = 0;
				float obstacleDistStartCur = node->m_obstacleStartDist[dir];
				float obstacleDistEndCur = node->m_obstacleEndDist[dir];
				
				if ( !bMultiNode )
				{
					// use the inter-node obstacle height from this node toward the next area
					obstacleHeight = node->m_obstacleHeight[dir];
				}
				else
				{
					if ( !areaOther->Contains( *node->GetPosition() ) )
					{
						// step one node toward the other area
						CNavNode *nodeTowardOtherArea = node->GetConnectedNode( dir );
						if ( nodeTowardOtherArea )
						{
							// see if that step took us upward a significant amount
							float deltaZ = nodeTowardOtherArea->GetPosition()->z - node->GetPosition()->z;
							if ( deltaZ > MaxTraversableHeight )
							{
								// see if we've arrived in the other area
								bool bInOtherArea = false;
								if ( areaOther->Contains( *nodeTowardOtherArea->GetPosition() ) )
								{
									float z = areaOther->GetZ( nodeTowardOtherArea->GetPosition()->x, nodeTowardOtherArea->GetPosition()->y );
									float deltaZ = fabs( nodeTowardOtherArea->GetPosition()->z - z );
									if ( deltaZ < 2.0f )
									{
										bInOtherArea = true;
									}
								}

								// if we have not arrived in the other area yet, take one more step in the same direction
								if ( !bInOtherArea )
								{
									CNavNode *nodeTowardOtherArea2 = nodeTowardOtherArea->GetConnectedNode( dir );
									if ( nodeTowardOtherArea2 && areaOther->Contains( *nodeTowardOtherArea2->GetPosition() ) )
									{										
										float areaDeltaZ = node->GetPosition()->z - nodeTowardOtherArea2->GetPosition()->z;
										if ( fabs( areaDeltaZ ) <= MaxTraversableHeight )
										{
											// if we arrived in the other area, the obstacle height to get here was the peak deltaZ of the node above to get here
											obstacleHeight = deltaZ;
											// make a nav area MinObstacleAreaWidth wide centered on the peak node, which is GenerationStepSize away from where we started
											obstacleDistStartCur = GenerationStepSize - (MinObstacleAreaWidth / 2);
											obstacleDistEndCur = GenerationStepSize + (MinObstacleAreaWidth / 2);
										}
									}
								}
							}
						}							
					}
				}

				obstacleHeightMin = MIN( obstacleHeight, obstacleHeightMin );
				obstacleHeightMax = MAX( obstacleHeight, obstacleHeightMax );							
				obstacleDistMin = MIN( obstacleDistStartCur, obstacleDistMin );
				obstacleDistMax = MAX( obstacleDistEndCur, obstacleDistMax );

				if ( obstacleHeightStart == 0 )
				{
					// keep track of the obstacle height and node z pos at the start of the edge
					obstacleHeightStart = obstacleHeight;
					zStart = node->GetPosition()->z;
				}
				// keep track of the obstacle height and node z pos at the end of the edge
				obstacleHeightEnd = obstacleHeight;
				zEnd = node->GetPosition()->z;

			}
			if ( node == nodeEnd )
				break;

			node = node->GetConnectedNode( dirEdge );
		}




		// if we had some obstacle height from EVERY node along the portal, then getting from this area to the other requires scaling an obstacle,
		// need to generate a nav area on top of it
		if ( ( obstacleHeightMax > MaxTraversableHeight ) && ( obstacleHeightMin > MaxTraversableHeight ) )		
		{
			// If the maximum obstacle height was greater than both the height at start and end of the edge, then the obstacle is highest somewhere
			// in the middle.  Use that as the height of both ends.
			if ( ( obstacleHeightMax > obstacleHeightStart ) && ( obstacleHeightMax > obstacleHeightEnd ) )
			{
				obstacleHeightStart = obstacleHeightMax;
				obstacleHeightEnd = obstacleHeightMax;
			}

			// for south and west, swap "start" and "end" values of edges so we can use common code below
			if ( dir == SOUTH || dir == WEST )
			{
				::V_swap( obstacleHeightStart, obstacleHeightEnd );
				::V_swap( zStart, zEnd );
			}					

			// Enforce min area width for new area
			AdjustObstacleDistances( &obstacleDistMin, &obstacleDistMax, bMultiNode ? GenerationStepSize * 2 : GenerationStepSize );
			Assert( obstacleDistMin < obstacleDistMax );
			Assert( obstacleDistMax - obstacleDistMin >= MinObstacleAreaWidth );
			float newAreaWidth = obstacleDistMax - obstacleDistMin;
			Assert( newAreaWidth > 0 );			

			// Calculate new area coordinates
			AddDirectionVector( &center, dir, obstacleDistMin + (newAreaWidth/2) );

			Vector cornerNW, cornerNE, cornerSE, cornerSW;
			switch ( dir )
			{
			case NORTH:
			case SOUTH:
				cornerNW.Init( center.x - halfPortalWidth, center.y - (newAreaWidth/2), zStart + obstacleHeightStart );
				cornerNE.Init( center.x + halfPortalWidth, center.y - (newAreaWidth/2), zEnd + obstacleHeightEnd );
				cornerSE.Init( center.x + halfPortalWidth, center.y + (newAreaWidth/2), zEnd + obstacleHeightEnd );
				cornerSW.Init( center.x - halfPortalWidth, center.y + (newAreaWidth/2), zStart + obstacleHeightStart );
				break;
			case EAST:
			case WEST:
				cornerNW.Init( center.x - (newAreaWidth/2), center.y - halfPortalWidth, zStart + obstacleHeightStart );
				cornerNE.Init( center.x + (newAreaWidth/2), center.y - halfPortalWidth, zEnd + obstacleHeightEnd );
				cornerSE.Init( center.x + (newAreaWidth/2), center.y + halfPortalWidth, zEnd + obstacleHeightEnd );
				cornerSW.Init( center.x - (newAreaWidth/2), center.y + halfPortalWidth, zStart + obstacleHeightStart );
				break;
			}

			CNavArea *areaNew = CreateArea();
			areaNew->Build( cornerNW, cornerNE, cornerSE, cornerSW );

			// add it to the nav area list
			TheNavAreas.AddToTail( areaNew );
			AddNavArea( areaNew );

			Assert( !areaNew->IsDegenerate() );

			Msg( "Created new fencetop area %d(%x) between %d(%x) and %d(%x)\n", areaNew->GetID(), areaNew->GetDebugID(), area->GetID(), area->GetDebugID(), areaOther->GetID(), areaOther->GetDebugID() );

			areaNew->SetAttributes( area->GetAttributes() );
			areaNew->SetAttributes( area->GetAttributes() | NAV_MESH_NO_MERGE | NAV_MESH_OBSTACLE_TOP );

			area->Disconnect( areaOther );							
			area->ConnectTo( areaNew, dir );			

			areaNew->ConnectTo( area, OppositeDirection( dir ) );
			areaNew->ConnectTo( areaOther, dir );							
			if ( areaOther->IsConnected( area, OppositeDirection( dir ) ) )
			{
				areaOther->Disconnect( area );								
				areaOther->ConnectTo( areaNew, OppositeDirection( dir ) );
			}					
//			AddToSelectedSet( areaNew );
			return true;
		}
	}

	return false;
}

//--------------------------------------------------------------------------------------------------------------
/**
* Remove any obstacle top areas which overlap.
*/
void CNavMesh::RemoveOverlappingObstacleTopAreas()
{
	// What we really want is the union of all obstacle top areas that get generated.  That would be hard to compute exactly,
	// so instead we'll just remove any that overlap.  The obstacle top areas don't have to be exact, we just need enough of
	// them so there is generally a path to get over any obstacle.

	// make a list of just the obstacle top areas to reduce the N of the N squared operation we're about to do
	CUtlVector<CNavArea *> vecObstacleTopAreas;
	FOR_EACH_VEC( TheNavAreas, it )
	{
		CNavArea *area = TheNavAreas[ it ];
		if ( area->GetAttributes() & NAV_MESH_OBSTACLE_TOP ) 
		{
			vecObstacleTopAreas.AddToTail( area );
		}
	}

	// look at every pair of obstacle top areas
	CUtlVector<CNavArea *> vecAreasToRemove;
	FOR_EACH_VEC( vecObstacleTopAreas, it )
	{
		CNavArea *area = vecObstacleTopAreas[it];

		Vector normal, otherNormal;
		area->ComputeNormal( &normal );
		area->ComputeNormal( &otherNormal, true );

		// Remove any obstacle areas that are steep enough to be jump areas
		float lowestNormalZ = MIN( normal.z, otherNormal.z );
		if ( lowestNormalZ < nav_slope_limit.GetFloat() )
		{
			vecAreasToRemove.AddToTail( area );
		}

		for ( int it2 = it+1; it2 < vecObstacleTopAreas.Count(); it2++ )
		{
			CNavArea *areaOther = vecObstacleTopAreas[it2];
			if ( area->IsOverlapping( areaOther ) )
			{		
				if ( area->Contains( areaOther ) )
				{
					// if one entirely contains the other, mark the other for removal
					vecAreasToRemove.AddToTail( areaOther );					
				}
				else if ( areaOther->Contains( area ) )
				{
					// if one entirely contains the other, mark the other for removal
					vecAreasToRemove.AddToTail( area );
				}
				else
				{
					// if they overlap without one being a superset of the other, just remove the smaller area
					CNavArea *areaToRemove = ( area->GetSizeX() * area->GetSizeY() > areaOther->GetSizeX() * areaOther->GetSizeY() ? areaOther : area );
					vecAreasToRemove.AddToTail( areaToRemove );					
				}
			}
		}
	}

	// now go delete all the areas we want to remove
	while ( vecAreasToRemove.Count() > 0 )
	{
		CNavArea *areaToDelete = vecAreasToRemove[0];
		RemoveFromSelectedSet( areaToDelete );
		TheNavMesh->OnEditDestroyNotify( areaToDelete );
		TheNavAreas.FindAndRemove( areaToDelete );
		TheNavMesh->DestroyArea( areaToDelete );

		// remove duplicates so we don't double-delete
		while ( vecAreasToRemove.FindAndRemove( areaToDelete ) );
	}

}

static void CommandNavCheckStairs( void )
{
	if ( !UTIL_IsCommandIssuedByServerAdmin() )
		return;

	TheNavMesh->MarkStairAreas();
}
static ConCommand nav_check_stairs( "nav_check_stairs", CommandNavCheckStairs, "Update the nav mesh STAIRS attribute", FCVAR_CHEAT );

//--------------------------------------------------------------------------------------------------------------
/**
 * Mark all areas that are on stairs.
 */
void CNavMesh::MarkStairAreas( void )
{
	FOR_EACH_VEC( TheNavAreas, it )
	{
		CNavArea *area = TheNavAreas[ it ];
		area->TestStairs();
	}
}


//--------------------------------------------------------------------------------------------------------------
enum StairTestType
{
	STAIRS_NO,
	STAIRS_YES,
	STAIRS_MAYBE,
};


//--------------------------------------------------------------------------------------------------------
// Test if a line across a nav area could be part of a stairway
StairTestType IsStairs( const Vector &start, const Vector &end, StairTestType ret )
{
	if ( ret == STAIRS_NO )
		return ret;

	const float inc = 5.0f;

	// the minimum height change each step to be a step and not a slope
	const float minStepZ = inc * tan( acos( nav_slope_limit.GetFloat() ) );
	const float MinStairNormal = 0.97f; // we don't care about ramps, just actual flat steps

	float t;
	Vector pos, normal;
	float height, priorHeight;

	// walk the line, checking for step height discontinuities
	float length = start.AsVector2D().DistTo( end.AsVector2D() );

	trace_t trace;
	CTraceFilterNoNPCsOrPlayer filter( NULL, COLLISION_GROUP_PLAYER_MOVEMENT );
	Vector hullMins( -inc/2, -inc/2, 0 );
	Vector hullMaxs( inc/2, inc/2, 0 );
	hullMaxs.z = 1; // don't care about vertical clearance

	if ( fabs( start.x - end.x ) > fabs( start.y - end.y ) )
	{
		hullMins.x = -8;
		hullMaxs.x = 8;
	}
	else
	{
		hullMins.y = -8;
		hullMaxs.y = 8;
	}

	Vector traceOffset( 0, 0, VEC_DUCK_HULL_MAX.z );

	// total height change must exceed a single step to be stairs
	if ( abs( start.z - end.z ) > StepHeight )
	{
		// initialize the height delta
		UTIL_TraceHull( start + traceOffset, start - traceOffset, hullMins, hullMaxs, MASK_NPCSOLID, &filter, &trace );
		if ( trace.startsolid || trace.IsDispSurface() )
		{
			return STAIRS_NO;
		}
		priorHeight = trace.endpos.z;

		// Save a copy for debug overlays
		Vector prevGround = start;
		prevGround.z = priorHeight;

		float traceIncrement = inc / length;
		for( t = 0.0f; t <= 1.0f; t += traceIncrement )
		{
			pos = start + t * ( end - start );

			UTIL_TraceHull( pos + traceOffset, pos - traceOffset, hullMins, hullMaxs, MASK_NPCSOLID, &filter, &trace );
			if ( trace.startsolid || trace.IsDispSurface() )
			{
				return STAIRS_NO;
			}
			height = trace.endpos.z;
			normal = trace.plane.normal;

			// Save a copy for debug overlays
			Vector ground( pos );
			ground.z = height;
			//NDebugOverlay::Cross3D( ground, 3, 0, 0, 255, true, 100.0f );
			//NDebugOverlay::Box( ground, hullMins, hullMaxs, 0, 0, 255, 0.0f, 100.0f );

			if ( t == 0.0f && fabs( height - start.z ) > StepHeight )
			{
				// Discontinuity at start
				return STAIRS_NO;
			}

			if ( t == 1.0f && fabs( height - end.z ) > StepHeight )
			{
				// Discontinuity at end
				return STAIRS_NO;
			}

			if ( normal.z < MinStairNormal )
			{
				// too steep here
				return STAIRS_NO;
			}


			float deltaZ = abs( height - priorHeight );

			if ( deltaZ >= minStepZ && deltaZ <= StepHeight )
			{
				// found a step
				ret = STAIRS_YES;
			}
			else if ( deltaZ > StepHeight )
			{
				// too steep here
				//NDebugOverlay::Cross3D( ground, 5, 255, 0, 0, true, 10.0f );
				//NDebugOverlay::Cross3D( prevGround, 5, 0, 255, 0, true, 10.0f );
				return STAIRS_NO;
			}

			// Save a copy for debug overlays
			prevGround = pos;
			prevGround.z = height;

			priorHeight = height;
		}
	}

	return ret;
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Test an area for being on stairs
 * NOTE: This assumes a globally constant "step height", 
 * and walkable surface normal, which really should be locomotor-specific.
 */
bool CNavArea::TestStairs( void )
{
	// clear STAIRS attribute
	SetAttributes( GetAttributes() & ~NAV_MESH_STAIRS );

	if ( GetSizeX() <= GenerationStepSize && GetSizeY() <= GenerationStepSize )
	{
		// Don't bother with stairs on small areas
		return false;
	}

	const float MatchingNormalDot = 0.95f;
	Vector firstNormal, secondNormal;
	ComputeNormal( &firstNormal );
	ComputeNormal( &secondNormal, true );
	if ( firstNormal.Dot( secondNormal ) < MatchingNormalDot )
	{
		// area corners aren't coplanar - no stairs
		return false;
	}

	// test center and edges north-to-south, and east-to-west
	StairTestType ret = STAIRS_MAYBE;
	Vector from, to;

	const float inset = 5.0f; // inset to keep the tests completely inside the nav area

	from = GetCorner( NORTH_WEST ) + Vector( inset, inset, 0 );
	to = GetCorner( NORTH_EAST ) + Vector( -inset, inset, 0 );
	ret = IsStairs( from, to, ret );

	from = GetCorner( SOUTH_WEST ) + Vector( inset, -inset, 0 );
	to = GetCorner( SOUTH_EAST ) + Vector( -inset, -inset, 0 );
	ret = IsStairs( from, to, ret );

	from = GetCorner( NORTH_WEST ) + Vector( inset, inset, 0 );
	to = GetCorner( SOUTH_WEST ) + Vector( inset, -inset, 0 );
	ret = IsStairs( from, to, ret );

	from = GetCorner( NORTH_EAST ) + Vector( -inset, inset, 0 );
	to = GetCorner( SOUTH_EAST ) + Vector( -inset, -inset, 0 );
	ret = IsStairs( from, to, ret );

	from = ( GetCorner( NORTH_WEST ) + GetCorner( NORTH_EAST ) ) / 2.0f + Vector( 0, inset, 0 );
	to = ( GetCorner( SOUTH_WEST ) + GetCorner( SOUTH_EAST ) ) / 2.0f + Vector( 0, -inset, 0 );
	ret = IsStairs( from, to, ret );

	from = ( GetCorner( NORTH_EAST ) + GetCorner( SOUTH_EAST ) ) / 2.0f + Vector( -inset, 0, 0 );
	to = ( GetCorner( NORTH_WEST ) + GetCorner( SOUTH_WEST ) ) / 2.0f + Vector( inset, 0, 0 );
	ret = IsStairs( from, to, ret );

	if ( ret == STAIRS_YES )
	{
		SetAttributes( NAV_MESH_STAIRS );
		return true;
	}

	return false;
}


//--------------------------------------------------------------------------------------------------------------
CON_COMMAND_F( nav_test_stairs, "Test the selected set for being on stairs", FCVAR_CHEAT )
{
	if ( !UTIL_IsCommandIssuedByServerAdmin() )
		return;

	int count = 0;

	const NavAreaVector &selectedSet = TheNavMesh->GetSelectedSet();
	for ( int i=0; i<selectedSet.Count(); ++i )
	{
		CNavArea *area = selectedSet[i];
		if ( area->TestStairs() )
		{
			++count;
		}
	}

	Msg( "Marked %d areas as stairs\n", count );
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Jump areas aren't used by the NextBot.  Delete them, connecting adjacent areas.
 */
void CNavMesh::RemoveJumpAreas( void )
{
	if ( !nav_generate_fixup_jump_areas.GetBool() )
	{
		return;
	}

	CUtlVector< CNavArea * > unusedAreas;

	int i;
	for ( i=0; i<TheNavAreas.Count(); ++i )
	{
		CNavArea *testArea = TheNavAreas[i];
		if ( !(testArea->GetAttributes() & NAV_MESH_JUMP) )
		{
			continue;
		}

		unusedAreas.AddToTail( testArea );
	}

	for ( i=0; i<unusedAreas.Count(); ++i )
	{
		CNavArea *areaToDelete = unusedAreas[i];
		TheNavMesh->OnEditDestroyNotify( areaToDelete );
		TheNavAreas.FindAndRemove( areaToDelete );
		TheNavMesh->DestroyArea( areaToDelete );
	}

	StripNavigationAreas();

	SetMarkedArea( NULL );			// unmark the mark area
	m_markedCorner = NUM_CORNERS;	// clear the corner selection
}


//--------------------------------------------------------------------------------------------------------------
void CNavMesh::CommandNavRemoveJumpAreas( void )
{
	JumpConnector connector;
	ForAllAreas( connector );

	int before = TheNavAreas.Count();
	RemoveJumpAreas();
	int after = TheNavAreas.Count();

	Msg( "Removed %d jump areas\n", before - after );
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Recursively chop area in half along X until child areas are roughly square
 */
static void splitX( CNavArea *area )
{
	if (area->IsRoughlySquare())
		return;

	float split = area->GetSizeX();
	split /= 2.0f;
	split += area->GetCorner( NORTH_WEST ).x;

	split = TheNavMesh->SnapToGrid( split );

	const float epsilon = 0.1f;
	if (fabs(split - area->GetCorner( NORTH_WEST ).x) < epsilon ||
		fabs(split - area->GetCorner( SOUTH_EAST ).x) < epsilon)
	{
		// too small to subdivide
		return;
	}

	CNavArea *alpha, *beta;
	if (area->SplitEdit( false, split, &alpha, &beta ))
	{
		// split each new area until square
		splitX( alpha );
		splitX( beta );
	}
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Recursively chop area in half along Y until child areas are roughly square
 */
static void splitY( CNavArea *area )
{
	if (area->IsRoughlySquare())
		return;

	float split = area->GetSizeY();
	split /= 2.0f;
	split += area->GetCorner( NORTH_WEST ).y;

	split = TheNavMesh->SnapToGrid( split );

	const float epsilon = 0.1f;
	if (fabs(split - area->GetCorner( NORTH_WEST ).y) < epsilon ||
		fabs(split - area->GetCorner( SOUTH_EAST ).y) < epsilon)
	{
		// too small to subdivide
		return;
	}

	CNavArea *alpha, *beta;
	if (area->SplitEdit( true, split, &alpha, &beta ))
	{
		// split each new area until square
		splitY( alpha );
		splitY( beta );
	}
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Split any long, thin, areas into roughly square chunks.
 */
void CNavMesh::SquareUpAreas( void )
{
	int it = 0;

	while( it < TheNavAreas.Count() )
	{
		CNavArea *area = TheNavAreas[ it ];

		// move the iterator in case the current area is split and deleted
		++it;

		if (area->HasNodes() && !area->IsRoughlySquare())
		{
			// chop this area into square pieces
			if (area->GetSizeX() > area->GetSizeY())
				splitX( area );
			else
				splitY( area );
		}
	}
}


//--------------------------------------------------------------------------------------------------------------
static bool testStitchConnection( CNavArea *source, CNavArea *target, const Vector &sourcePos, const Vector &targetPos )
{
	trace_t result;
	Vector from( sourcePos );
	Vector pos( targetPos );
	CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_NONE, WALK_THRU_EVERYTHING );
	Vector to, toNormal;
	bool success = false;
	if ( TraceAdjacentNode( 0, from, pos, &result ) )
	{
		to = result.endpos;
		toNormal = result.plane.normal;
		success = true;
	}
	else
	{
		// test going up ClimbUpHeight
		bool success = false;
		for ( float height = StepHeight; height <= ClimbUpHeight; height += 1.0f )
		{
			trace_t tr;
			Vector start( from );
			Vector end( pos );
			start.z += height;
			end.z += height;
			UTIL_TraceHull( start, end, NavTraceMins, NavTraceMaxs, TheNavMesh->GetGenerationTraceMask(), &filter, &tr );
			if ( !tr.startsolid && tr.fraction == 1.0f )
			{
				if ( !StayOnFloor( &tr ) )
				{
					break;
				}

				to = tr.endpos;
				toNormal = tr.plane.normal;

				start = end = from;
				end.z += height;
				UTIL_TraceHull( start, end, NavTraceMins, NavTraceMaxs, TheNavMesh->GetGenerationTraceMask(), &filter, &tr );
				if ( tr.fraction < 1.0f )
				{
					break;
				}

				success = true;
				break;
			}
		}
	}

	return success;
}




//--------------------------------------------------------------------------------------------------------
class IncrementallyGeneratedAreas
{
public:
	bool operator()( CNavArea *area )
	{
		return area->HasNodes();
	}
};


//--------------------------------------------------------------------------------------------------------
/**
 * Incremental generation fixup for where edges lap up against the existing nav mesh:
 * we have nodes, but the surrounding areas don't.  So, we trace outward, to see if we
 * can walk/fall to an adjacent area.  This handles dropping down into existing areas etc.
 * TODO: test pre-existing areas for drop-downs into the newly-generated areas.
 */
void CNavMesh::StitchGeneratedAreas( void )
{
	if ( m_generationMode == GENERATE_INCREMENTAL )
	{
		IncrementallyGeneratedAreas incrementalAreas;
		StitchMesh( incrementalAreas );
	}
}


//--------------------------------------------------------------------------------------------------------
class AreaSet
{
public:
	AreaSet( CUtlVector< CNavArea * > *areas )
	{
		m_areas = areas;
	}

	bool operator()( CNavArea *area )
	{
		return ( m_areas->HasElement( area ) );
	}

private:
	CUtlVector< CNavArea * > *m_areas;
};


//--------------------------------------------------------------------------------------------------------
/**
 * Stitches an arbitrary set of areas (newly-merged, for example) into the existing mesh
 */
void CNavMesh::StitchAreaSet( CUtlVector< CNavArea * > *areas )
{
	AreaSet areaSet( areas );
	StitchMesh( areaSet );
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Determine if we can "jump down" from given point
 */
inline bool testJumpDown( const Vector *fromPos, const Vector *toPos )
{
	float dz = fromPos->z - toPos->z;

	// drop can't be too far, or too short (or nonexistant)
	if (dz <= JumpCrouchHeight || dz >= DeathDrop)
		return false;

	//
	// Check LOS out and down
	//
	// +-----+
	// |     |
	// F     |
	//       |
	//       T 
	//

	Vector from, to;
	float up;
	trace_t result;

	// Try to go up and out, up to ClimbUpHeight, to get over obstacles
	for ( up=1.0f; up<=ClimbUpHeight; up += 1.0f )
	{
		from = *fromPos;
		to.Init( fromPos->x, fromPos->y, fromPos->z + up );

		UTIL_TraceHull( from, to, NavTraceMins, NavTraceMaxs, TheNavMesh->GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );
		if (result.fraction <= 0.0f || result.startsolid)
			continue;

		from.Init( fromPos->x, fromPos->y, result.endpos.z - 0.5f );
		to.Init( toPos->x, toPos->y, from.z );

		UTIL_TraceHull( from, to, NavTraceMins, NavTraceMaxs, TheNavMesh->GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );
		if (result.fraction != 1.0f || result.startsolid)
			continue;

		// Success!
		break;
	}

	if ( up > ClimbUpHeight )
		return false;

	// We've made it up and out, so see if we can drop down
	from = to;
	to.z = toPos->z + 2.0f;
	UTIL_TraceHull( from, to, NavTraceMins, NavTraceMaxs, TheNavMesh->GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );
	if (result.fraction <= 0.0f || result.startsolid)
		return false;

	// Allow a little fudge so we can drop down onto stairs
	if ( result.endpos.z > to.z + StepHeight )
		return false;

	return true;
}


//--------------------------------------------------------------------------------------------------------------
inline CNavArea *findJumpDownArea( const Vector *fromPos, NavDirType dir )
{
	if ( !nav_generate_jump_connections.GetBool() )
	{
		return NULL;
	}

	Vector start( fromPos->x, fromPos->y, fromPos->z + HalfHumanHeight );
	AddDirectionVector( &start, dir, GenerationStepSize/2.0f );

	Vector toPos;
	CNavArea *downArea = findFirstAreaInDirection( &start, dir, 4.0f * GenerationStepSize, DeathDrop, NULL, &toPos );

	if (downArea && testJumpDown( fromPos, &toPos ))
		return downArea;

	return NULL;
}


//--------------------------------------------------------------------------------------------------------------
template < typename Functor >
void CNavMesh::StitchAreaIntoMesh( CNavArea *area, NavDirType dir, Functor &func )
{
	Vector corner1, corner2;
	switch ( dir )
	{
		default:
			Assert(0);
		case NORTH:
			corner1 = area->GetCorner( NORTH_WEST );
			corner2 = area->GetCorner( NORTH_EAST );
			break;
		case SOUTH:
			corner1 = area->GetCorner( SOUTH_WEST );
			corner2 = area->GetCorner( SOUTH_EAST );
			break;
		case EAST:
			corner1 = area->GetCorner( NORTH_EAST );
			corner2 = area->GetCorner( SOUTH_EAST );
			break;
		case WEST:
			corner1 = area->GetCorner( NORTH_WEST );
			corner2 = area->GetCorner( SOUTH_WEST );
			break;
	}
	
	Vector edgeDir = corner2 - corner1;
	edgeDir.z = 0.0f;
	
	float edgeLength = edgeDir.NormalizeInPlace();
	
	for ( float n=0; n<edgeLength - 1.0f; n += GenerationStepSize )
	{
		Vector sourcePos = corner1 + edgeDir * ( n + 0.5f );
		sourcePos.z += HalfHumanHeight;
		
		Vector targetPos = sourcePos;
		switch ( dir )
		{
			case NORTH:	targetPos.y -= GenerationStepSize * 0.5f; break;
			case SOUTH:	targetPos.y += GenerationStepSize * 0.5f; break;
			case EAST:	targetPos.x += GenerationStepSize * 0.5f; break;
			case WEST:	targetPos.x -= GenerationStepSize * 0.5f; break;
		}
		
		CNavArea *targetArea = TheNavMesh->GetNavArea( targetPos );
		if ( targetArea && !func( targetArea ) )
		{
			targetPos.z = targetArea->GetZ( targetPos.x, targetPos.y ) + HalfHumanHeight;
			
			// outgoing connection
			if ( testStitchConnection( area, targetArea, sourcePos, targetPos ) )
			{
				area->ConnectTo( targetArea, dir );
			}
			
			// incoming connection
			if ( testStitchConnection( targetArea, area, targetPos, sourcePos ) )
			{
				targetArea->ConnectTo( area, OppositeDirection( dir ) );
			}
		}
		else
		{
			sourcePos.z -= HalfHumanHeight;
			sourcePos.z += 1;
			CNavArea *downArea = findJumpDownArea( &sourcePos, dir );
			if ( downArea && downArea != area && !func( downArea ) )
			{
				area->ConnectTo( downArea, dir );
			}
		}
	}
}


//--------------------------------------------------------------------------------------------------------------
/**
* Checks to see if there is a cliff - a drop of at least CliffHeight - in specified direction.
*/
inline bool CheckCliff( const Vector *fromPos, NavDirType dir, bool bExhaustive = true )
{
	// cliffs are half-baked, not used by any existing AI, and create poorly behaved nav areas (ie: long, thin, strips) (MSB 8/7/09)
	return false;


	Vector toPos( fromPos->x, fromPos->y, fromPos->z );
	AddDirectionVector( &toPos, dir, GenerationStepSize );

	trace_t trace;
	// trace a step in specified direction and see where we'd find up
	if ( TraceAdjacentNode( 0, *fromPos, toPos, &trace, DeathDrop * 10 ) && !trace.allsolid && !trace.startsolid )
	{
		float deltaZ = fromPos->z - trace.endpos.z;
		// would we fall off a cliff?
		if ( deltaZ > CliffHeight )
			return true;

		// if not, special case for south and east.  South and east edges are not considered part of a nav area, so
		// we look ahead two steps for south and east.  This ensures that the n-1th row and column of nav nodes
		// on the south and east sides of a nav area reflect any cliffs on the nth row and column.

		// if we're looking to south or east, and the first node we found was approximately flat, and this is the top-level
		// call, recurse one level to check one more step in this direction
		if ( ( dir == SOUTH || dir == EAST ) && ( fabs( deltaZ ) < StepHeight ) && bExhaustive )
		{	
			return CheckCliff( &trace.endpos, dir, false ); 
		}
	}
	return false;
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Define connections between adjacent generated areas
 */
void CNavMesh::ConnectGeneratedAreas( void )
{
	Msg( "Connecting navigation areas...\n" );

	FOR_EACH_VEC( TheNavAreas, it )
	{
		CNavArea *area = TheNavAreas[ it ];

		// scan along edge nodes, stepping one node over into the next area
		// for now, only use bi-directional connections

		// north edge
		CNavNode *node;
		for( node = area->m_node[ NORTH_WEST ]; node != area->m_node[ NORTH_EAST ]; node = node->GetConnectedNode( EAST ) )
		{
			CNavNode *adj = node->GetConnectedNode( NORTH );

			if (adj && adj->GetArea() && adj->GetConnectedNode( SOUTH ) == node )
			{
				area->ConnectTo( adj->GetArea(), NORTH );
			}
			else
			{
				CNavArea *downArea = findJumpDownArea( node->GetPosition(), NORTH );
				if (downArea && downArea != area)
					area->ConnectTo( downArea, NORTH );
			}
		}

		// west edge
		for( node = area->m_node[ NORTH_WEST ]; node != area->m_node[ SOUTH_WEST ]; node = node->GetConnectedNode( SOUTH ) )
		{
			CNavNode *adj = node->GetConnectedNode( WEST );
			
			if (adj && adj->GetArea() && adj->GetConnectedNode( EAST ) == node )
			{
				area->ConnectTo( adj->GetArea(), WEST );
			}
			else
			{
				CNavArea *downArea = findJumpDownArea( node->GetPosition(), WEST );
				if (downArea && downArea != area)
					area->ConnectTo( downArea, WEST );
			}
		}

		// south edge - this edge's nodes are actually part of adjacent areas
		// move one node north, and scan west to east
		/// @todo This allows one-node-wide areas - do we want this?
		node = area->m_node[ SOUTH_WEST ];
		if ( node ) // pre-existing areas in incremental generates won't have nodes
		{
			node = node->GetConnectedNode( NORTH );
		}
		if (node)
		{
			CNavNode *end = area->m_node[ SOUTH_EAST ]->GetConnectedNode( NORTH );
			/// @todo Figure out why cs_backalley gets a NULL node in here...
			for( ; node && node != end; node = node->GetConnectedNode( EAST ) )
			{
				CNavNode *adj = node->GetConnectedNode( SOUTH );
				
				if (adj && adj->GetArea() && adj->GetConnectedNode( NORTH ) == node )
				{
					area->ConnectTo( adj->GetArea(), SOUTH );
				}
				else
				{
					CNavArea *downArea = findJumpDownArea( node->GetPosition(), SOUTH );
					if (downArea && downArea != area)
						area->ConnectTo( downArea, SOUTH );
				}
			}
		}

		// south edge part 2 - scan the actual south edge.  If the node is not part of an adjacent area, then it
		// really belongs to us.  This will happen if our area runs right up against a ledge.
		for( node = area->m_node[ SOUTH_WEST ]; node != area->m_node[ SOUTH_EAST ]; node = node->GetConnectedNode( EAST ) )
		{
			if ( node->GetArea() )
				continue;	// some other area owns this node, pay no attention to it

			CNavNode *adj = node->GetConnectedNode( SOUTH );

			if ( node->IsBlockedInAnyDirection() || (adj && adj->IsBlockedInAnyDirection()) )
				continue;	// The space around this node is blocked, so don't connect across it

			// Don't directly connect to adj's area, since it's already 1 cell removed from our area.
			// There was no area in between, presumably for good reason.  Only look for jump down links.
			if ( !adj || !adj->GetArea() )
			{
				CNavArea *downArea = findJumpDownArea( node->GetPosition(), SOUTH );
				if (downArea && downArea != area)
					area->ConnectTo( downArea, SOUTH );
			}
		}

		// east edge - this edge's nodes are actually part of adjacent areas
		node = area->m_node[ NORTH_EAST ];
		if ( node ) // pre-existing areas in incremental generates won't have nodes
		{
			node = node->GetConnectedNode( WEST );
		}
		if (node)
		{
			CNavNode *end = area->m_node[ SOUTH_EAST ]->GetConnectedNode( WEST );
			for( ; node && node != end; node = node->GetConnectedNode( SOUTH ) )
			{
				CNavNode *adj = node->GetConnectedNode( EAST );			

				if (adj && adj->GetArea() && adj->GetConnectedNode( WEST ) == node )
				{
					area->ConnectTo( adj->GetArea(), EAST );
				}
				else
				{
					CNavArea *downArea = findJumpDownArea( node->GetPosition(), EAST );
					if (downArea && downArea != area)
						area->ConnectTo( downArea, EAST );
				}
			}
		}

		// east edge part 2 - scan the actual east edge.  If the node is not part of an adjacent area, then it
		// really belongs to us.  This will happen if our area runs right up against a ledge.
		for( node = area->m_node[ NORTH_EAST ]; node != area->m_node[ SOUTH_EAST ]; node = node->GetConnectedNode( SOUTH ) )
		{
			if ( node->GetArea() )
				continue;	// some other area owns this node, pay no attention to it

			CNavNode *adj = node->GetConnectedNode( EAST );

			if ( node->IsBlockedInAnyDirection() || (adj && adj->IsBlockedInAnyDirection()) )
				continue;	// The space around this node is blocked, so don't connect across it

			// Don't directly connect to adj's area, since it's already 1 cell removed from our area.
			// There was no area in between, presumably for good reason.  Only look for jump down links.
			if ( !adj || !adj->GetArea() )
			{
				CNavArea *downArea = findJumpDownArea( node->GetPosition(), EAST );
				if (downArea && downArea != area)
					area->ConnectTo( downArea, EAST );
			}
		}
	}

	StitchGeneratedAreas();
}

//--------------------------------------------------------------------------------------------------------------
bool CNavArea::IsAbleToMergeWith( CNavArea *other ) const
{
	if ( !HasNodes() || ( GetAttributes() & NAV_MESH_NO_MERGE ) ) 
		return false;

	if ( !other->HasNodes() || ( other->GetAttributes() & NAV_MESH_NO_MERGE ) )
		return false;

	return true;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Merge areas together to make larger ones (must remain rectangular - convex).
 * Areas can only be merged if their attributes match.
 */
void CNavMesh::MergeGeneratedAreas( void )
{
	Msg( "Merging navigation areas...\n" );

	bool merged;

	do
	{
		merged = false;

		FOR_EACH_VEC( TheNavAreas, it )
		{
			CNavArea *area = TheNavAreas[ it ];
			if ( !area->HasNodes() || ( area->GetAttributes() & NAV_MESH_NO_MERGE ) ) 
				continue;

			// north edge
			FOR_EACH_VEC( area->m_connect[ NORTH ], nit )
			{
				CNavArea *adjArea = area->m_connect[ NORTH ][ nit ].area;
				if ( !area->IsAbleToMergeWith( adjArea ) ) // pre-existing areas in incremental generates won't have nodes
					continue;

				if ( area->GetSizeY() + adjArea->GetSizeY() > GenerationStepSize * nav_area_max_size.GetInt() )
					continue;

				if (area->m_node[ NORTH_WEST ] == adjArea->m_node[ SOUTH_WEST ] &&
					area->m_node[ NORTH_EAST ] == adjArea->m_node[ SOUTH_EAST ] &&
					area->GetAttributes() == adjArea->GetAttributes() &&
					area->IsCoplanar( adjArea ))
				{
					// merge vertical
					area->m_node[ NORTH_WEST ] = adjArea->m_node[ NORTH_WEST ];
					area->m_node[ NORTH_EAST ] = adjArea->m_node[ NORTH_EAST ];

					merged = true;
					//CONSOLE_ECHO( "  Merged (north) areas #%d and #%d\n", area->m_id, adjArea->m_id );

					area->FinishMerge( adjArea );

					// restart scan - iterator is invalidated
					break;
				}
			}

			if (merged)
				break;

			// south edge
			FOR_EACH_VEC( area->m_connect[ SOUTH ], sit )
			{
				CNavArea *adjArea = area->m_connect[ SOUTH ][ sit ].area;
				if ( !area->IsAbleToMergeWith( adjArea ) ) // pre-existing areas in incremental generates won't have nodes
					continue;

				if ( area->GetSizeY() + adjArea->GetSizeY() > GenerationStepSize * nav_area_max_size.GetInt() )
					continue;

				if (adjArea->m_node[ NORTH_WEST ] == area->m_node[ SOUTH_WEST ] &&
					adjArea->m_node[ NORTH_EAST ] == area->m_node[ SOUTH_EAST ] &&
					area->GetAttributes() == adjArea->GetAttributes() &&
					area->IsCoplanar( adjArea ))
				{
					// merge vertical
					area->m_node[ SOUTH_WEST ] = adjArea->m_node[ SOUTH_WEST ];
					area->m_node[ SOUTH_EAST ] = adjArea->m_node[ SOUTH_EAST ];

					merged = true;
					//CONSOLE_ECHO( "  Merged (south) areas #%d and #%d\n", area->m_id, adjArea->m_id );

					area->FinishMerge( adjArea );

					// restart scan - iterator is invalidated
					break;
				}

			}

			if (merged)
				break;


			// west edge
			FOR_EACH_VEC( area->m_connect[ WEST ], wit )
			{
				CNavArea *adjArea = area->m_connect[ WEST ][ wit ].area;
				if ( !area->IsAbleToMergeWith( adjArea ) ) // pre-existing areas in incremental generates won't have nodes
					continue;

				if ( area->GetSizeX() + adjArea->GetSizeX() > GenerationStepSize * nav_area_max_size.GetInt() )
					continue;

				if (area->m_node[ NORTH_WEST ] == adjArea->m_node[ NORTH_EAST ] &&
						area->m_node[ SOUTH_WEST ] == adjArea->m_node[ SOUTH_EAST ] &&
						area->GetAttributes() == adjArea->GetAttributes() &&
						area->IsCoplanar( adjArea ))
				{
					// merge horizontal
					area->m_node[ NORTH_WEST ] = adjArea->m_node[ NORTH_WEST ];
					area->m_node[ SOUTH_WEST ] = adjArea->m_node[ SOUTH_WEST ];

					merged = true;
					//CONSOLE_ECHO( "  Merged (west) areas #%d and #%d\n", area->m_id, adjArea->m_id );

					area->FinishMerge( adjArea );

					// restart scan - iterator is invalidated
					break;
				}

			}

			if (merged)
				break;

			// east edge
			FOR_EACH_VEC( area->m_connect[ EAST ], eit )
			{
				CNavArea *adjArea = area->m_connect[ EAST ][ eit ].area;
				if ( !area->IsAbleToMergeWith( adjArea ) ) // pre-existing areas in incremental generates won't have nodes
					continue;

				if ( area->GetSizeX() + adjArea->GetSizeX() > GenerationStepSize * nav_area_max_size.GetInt() )
					continue;

				if (adjArea->m_node[ NORTH_WEST ] == area->m_node[ NORTH_EAST ] &&
					adjArea->m_node[ SOUTH_WEST ] == area->m_node[ SOUTH_EAST ] &&
					area->GetAttributes() == adjArea->GetAttributes() &&
					area->IsCoplanar( adjArea ))
				{
					// merge horizontal
					area->m_node[ NORTH_EAST ] = adjArea->m_node[ NORTH_EAST ];
					area->m_node[ SOUTH_EAST ] = adjArea->m_node[ SOUTH_EAST ];

					merged = true;
					//CONSOLE_ECHO( "  Merged (east) areas #%d and #%d\n", area->m_id, adjArea->m_id );

					area->FinishMerge( adjArea );

					// restart scan - iterator is invalidated
					break;
				}
			}

			if (merged)
				break;
		}
	}
	while( merged );
}

//--------------------------------------------------------------------------------------------------------------
/**
* Given arbitrary corners of a compass grid-aligned rectangle, classify them by compass direction.
* Input: vec[4]: arbitrary corners
* Output: vecNW, vecNE, vecSE, vecSW: filled in with which corner is in which compass direction
*/
void ClassifyCorners( Vector vec[4], Vector &vecNW, Vector &vecNE, Vector &vecSE, Vector &vecSW )
{
	vecNW = vecNE = vecSE = vecSW = vec[0];

	for ( int i = 0; i < 4; i++ )
	{
		if ( ( vec[i].x <= vecNW.x ) && ( vec[i].y <= vecNW.y ) )
		{
			vecNW = vec[i];
		}
		if ( ( vec[i].x >= vecNE.x ) && ( vec[i].y <= vecNE.y ) )
		{
			vecNE = vec[i];
		}
		if ( ( vec[i].x >= vecSE.x ) && ( vec[i].y >= vecSE.y ) )
		{
			vecSE = vec[i];
		}
		if ( ( vec[i].x <= vecSW.x ) && ( vec[i].y >= vecSW.y ) )
		{
			vecSW = vec[i];
		}
	}
}


//--------------------------------------------------------------------------------------------------------------
/**
* Perform miscellaneous fixups to generated mesh
*/
void CNavMesh::FixUpGeneratedAreas( void )
{
	FixCornerOnCornerAreas();
	FixConnections();
}


//--------------------------------------------------------------------------------------------------------------
void CNavMesh::FixConnections( void )
{
	// Test the steep sides of stairs for any outgoing links that cross nodes that were partially obstructed.
	FOR_EACH_VEC( TheNavAreas, it )
	{
		CNavArea *area = TheNavAreas[ it ];
		if ( !area->HasAttributes( NAV_MESH_STAIRS ) )
			continue;

		if ( !area->HasNodes() )
			continue;

		for ( int dir=0; dir<NUM_DIRECTIONS; ++dir )
		{
			NavCornerType cornerType[2];
			GetCornerTypesInDirection( (NavDirType)dir, &cornerType[0], &cornerType[1] );

			// Flat edges of stairs need to connect.  It's the slopes we don't want to climb over things for.
			float cornerDeltaZ = fabs( area->GetCorner( cornerType[0] ).z - area->GetCorner( cornerType[1] ).z );
			if ( cornerDeltaZ < StepHeight )
				continue;

			const NavConnectVector *connectedAreas = area->GetAdjacentAreas( (NavDirType)dir );
			CUtlVector< CNavArea * > areasToDisconnect;
			for ( int i=0; i<connectedAreas->Count(); ++i )
			{
				CNavArea *adjArea = connectedAreas->Element(i).area;
				if ( !adjArea->HasNodes() )
					continue;

				Vector pos, adjPos;
				float width;
				area->ComputePortal( adjArea, (NavDirType)dir, &pos, &width );
				adjArea->GetClosestPointOnArea( pos, &adjPos );

				CNavNode *node = area->FindClosestNode( pos, (NavDirType)dir );
				CNavNode *adjNode = adjArea->FindClosestNode( adjPos, OppositeDirection( (NavDirType)dir ) );
				pos = *node->GetPosition();
				adjPos = *adjNode->GetPosition();

				if ( !node || !adjNode )
					continue;

				NavCornerType adjCornerType[2];
				GetCornerTypesInDirection( OppositeDirection((NavDirType)dir), &adjCornerType[0], &adjCornerType[1] );

				// From the stair's perspective, we can't go up more than step height to reach the adjacent area.
				// Also, if the adjacent area has to jump up higher than StepHeight above the stair area to reach the stairs,
				// there's an obstruction close to the adjacent area that could prevent walking from the stairs down.
				if ( node->GetGroundHeightAboveNode( cornerType[0] ) > StepHeight )
				{
					areasToDisconnect.AddToTail( adjArea );
				}
				else if ( node->GetGroundHeightAboveNode( cornerType[1] ) > StepHeight )
				{
					areasToDisconnect.AddToTail( adjArea );
				}
				else if ( adjPos.z + adjNode->GetGroundHeightAboveNode( adjCornerType[0] ) > pos.z + StepHeight )
				{
					areasToDisconnect.AddToTail( adjArea );
				}
				else if ( adjPos.z + adjNode->GetGroundHeightAboveNode( adjCornerType[1] ) > pos.z + StepHeight )
				{
					areasToDisconnect.AddToTail( adjArea );
				}
			}

			for ( int i=0; i<areasToDisconnect.Count(); ++i )
			{
				area->Disconnect( areasToDisconnect[i] );
			}
		}
	}

	// Test to prevent A->C if A->B->C.  This can happen in doorways and dropdowns from rooftops.
	// @TODO: find the root cause of A->C links.
	FOR_EACH_VEC( TheNavAreas, it )
	{
		CNavArea *area = TheNavAreas[ it ];
		CUtlVector< CNavArea * > areasToDisconnect;
		for ( int dir=0; dir<NUM_DIRECTIONS; ++dir )
		{
			const NavConnectVector *connectedAreas = area->GetAdjacentAreas( (NavDirType)dir );
			for ( int i=0; i<connectedAreas->Count(); ++i )
			{
				CNavArea *adjArea = connectedAreas->Element(i).area;
				const NavConnectVector *adjConnectedAreas = adjArea->GetAdjacentAreas( (NavDirType)dir );
				for ( int j=0; j<adjConnectedAreas->Count(); ++j )
				{
					CNavArea *farArea = adjConnectedAreas->Element(j).area;

					if ( area->IsConnected( farArea, (NavDirType)dir ) )
					{
						areasToDisconnect.AddToTail( farArea );
					}
				}
			}
		}

		for ( int i=0; i<areasToDisconnect.Count(); ++i )
		{
			area->Disconnect( areasToDisconnect[i] );
		}
	}
}


//--------------------------------------------------------------------------------------------------------------
/**
* Fix any spots where we there are nav nodes touching only corner-on-corner but we intend bots to be able to traverse
*/
void CNavMesh::FixCornerOnCornerAreas( void )
{
	const float MaxDrop = StepHeight;	// don't make corner on corner areas that are too steep

	FOR_EACH_VEC( TheNavAreas, it )
	{
		CNavArea *area = TheNavAreas[ it ];

		// determine if we have any corners where the only nav area we touch is diagonally corner-to-corner.
		// if there are, generate additional small (0.5 x 0.5 grid size) nav areas in the corners between
		// them if map geometry allows and make connections in cardinal compass directions to create a path 
		// between the two areas.

//
//              XXXXXXXXX                       XXXXXXXXX
//              X       X                       X       X 
//				X other X                   ****X other X
//              X       X                   *newX       X
//      XXXXXXXXXXXXXXXXX      =>       XXXXXXXXXXXXXXXXX       
//      X       X                       X       Xnew*
//      X area  X                       X area  X****
//		X       X                       X       X
//      XXXXXXXXX                       XXXXXXXXX
//

		// check each corner
		for ( int iCorner = NORTH_WEST; iCorner < NUM_CORNERS; iCorner++ )
		{
			// get cardinal direction to right and left of this corner
			NavDirType dirToRight = (NavDirType) iCorner;			
			NavDirType dirToLeft = (NavDirType) ( ( iCorner+3 ) % NUM_DIRECTIONS );
					
			// if we have any connections on cardinal compass directions on edge on either side of corner we're OK, skip this nav area
			if ( area->GetAdjacentCount( dirToLeft ) > 0 || area->GetAdjacentCount( dirToRight ) > 0 ||
				area->GetIncomingConnections( dirToLeft )->Count() > 0 || area->GetIncomingConnections( dirToRight )->Count() > 0 )
				continue;

			Vector cornerPos = area->GetCorner( (NavCornerType) iCorner );
			NavDirType dirToRightTwice = DirectionRight( dirToRight );
			NavDirType dirToLeftTwice = DirectionLeft( dirToLeft );
			NavDirType dirsAlongOtherEdge[2] = { dirToLeft, dirToRight };
			NavDirType dirsAlongOurEdge[2] = { dirToLeftTwice, dirToRightTwice };

			// consider 2 potential new nav areas, to left and right of the corner we're considering
			for ( int iDir = 0; iDir < ARRAYSIZE( dirsAlongOtherEdge ); iDir++ )
			{
				NavDirType dirAlongOtherEdge = dirsAlongOtherEdge[iDir];
				NavDirType dirAlongOurEdge = dirsAlongOurEdge[iDir];

				// look at the point 0.5 grid units along edge of other nav area
				Vector vecDeltaOtherEdge;
				DirectionToVector2D( dirAlongOtherEdge, (Vector2D *) &vecDeltaOtherEdge );
				vecDeltaOtherEdge.z = 0;
				vecDeltaOtherEdge *= GenerationStepSize * 0.5;
				Vector vecOtherEdgePos = cornerPos + vecDeltaOtherEdge;

				// see if there is a nav area at that location
				CNavArea *areaOther = GetNavArea( vecOtherEdgePos );
				Assert( areaOther != area );
				if ( !areaOther )
					continue;		// no other area in that location, we're not touching on corner

				// see if we can move from our corner in that direction
				trace_t result;
				if ( !TraceAdjacentNode( 0, cornerPos, vecOtherEdgePos, &result, MaxDrop ) )
					continue;		// something is blocking movement, don't create additional nodes to aid movement
				
				// get the corner of the other nav area that might touch our corner
				int iCornerOther = ( ( iCorner + 2 ) % NUM_CORNERS );						
				Vector cornerPosOther = areaOther->GetCorner( (NavCornerType) iCornerOther );

				if ( cornerPos != cornerPosOther )
					continue;		// that nav area does not touch us on corner
				
				// we are touching corner-to-corner with the other nav area and don't have connections in cardinal directions around
				// the corner that touches, this is a candidate to generate new small helper nav areas.

				// calculate the corners of the 0.5 x 0.5 nav area we would consider building between us and the other nav area whose corner we touch
				Vector vecDeltaOurEdge;
				DirectionToVector2D( dirAlongOurEdge, (Vector2D *) &vecDeltaOurEdge );
				vecDeltaOurEdge.z = 0;
				vecDeltaOurEdge *= GenerationStepSize * 0.5;
				Vector vecOurEdgePos = cornerPos + vecDeltaOurEdge;
				Vector vecCorner[4];					
				vecCorner[0] = cornerPos + vecDeltaOtherEdge + vecDeltaOurEdge;		// far corner of new nav area
				vecCorner[1] = cornerPos + vecDeltaOtherEdge;						// intersection of far edge of new nav area with other nav area we touch
				vecCorner[2] = cornerPos;											// common corner of this nav area, nav area we touch, and new nav area
				vecCorner[3] = cornerPos + vecDeltaOurEdge;							// intersection of far edge of new nav area with this nav area
			
				CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_NONE, WALK_THRU_EVERYTHING );
				if ( !TraceAdjacentNode( 0, vecCorner[1], vecCorner[0], &result, MaxDrop ) ||	// can we move from edge of other area to far corner of new node
					!TraceAdjacentNode( 0, vecCorner[3], vecCorner[0], &result, MaxDrop ) )		// can we move from edge of this area to far corner of new node
					continue;	// new node would not fit

				// as sanity check, make sure there's not already a nav area there, shouldn't be
				CNavArea *areaTest = GetNavArea( vecCorner[0] );
				Assert ( !areaTest );
				if ( areaTest )
					continue;

				vecCorner[0] = result.endpos;

				// create a new nav area
				CNavArea *areaNew = CreateArea();

				// arrange the corners of the new nav area by compass direction
				Vector vecNW, vecNE, vecSE, vecSW;
				ClassifyCorners( vecCorner, vecNW, vecNE, vecSE, vecSW );
				areaNew->Build( vecNW, vecNE, vecSE, vecSW );

				// add it to the nav area list
				TheNavAreas.AddToTail( areaNew );
				AddNavArea( areaNew );
				
				areaNew->SetAttributes( area->GetAttributes() );

				// reciprocally connect between this area and new area
				area->ConnectTo( areaNew, dirAlongOtherEdge );
				areaNew->ConnectTo( area, OppositeDirection( dirAlongOtherEdge ) );

				// reciprocally connect between other area and new area
				areaOther->ConnectTo( areaNew, dirAlongOurEdge );
				areaNew->ConnectTo( areaOther, OppositeDirection( dirAlongOurEdge ) );
			}
		}	
	}
}

//--------------------------------------------------------------------------------------------------------------
/**
* Fix any areas where one nav area overhangs another and the two nav areas are connected.  Subdivide the lower
* nav area such that the upper nav area doesn't overhang any area it's connected to.
*/
void CNavMesh::SplitAreasUnderOverhangs( void )
{
	// restart the whole process whenever this gets set to true
	bool bRestartProcessing = false;

	do 
	{
		bRestartProcessing = false;

		// iterate all nav areas
		for ( int it = 0; it < TheNavAreas.Count() && !bRestartProcessing; it++ )
		{
			CNavArea *area = TheNavAreas[ it ];
			Extent areaExtent;
			area->GetExtent( &areaExtent );

			// iterate all directions
			for ( int dir = NORTH; dir < NUM_DIRECTIONS && !bRestartProcessing; dir++ )
			{
				// iterate all connections in that direction
				const NavConnectVector *pConnections = area->GetAdjacentAreas( (NavDirType) dir );
				for ( int iConnection = 0; iConnection < pConnections->Count() && !bRestartProcessing; iConnection++ )
				{
					CNavArea *otherArea = (*pConnections)[iConnection].area;
					Extent otherAreaExtent;
					otherArea->GetExtent( &otherAreaExtent );

					// see if the area we are connected to overlaps our X/Y extents
					if ( area->IsOverlapping( otherArea ) )
					{
						// if the upper area isn't at least crouch height above the lower area, this is some weird minor
						// overlap, disregard it
						const float flMinSeparation = HumanCrouchHeight;
						if ( !( areaExtent.lo.z > otherAreaExtent.hi.z + flMinSeparation ) &&
							!( otherAreaExtent.lo.z > areaExtent.hi.z + flMinSeparation ) )
							continue;

						// figure out which area is above and which is below
						CNavArea *areaBelow = area, *areaAbove = otherArea;
						NavDirType dirFromAboveToBelow = OppositeDirection( (NavDirType) dir );
						if ( otherAreaExtent.lo.z < areaExtent.lo.z )
						{
							areaBelow = otherArea;
							areaAbove = area;
							dirFromAboveToBelow = OppositeDirection( dirFromAboveToBelow );
						}
						NavDirType dirFromBelowToAbove = OppositeDirection( dirFromAboveToBelow );

						// Msg( "area %d overhangs area %d and is connected\n", areaAbove->GetID(), areaBelow->GetID() );

						Extent extentBelow, extentAbove;
						areaBelow->GetExtent( &extentBelow );
						areaAbove->GetExtent( &extentAbove );
					
						float splitCoord;			// absolute world coordinate along which we will split lower nav area (X or Y, depending on axis we split on)
						float splitLen;				// length of the segment of lower nav area that is in shadow of the upper nav area
						float splitEdgeSize;		// current length of the edge of nav area that is getting split
						bool bSplitAlongX = false;

						// determine along what edge we are splitting and make some key measurements
						if ( ( dirFromAboveToBelow == EAST ) || ( dirFromAboveToBelow == WEST ) )
						{
							splitEdgeSize = extentBelow.hi.x - extentBelow.lo.x;
							if ( extentAbove.hi.x < extentBelow.hi.x )
							{
								splitCoord = extentAbove.hi.x;
								splitLen = splitCoord - extentBelow.lo.x;
							}
							else
							{
								splitCoord = extentAbove.lo.x;
								splitLen = extentBelow.hi.x - splitCoord;
							}							
						}
						else
						{
							splitEdgeSize = extentBelow.hi.y - extentBelow.lo.y;
							bSplitAlongX = true;
							if ( extentAbove.hi.y < extentBelow.hi.y )
							{
								splitCoord = extentAbove.hi.y;
								splitLen = splitCoord - extentBelow.lo.y;
							}
							else
							{
								splitCoord = extentAbove.lo.y;
								splitLen = extentBelow.hi.y - splitCoord;
							}
						}
						Assert( splitLen >= 0 );
						Assert( splitEdgeSize > 0 );

						// if we split the lower nav area right where it's in shadow of the upper nav area, will it create a really tiny strip?
						if ( splitLen < GenerationStepSize )
						{
							// if the "in shadow" part of the lower nav area is really small or the lower nav area is really small to begin with,
							// don't split it, we're better off as is
							if ( ( splitLen < GenerationStepSize*0.3 ) || ( splitEdgeSize <= GenerationStepSize * 2 ) )
								continue;

							// Move our split point so we don't create a really tiny strip on the lower nav area.  Move the split point away from
							// the upper nav area so the "in shadow" area expands to be GenerationStepSize.  The checks above ensure we have room to do this.
							float splitDelta = GenerationStepSize - splitLen;
							splitCoord += splitDelta * ( ( ( dirFromAboveToBelow == NORTH ) || ( dirFromAboveToBelow == WEST ) ) ? -1 : 1 );						
						}

						// remove any connections between the two areas (so they don't get inherited by the new areas when we split the lower area),
						// but remember what the connections were.
						bool bConnectionFromBelow = false, bConnectionFromAbove = false;
						if ( areaBelow->IsConnected( areaAbove, dirFromBelowToAbove ) )
						{
							bConnectionFromBelow = true;
							areaBelow->Disconnect( areaAbove );
						}
						if ( areaAbove->IsConnected( areaBelow, dirFromAboveToBelow ) )
						{
							bConnectionFromAbove = true;
							areaAbove->Disconnect( areaBelow );
						}						

						CNavArea *pNewAlpha = NULL,*pNewBeta = NULL;
//						int idBelow = areaBelow->GetID();
//						AddToSelectedSet( areaBelow );
						// split the lower nav area
						if ( areaBelow->SplitEdit( bSplitAlongX, splitCoord, &pNewAlpha, &pNewBeta ) )
						{
//							Msg( "Split area %d into %d and %d\n", idBelow, pNewAlpha->GetID(), pNewBeta->GetID() );

							// determine which of the two new lower areas is the one *not* in shadow of the upper nav area.  This is the one we want to
							// reconnect to
							CNavArea *pNewNonoverlappedArea = ( ( dirFromAboveToBelow == NORTH ) || ( dirFromAboveToBelow == WEST ) ) ? pNewAlpha : pNewBeta;

							// restore the previous connections from the upper nav area to the new lower nav area that is not in shadow of the upper
							if ( bConnectionFromAbove )
							{
								areaAbove->ConnectTo( pNewNonoverlappedArea, dirFromAboveToBelow );
							}
							if ( bConnectionFromBelow )
							{
								areaBelow->ConnectTo( pNewNonoverlappedArea, OppositeDirection( dirFromAboveToBelow ) );
							}

							// Now we need to just start the whole process over.  We've just perturbed the list we're iterating on (removed a nav area, added two 
							// new ones, when we did the split), and it's possible we may have to subdivide a lower nav area twice if the upper nav area
							// overhangs a corner of the lower area.  We just start all over again each time we do a split until no more overhangs occur.
							bRestartProcessing = true;
						}						
						else
						{
//							Msg( "Failed to split area %d\n", idBelow );
						}
					}
				}			
			}
		}
	} 
	while ( bRestartProcessing );
}


//--------------------------------------------------------------------------------------------------------------
bool TestForValidCrouchArea( CNavNode *node )
{
	// must make sure we don't have a bogus crouch area.  check up to JumpCrouchHeight above
	// the node for a HumanCrouchHeight space.

	CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_PLAYER_MOVEMENT, WALK_THRU_EVERYTHING );
	trace_t tr;
	Vector start( *node->GetPosition() );
	Vector end( *node->GetPosition() );
	end.z += JumpCrouchHeight;

	Vector mins( 0, 0, 0 );
	Vector maxs( GenerationStepSize, GenerationStepSize, HumanCrouchHeight );

	UTIL_TraceHull(
		start,
		end,
		mins,
		maxs,
		TheNavMesh->GetGenerationTraceMask(),
		&filter,
		&tr );

	return ( !tr.allsolid );
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Make sure that if other* are similar, test is also close.  Used in TestForValidJumpArea.
 */
bool IsHeightDifferenceValid( float test, float other1, float other2, float other3 )
{
	// Make sure the other nodes are level.
	const float CloseDelta = StepHeight / 2;
	if ( fabs( other1 - other2 ) > CloseDelta )
		return true;

	if ( fabs( other1 - other3 ) > CloseDelta )
		return true;

	if ( fabs( other2 - other3 ) > CloseDelta )
		return true;

	// Now make sure the test node is near the others.  If it is more than StepHeight away,
	// it'll form a distorted jump area.
	const float MaxDelta = StepHeight;
	if ( fabs( test - other1 ) > MaxDelta )
		return false;

	if ( fabs( test - other2 ) > MaxDelta )
		return false;

	if ( fabs( test - other3 ) > MaxDelta )
		return false;

	return true;
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Check that a 1x1 area with 'node' at the northwest corner has a valid shape - if 3 corners
 * are flat, and the 4th is significantly higher or lower, it would form a jump area that bots
 * can't navigate over well.
 */
bool TestForValidJumpArea( CNavNode *node )
{
	return true;

	CNavNode *east = node->GetConnectedNode( EAST );
	CNavNode *south = node->GetConnectedNode( SOUTH );
	if ( !east || !south )
		return false;

	CNavNode *southEast = east->GetConnectedNode( SOUTH );
	if ( !southEast )
		return false;

	if ( !IsHeightDifferenceValid(
		node->GetPosition()->z,
		south->GetPosition()->z,
		southEast->GetPosition()->z,
		east->GetPosition()->z ) )
		return false;

	if ( !IsHeightDifferenceValid(
		south->GetPosition()->z,
		node->GetPosition()->z,
		southEast->GetPosition()->z,
		east->GetPosition()->z ) )
		return false;

	if ( !IsHeightDifferenceValid(
		southEast->GetPosition()->z,
		south->GetPosition()->z,
		node->GetPosition()->z,
		east->GetPosition()->z ) )
		return false;

	if ( !IsHeightDifferenceValid(
		east->GetPosition()->z,
		south->GetPosition()->z,
		southEast->GetPosition()->z,
		node->GetPosition()->z ) )
		return false;

	return true;
}


//--------------------------------------------------------------------------------------------------------------
class TestOverlapping
{
	Vector m_nw;
	Vector m_ne;
	Vector m_sw;
	Vector m_se;
public:
	TestOverlapping( const Vector &nw, const Vector &ne, const Vector &sw, const Vector &se ) :
	  m_nw( nw ), m_ne( ne ), m_sw( sw ), m_se( se )
	{
	}

	// This approximates CNavArea::GetZ, so we can pretend our four corners delineate a nav area
	float GetZ( const Vector &pos ) const
	{
		float dx = m_se.x - m_nw.x;
		float dy = m_se.y - m_nw.y;

		// guard against division by zero due to degenerate areas
		if (dx == 0.0f || dy == 0.0f)
			return m_ne.z;

		float u = (pos.x - m_nw.x) / dx;
		float v = (pos.y - m_nw.y) / dy;

		// clamp Z values to (x,y) volume
		if (u < 0.0f)
			u = 0.0f;
		else if (u > 1.0f)
			u = 1.0f;

		if (v < 0.0f)
			v = 0.0f;
		else if (v > 1.0f)
			v = 1.0f;

		float northZ = m_nw.z + u * (m_ne.z - m_nw.z);
		float southZ = m_sw.z + u * (m_se.z - m_sw.z);

		return northZ + v * (southZ - northZ);
	}

	bool OverlapsExistingArea( void )
	{
		CNavArea *overlappingArea = NULL;
		CNavLadder *overlappingLadder = NULL;

		Vector nw = m_nw;
		Vector se = m_se;
		Vector start = nw;
		start.x += GenerationStepSize/2;
		start.y += GenerationStepSize/2;

		while ( start.x < se.x )
		{
			start.y = nw.y + GenerationStepSize/2;
			while ( start.y < se.y )
			{
				start.z = GetZ( start );
				Vector end = start;
				start.z -= StepHeight;
				end.z += HalfHumanHeight;

				if ( TheNavMesh->FindNavAreaOrLadderAlongRay( start, end, &overlappingArea, &overlappingLadder, NULL ) )
				{
					if ( overlappingArea )
					{
						return true;
					}
				}

				start.y += GenerationStepSize;
			}
			start.x += GenerationStepSize;
		}
		return false;
	}
};


//--------------------------------------------------------------------------------------------------------------
/** 
 * Check if an rectangular area of the given size can be
 * made starting from the given node as the NW corner.
 * Only consider fully connected nodes for this check.
 * All of the nodes within the test area must have the same attributes.
 * All of the nodes must be approximately co-planar w.r.t the NW node's normal, with the
 * exception of 1x1 areas which can be any angle.
 */
bool CNavMesh::TestArea( CNavNode *node, int width, int height )
{
	Vector normal = *node->GetNormal();
	float d = -DotProduct( normal, *node->GetPosition() );

	bool nodeCrouch = node->m_crouch[ SOUTH_EAST ];

	// The area's interior will be the south-east side of this north-west node.
	// If that interior space is blocked, there's no space to build an area.
	if ( node->m_isBlocked[ SOUTH_EAST ] )
	{
		return false;
	}

	int nodeAttributes = node->GetAttributes() & ~NAV_MESH_CROUCH;

	const float offPlaneTolerance = 5.0f;

	CNavNode *vertNode, *horizNode;

	vertNode = node;
	int x,y;
	for( y=0; y<height; y++ )
	{
		horizNode = vertNode;

		for( x=0; x<width; x++ )
		{
			//
			// Compute the crouch attributes for the test node, taking into account only the side(s) of the node
			// that are in the area

			// NOTE: The nodes on the south and east borders of an area aren't contained in the area.  This means that
			// crouch attributes and blocked state need to be checked to the south and east of the southEdge and eastEdge nodes.

			bool horizNodeCrouch = false;
			bool westEdge = (x == 0);
			bool eastEdge = (x == width - 1);
			bool northEdge = (y == 0);
			bool southEdge = (y == height - 1);

			// Check corners first
			if ( northEdge && westEdge )
			{
				// The area's interior will be the south-east side of this north-west node.
				// If that interior space is blocked, there's no space to build an area.
				horizNodeCrouch = horizNode->m_crouch[ SOUTH_EAST ];
				if ( horizNode->m_isBlocked[ SOUTH_EAST ] )
				{
					return false;
				}
			}
			else if ( northEdge && eastEdge )
			{
				// interior space of the area extends one more cell to the east past the easternmost nodes.
				// This means we need to check to the southeast as well as the southwest.
				horizNodeCrouch = horizNode->m_crouch[ SOUTH_EAST ] || horizNode->m_crouch[ SOUTH_WEST ];
				if ( horizNode->m_isBlocked[ SOUTH_EAST ] || horizNode->m_isBlocked[ SOUTH_WEST ] )
				{
					return false;
				}
			}
			else if ( southEdge && westEdge )
			{
				// The interior space of the area extends one more cell to the south past the southernmost nodes.
				// This means we need to check to the southeast as well as the southwest.
				horizNodeCrouch = horizNode->m_crouch[ SOUTH_EAST ] || horizNode->m_crouch[ NORTH_EAST ];
				if ( horizNode->m_isBlocked[ SOUTH_EAST ] || horizNode->m_isBlocked[ NORTH_EAST ] )
				{
					return false;
				}
			}
			else if ( southEdge && eastEdge )
			{
				// This node is completely in the interior of the area, so we need to check in all directions.
				horizNodeCrouch = (horizNode->GetAttributes() & NAV_MESH_CROUCH) != 0;
				if ( horizNode->IsBlockedInAnyDirection() )
				{
					return false;
				}
			}
			// check sides next
			else if ( northEdge )
			{
				horizNodeCrouch = horizNode->m_crouch[ SOUTH_EAST ] || horizNode->m_crouch[ SOUTH_WEST ];
				if ( horizNode->m_isBlocked[ SOUTH_EAST ] || horizNode->m_isBlocked[ SOUTH_WEST ] )
				{
					return false;
				}
			}
			else if ( southEdge )
			{
				// This node is completely in the interior of the area, so we need to check in all directions.
				horizNodeCrouch = (horizNode->GetAttributes() & NAV_MESH_CROUCH) != 0;
				if ( horizNode->IsBlockedInAnyDirection() )
				{
					return false;
				}
			}
			else if ( eastEdge )
			{
				// This node is completely in the interior of the area, so we need to check in all directions.
				horizNodeCrouch = (horizNode->GetAttributes() & NAV_MESH_CROUCH) != 0;
				if ( horizNode->IsBlockedInAnyDirection() )
				{
					return false;
				}
			}
			else if ( westEdge )
			{
				horizNodeCrouch = horizNode->m_crouch[ SOUTH_EAST ] || horizNode->m_crouch[ NORTH_EAST ];
				if ( horizNode->m_isBlocked[ SOUTH_EAST ] || horizNode->m_isBlocked[ NORTH_EAST ] )
				{
					return false;
				}
			}
			// finally, we have a center node
			else
			{
				// This node is completely in the interior of the area, so we need to check in all directions.
				horizNodeCrouch = (horizNode->GetAttributes() & NAV_MESH_CROUCH) != 0;
				if ( horizNode->IsBlockedInAnyDirection() )
				{
					return false;
				}
			}

			// all nodes must be crouch/non-crouch
			if ( nodeCrouch != horizNodeCrouch )
				return false;

			// all nodes must have the same non-crouch attributes
			int horizNodeAttributes = horizNode->GetAttributes() & ~NAV_MESH_CROUCH;
			if (horizNodeAttributes != nodeAttributes)
				return false;

			if (horizNode->IsCovered())
				return false;

			if (!horizNode->IsClosedCell())
				return false;

			if ( !CheckObstacles( horizNode, width, height, x, y ) )
				return false;

			horizNode = horizNode->GetConnectedNode( EAST );
			if (horizNode == NULL)
				return false;

			// nodes must lie on/near the plane
			if (width > 1 || height > 1)
			{
				float dist = (float)fabs( DotProduct( *horizNode->GetPosition(), normal ) + d );
				if (dist > offPlaneTolerance)
					return false;
			}
		}

		// Check the final (x=width) node, the above only checks thru x=width-1
		if ( !CheckObstacles( horizNode, width, height, x, y ) )
			return false;

		vertNode = vertNode->GetConnectedNode( SOUTH );
		if (vertNode == NULL)
			return false;

		// nodes must lie on/near the plane
		if (width > 1 || height > 1)
		{
			float dist = (float)fabs( DotProduct( *vertNode->GetPosition(), normal ) + d );
			if (dist > offPlaneTolerance)
				return false;
		}
	}

	// check planarity of southern edge
	if (width > 1 || height > 1)
	{
		horizNode = vertNode;

		for( x=0; x<width; x++ )
		{
			if ( !CheckObstacles( horizNode, width, height, x, y ) )
				return false;

			horizNode = horizNode->GetConnectedNode( EAST );
			if (horizNode == NULL)
				return false;

			// nodes must lie on/near the plane
			float dist = (float)fabs( DotProduct( *horizNode->GetPosition(), normal ) + d );
			if (dist > offPlaneTolerance)
				return false;
		}

		// Check the final (x=width) node, the above only checks thru x=width-1
		if ( !CheckObstacles( horizNode, width, height, x, y ) )
			return false;
	}

	vertNode = node;
	for( y=0; y<height; ++y )
	{
		horizNode = vertNode;

		for( int x=0; x<width; ++x )
		{
			// look for odd jump areas (3 points on the ground, 1 point floating much higher or lower)
			if ( !TestForValidJumpArea( horizNode ) )
			{
				return false;
			}

			// Now that we've done the quick checks, test for a valid crouch area.
			// This finds pillars etc in the middle of 4 nodes, that weren't found initially.
			if ( nodeCrouch && !TestForValidCrouchArea( horizNode ) )
			{
				return false;
			}

			horizNode = horizNode->GetConnectedNode( EAST );
		}

		vertNode = vertNode->GetConnectedNode( SOUTH );
	}

	if ( m_generationMode == GENERATE_INCREMENTAL )
	{
		// Incremental generation needs to check that it's not overlapping existing areas...
		const Vector *nw = node->GetPosition();

		vertNode = node;
		for( int y=0; y<height; ++y )
		{
			vertNode = vertNode->GetConnectedNode( SOUTH );
		}
		const Vector *sw = vertNode->GetPosition();

		horizNode = node;
		for( int x=0; x<width; ++x )
		{
			horizNode = horizNode->GetConnectedNode( EAST );
		}
		const Vector *ne = horizNode->GetPosition();

		vertNode = horizNode;
		for( int y=0; y<height; ++y )
		{
			vertNode = vertNode->GetConnectedNode( SOUTH );
		}
		const Vector *se = vertNode->GetPosition();

		TestOverlapping test( *nw, *ne, *sw, *se );
		if ( test.OverlapsExistingArea() )
			return false;
	}

	return true;
}

//--------------------------------------------------------------------------------------------------------------
/** 
* Checks if a node has an untraversable obstacle in any direction to a neighbor.  
* width and height are size of nav area this node would be a part of, x and y are node's position
* within that grid
*/
bool CNavMesh::CheckObstacles( CNavNode *node, int width, int height, int x, int y )
{
	// any area bigger than 1x1 can't have obstacles in any connection between nodes
	if ( width > 1 || height > 1 )
	{
		if ( ( x > 0 ) && ( node->m_obstacleHeight[WEST] > MaxTraversableHeight ) )
			return false;

		if ( ( y > 0 ) && ( node->m_obstacleHeight[NORTH] > MaxTraversableHeight ) )
			return false;

		if ( ( x < width-1 ) && ( node->m_obstacleHeight[EAST] > MaxTraversableHeight ) )
			return false;

		if ( ( y < height-1 ) && ( node->m_obstacleHeight[SOUTH] > MaxTraversableHeight ) )
			return false;
	}

	// 1x1 area can have obstacles, that area will get fixed up later
	return true;
}

//--------------------------------------------------------------------------------------------------------------
/** 
 * Create a nav area, and mark all nodes it overlaps as "covered"
 * NOTE: Nodes on the east and south edges are not included.
 * Returns number of nodes covered by this area, or -1 for error;
 */
int CNavMesh::BuildArea( CNavNode *node, int width, int height )
{
	CNavNode *nwNode = node;
	CNavNode *neNode = NULL;
	CNavNode *swNode = NULL;
	CNavNode *seNode = NULL;

	CNavNode *vertNode = node;
	CNavNode *horizNode;

	int coveredNodes = 0;

	for( int y=0; y<height; y++ )
	{
		horizNode = vertNode;

		for( int x=0; x<width; x++ )
		{
			horizNode->Cover();
			++coveredNodes;
			
			horizNode = horizNode->GetConnectedNode( EAST );
		}

		if (y == 0)
			neNode = horizNode;

		vertNode = vertNode->GetConnectedNode( SOUTH );
	}

	swNode = vertNode;

	horizNode = vertNode;		
	for( int x=0; x<width; x++ )
	{
		horizNode = horizNode->GetConnectedNode( EAST );
	}
	seNode = horizNode;

	if (!nwNode || !neNode || !seNode || !swNode)
	{
		Error( "BuildArea - NULL node.\n" );
		return -1;
	}

	CNavArea *area = CreateArea();
	if (area == NULL)
	{
		Error( "BuildArea: Out of memory.\n" );
		return -1;
	}
	
	area->Build( nwNode, neNode, seNode, swNode );
		
	TheNavAreas.AddToTail( area );
	// since all internal nodes have the same attributes, set this area's attributes

	area->SetAttributes( node->GetAttributes() );

	// If any of the corners have an obstacle in the direction of another corner, then there's an internal obstruction of this nav node.
	// Mark it as not mergable so it doesn't become a part of anything else and we will fix it up later.
	if ( nwNode->m_obstacleHeight[SOUTH] > MaxTraversableHeight || nwNode->m_obstacleHeight[EAST] > MaxTraversableHeight ||
		 neNode->m_obstacleHeight[WEST] > MaxTraversableHeight || neNode->m_obstacleHeight[SOUTH] > MaxTraversableHeight ||
		 seNode->m_obstacleHeight[NORTH] > MaxTraversableHeight || seNode->m_obstacleHeight[WEST] > MaxTraversableHeight ||
		 swNode->m_obstacleHeight[EAST] > MaxTraversableHeight || swNode->m_obstacleHeight[NORTH] > MaxTraversableHeight  )
	{		
		Assert( width == 1 );		// We should only ever try to build a 1x1 area out of any two nodes that have an obstruction between them
		Assert( height == 1 );

		area->SetAttributes( area->GetAttributes() | NAV_MESH_NO_MERGE );
	}

	// Check that the node was crouch in the right direction
	bool nodeCrouch = node->m_crouch[ SOUTH_EAST ];
	if ( (area->GetAttributes() & NAV_MESH_CROUCH) && !nodeCrouch )
	{
		area->SetAttributes( area->GetAttributes() & ~NAV_MESH_CROUCH );
	}

	return coveredNodes;
}


//--------------------------------------------------------------------------------------------------------------
/**
 * This function uses the CNavNodes that have been sampled from the map to
 * generate CNavAreas - rectangular areas of "walkable" space. These areas
 * are connected to each other, proving information on know how to move from
 * area to area.
 *
 * This is a "greedy" algorithm that attempts to cover the walkable area 
 * with the fewest, largest, rectangles.
 */
void CNavMesh::CreateNavAreasFromNodes( void )
{
	// haven't yet seen a map use larger than 30...
	int tryWidth = nav_area_max_size.GetInt();
	int tryHeight = tryWidth;
	int uncoveredNodes = CNavNode::GetListLength();

	while( uncoveredNodes > 0 )
	{
		for( CNavNode *node = CNavNode::GetFirst(); node; node = node->GetNext() )
		{
			if (node->IsCovered())
				continue;

			if (TestArea( node, tryWidth, tryHeight ))
			{
				int covered = BuildArea( node, tryWidth, tryHeight );
				if (covered < 0)
				{
					Error( "Generate: Error - Data corrupt.\n" );
					return;
				}

				uncoveredNodes -= covered;
			}
		}

		if (tryWidth >= tryHeight)
			--tryWidth;
		else
			--tryHeight;

		if (tryWidth <= 0 || tryHeight <= 0)
			break;
	}

	if ( !TheNavAreas.Count() )
	{
		// If we somehow have no areas, don't try to create an impossibly-large grid
		AllocateGrid( 0, 0, 0, 0 );
		return;
	}

	Extent extent;
	extent.lo.x = 9999999999.9f;
	extent.lo.y = 9999999999.9f;
	extent.hi.x = -9999999999.9f;
	extent.hi.y = -9999999999.9f;

	// compute total extent
	FOR_EACH_VEC( TheNavAreas, it )
	{
		CNavArea *area = TheNavAreas[ it ];
		Extent areaExtent;
		area->GetExtent( &areaExtent );

		if (areaExtent.lo.x < extent.lo.x)
			extent.lo.x = areaExtent.lo.x;
		if (areaExtent.lo.y < extent.lo.y)
			extent.lo.y = areaExtent.lo.y;
		if (areaExtent.hi.x > extent.hi.x)
			extent.hi.x = areaExtent.hi.x;
		if (areaExtent.hi.y > extent.hi.y)
			extent.hi.y = areaExtent.hi.y;
	}

	// add the areas to the grid
	AllocateGrid( extent.lo.x, extent.hi.x, extent.lo.y, extent.hi.y );

	FOR_EACH_VEC( TheNavAreas, git )
	{
		AddNavArea( TheNavAreas[ git ] );
	}

	
	ConnectGeneratedAreas();
	MarkPlayerClipAreas();
	MarkJumpAreas();	// mark jump areas before we merge generated areas, so we don't merge jump and non-jump areas
	MergeGeneratedAreas();
	SplitAreasUnderOverhangs();
	SquareUpAreas();
	MarkStairAreas();
	StichAndRemoveJumpAreas();
	HandleObstacleTopAreas();
	FixUpGeneratedAreas();

	/// @TODO: incremental generation doesn't create ladders yet
	if ( m_generationMode != GENERATE_INCREMENTAL )
	{
		for ( int i=0; i<m_ladders.Count(); ++i )
		{
			CNavLadder *ladder = m_ladders[i];
			ladder->ConnectGeneratedLadder( 0.0f );
		}
	}
}


//--------------------------------------------------------------------------------------------------------------
// adds walkable positions for any/all positions a mod specifies
void CNavMesh::AddWalkableSeeds( void )
{
	CBaseEntity *spawn = gEntList.FindEntityByClassname( NULL, GetPlayerSpawnName() );

	if (spawn )
	{
		// snap it to the sampling grid
		Vector pos = spawn->GetAbsOrigin();
		pos.x = TheNavMesh->SnapToGrid( pos.x );
		pos.y = TheNavMesh->SnapToGrid( pos.y );

		Vector normal;
		if ( FindGroundForNode( &pos, &normal ) )
		{
			AddWalkableSeed( pos, normal );
		}
	}
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Initiate the generation process
 */
void CNavMesh::BeginGeneration( bool incremental )
{
	IGameEvent *event = gameeventmanager->CreateEvent( "nav_generate" );
	if ( event )
	{
		gameeventmanager->FireEvent( event );
	}

#ifdef TERROR
	engine->ServerCommand( "director_stop\nnb_delete_all\n" );
	if ( !incremental && !engine->IsDedicatedServer() )
	{
		CBasePlayer *host = UTIL_GetListenServerHost();
		if ( host )
		{
			host->ChangeTeam( TEAM_SPECTATOR );
		}
	}
#else
	engine->ServerCommand( "bot_kick\n" );
#endif

	// Right now, incrementally-generated areas won't connect to existing areas automatically.
	// Since this means hand-editing will be necessary, don't do a full analyze.
	if ( incremental )
	{
		nav_quicksave.SetValue( 1 );
	}

	m_generationState = SAMPLE_WALKABLE_SPACE;
	m_sampleTick = 0;
	m_generationMode = (incremental) ? GENERATE_INCREMENTAL : GENERATE_FULL;
	lastMsgTime = 0.0f;

	// clear any previous mesh
	DestroyNavigationMesh( incremental );

	SetNavPlace( UNDEFINED_PLACE );

	// build internal representations of ladders, which are used to find new walkable areas
	if ( !incremental ) ///< @incremental update doesn't build ladders to avoid overlapping existing ones
	{
		BuildLadders();
	}

	// start sampling from a spawn point
	if ( !incremental )
	{
		AddWalkableSeeds();
	}

	// the system will see this NULL and select the next walkable seed
	m_currentNode = NULL;

	// if there are no seed points, we can't generate
	if (m_walkableSeeds.Count() == 0)
	{
		m_generationMode = GENERATE_NONE;
		Msg( "No valid walkable seed positions.  Cannot generate Navigation Mesh.\n" );
		return;
	}

	// initialize seed list index
	m_seedIdx = 0;

	Msg( "Generating Navigation Mesh...\n" );
	m_generationStartTime = Plat_FloatTime();
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Re-analyze an existing Mesh.  Determine Hiding Spots, Encounter Spots, etc.
 */
void CNavMesh::BeginAnalysis( bool quitWhenFinished )
{
#ifdef TERROR
	if ( !engine->IsDedicatedServer() )
	{
		CBasePlayer *host = UTIL_GetListenServerHost();
		if ( host )
		{
			host->ChangeTeam( TEAM_SPECTATOR );
			engine->ServerCommand( "director_no_death_check 1\ndirector_stop\nnb_delete_all\n" );

			ConVarRef mat_fullbright( "mat_fullbright" );
			ConVarRef mat_hdr_level( "mat_hdr_level" );

			if( mat_fullbright.GetBool() )
			{
				Warning( "Setting mat_fullbright 0\n" );
				mat_fullbright.SetValue( 0 );
			}

			if ( mat_hdr_level.GetInt() < 2 )
			{
				Warning( "Enabling HDR and reloading materials\n" );
				mat_hdr_level.SetValue( 2 );
				engine->ClientCommand( host->edict(), "mat_reloadallmaterials\n" );
			}

			// Running a threaded server breaks our lighting calculations
			ConVarRef host_thread_mode( "host_thread_mode" );
			m_hostThreadModeRestoreValue = host_thread_mode.GetInt();
			host_thread_mode.SetValue( 0 );
			ConVarRef mat_queue_mode( "mat_queue_mode" );
			mat_queue_mode.SetValue( 0 );
		}
	}
#endif

	// Remove and re-add elements in TheNavAreas, to ensure indices are useful for progress feedback
	NavAreaVector tmpSet;
	{
		FOR_EACH_VEC( TheNavAreas, it )
		{
			tmpSet.AddToTail( TheNavAreas[it] );
		}
	}
	TheNavAreas.RemoveAll();
	{
		FOR_EACH_VEC( tmpSet, it )
		{
			TheNavAreas.AddToTail( tmpSet[it] );
		}
	}

	DestroyHidingSpots();
	m_generationState = FIND_HIDING_SPOTS;
	m_generationIndex = 0;
	m_generationMode = GENERATE_ANALYSIS_ONLY;
	m_bQuitWhenFinished = quitWhenFinished;
	lastMsgTime = 0.0f;
	m_generationStartTime = Plat_FloatTime();
}


//--------------------------------------------------------------------------------------------------------------
void ShowViewPortPanelToAll( const char * name, bool bShow, KeyValues *data )
{
	CRecipientFilter filter;
	filter.AddAllPlayers();
	filter.MakeReliable();

	int count = 0;
	KeyValues *subkey = NULL;

	if ( data )
	{
		subkey = data->GetFirstSubKey();
		while ( subkey )
		{
			count++; subkey = subkey->GetNextKey();
		}

		subkey = data->GetFirstSubKey(); // reset 
	}

	UserMessageBegin( filter, "VGUIMenu" );
		WRITE_STRING( name ); // menu name
		WRITE_BYTE( bShow?1:0 );
		WRITE_BYTE( count );
		
		// write additional data (be careful not more than 192 bytes!)
		while ( subkey )
		{
			WRITE_STRING( subkey->GetName() );
			WRITE_STRING( subkey->GetString() );
			subkey = subkey->GetNextKey();
		}
	MessageEnd();
}


//--------------------------------------------------------------------------------------------------------------
static void AnalysisProgress( const char *msg, int ticks, int current, bool showPercent = true )
{
	const float MsgInterval = 10.0f;
	float now = Plat_FloatTime();
	if ( now > lastMsgTime + MsgInterval )
	{
		if ( showPercent && ticks )
		{
			Msg( "%s %.0f%%\n", msg, current*100.0f/ticks );
		}
		else
		{
			Msg( "%s\n", msg );
		}

		lastMsgTime = now;
	}

	KeyValues *data = new KeyValues("data");
	data->SetString( "msg",	msg );
	data->SetInt( "total", ticks );
	data->SetInt( "current", current );

	ShowViewPortPanelToAll( PANEL_NAV_PROGRESS, true, data );

	data->deleteThis();
}


//--------------------------------------------------------------------------------------------------------------
static void HideAnalysisProgress( void )
{
	KeyValues *data = new KeyValues("data");
	ShowViewPortPanelToAll( PANEL_NAV_PROGRESS, false, data );
	data->deleteThis();
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Process the auto-generation for 'maxTime' seconds. return false if generation is complete.
 */
bool CNavMesh::UpdateGeneration( float maxTime )
{
	double startTime = Plat_FloatTime();
	static unsigned int s_movedPlayerToArea = 0;	// Last area we moved a player to for lighting calcs
	static CountdownTimer s_playerSettleTimer;		// Settle time after moving the player for lighting calcs
	static CUtlVector<CNavArea *> s_unlitAreas;
	static CUtlVector<CNavArea *> s_unlitSeedAreas;

	static ConVarRef host_thread_mode( "host_thread_mode" );

	switch( m_generationState )
	{
		//---------------------------------------------------------------------------
		case SAMPLE_WALKABLE_SPACE:
		{
			AnalysisProgress( "Sampling walkable space...", 100, m_sampleTick / 10, false );
			m_sampleTick = ( m_sampleTick + 1 ) % 1000;

			while ( SampleStep() )
			{
				if ( Plat_FloatTime() - startTime > maxTime )
				{
					return true;
				}
			}

			// sampling is complete, now build nav areas
			m_generationState = CREATE_AREAS_FROM_SAMPLES;

			return true;
		}

		//---------------------------------------------------------------------------
		case CREATE_AREAS_FROM_SAMPLES:
		{
			Msg( "Creating navigation areas from sampled data...\n" );

			// Select all pre-existing areas
			if ( m_generationMode == GENERATE_INCREMENTAL )
			{
				ClearSelectedSet();
				FOR_EACH_VEC( TheNavAreas, nit )
				{
					CNavArea *area = TheNavAreas[nit];
					AddToSelectedSet( area );
				}
			}

			// Create new areas
			CreateNavAreasFromNodes();

			// And toggle the selection, so we end up with the new areas
			if ( m_generationMode == GENERATE_INCREMENTAL )
			{
				CommandNavToggleSelectedSet();
			}

			DestroyHidingSpots();

			// Remove and re-add elements in TheNavAreas, to ensure indices are useful for progress feedback
			NavAreaVector tmpSet;
			{
				FOR_EACH_VEC( TheNavAreas, it )
				{
					tmpSet.AddToTail( TheNavAreas[it] );
				}
			}
			TheNavAreas.RemoveAll();
			{
				FOR_EACH_VEC( tmpSet, it )
				{
					TheNavAreas.AddToTail( tmpSet[it] );
				}
			}

			m_generationState = FIND_HIDING_SPOTS;
			m_generationIndex = 0;
			return true;
		}

		//---------------------------------------------------------------------------
		case FIND_HIDING_SPOTS:
		{
			while( m_generationIndex < TheNavAreas.Count() )
			{
				CNavArea *area = TheNavAreas[ m_generationIndex ];
				++m_generationIndex;

				area->ComputeHidingSpots();

				// don't go over our time allotment
				if( Plat_FloatTime() - startTime > maxTime )
				{
					AnalysisProgress( "Finding hiding spots...", 100, 100 * m_generationIndex / TheNavAreas.Count() );
					return true;
				}
			}

			Msg( "Finding hiding spots...DONE\n" );

			m_generationState = FIND_ENCOUNTER_SPOTS;
			m_generationIndex = 0;
			return true;
		}

		//---------------------------------------------------------------------------
		case FIND_ENCOUNTER_SPOTS:
		{
			while( m_generationIndex < TheNavAreas.Count() )
			{
				CNavArea *area = TheNavAreas[ m_generationIndex ];
				++m_generationIndex;

				area->ComputeSpotEncounters();

				// don't go over our time allotment
				if( Plat_FloatTime() - startTime > maxTime )
				{
					AnalysisProgress( "Finding encounter spots...", 100, 100 * m_generationIndex / TheNavAreas.Count() );
					return true;
				}
			}

			Msg( "Finding encounter spots...DONE\n" );

			m_generationState = FIND_SNIPER_SPOTS;
			m_generationIndex = 0;
			return true;
		}

		//---------------------------------------------------------------------------
		case FIND_SNIPER_SPOTS:
		{
			while( m_generationIndex < TheNavAreas.Count() )
			{
				CNavArea *area = TheNavAreas[ m_generationIndex ];
				++m_generationIndex;

				area->ComputeSniperSpots();

				// don't go over our time allotment
				if( Plat_FloatTime() - startTime > maxTime )
				{
					AnalysisProgress( "Finding sniper spots...", 100, 100 * m_generationIndex / TheNavAreas.Count() );
					return true;
				}
			}

			Msg( "Finding sniper spots...DONE\n" );

			m_generationState = COMPUTE_MESH_VISIBILITY;
			m_generationIndex = 0;
			BeginVisibilityComputations();
			Msg( "Computing mesh visibility...\n" );
		
			return true;
		}

		//---------------------------------------------------------------------------
		case COMPUTE_MESH_VISIBILITY:
		{
			while( m_generationIndex < TheNavAreas.Count() )
			{
				CNavArea *area = TheNavAreas[ m_generationIndex ];
				++m_generationIndex;

				area->ComputeVisibilityToMesh();

				// don't go over our time allotment
				if ( Plat_FloatTime() - startTime > maxTime )
				{
					AnalysisProgress( "Computing mesh visibility...", 100, 100 * m_generationIndex / TheNavAreas.Count() );
					return true;
				}
			}

			Msg( "Optimizing mesh visibility...\n" );

			EndVisibilityComputations();

			Msg( "Computing mesh visibility...DONE\n" );

			m_generationState = FIND_EARLIEST_OCCUPY_TIMES;
			m_generationIndex = 0;
			return true;
		}

		//---------------------------------------------------------------------------
		case FIND_EARLIEST_OCCUPY_TIMES:
		{
			while( m_generationIndex < TheNavAreas.Count() )
			{
				CNavArea *area = TheNavAreas[ m_generationIndex ];
				++m_generationIndex;

				area->ComputeEarliestOccupyTimes();

				// don't go over our time allotment
				if( Plat_FloatTime() - startTime > maxTime )
				{
					AnalysisProgress( "Finding earliest occupy times...", 100, 100 * m_generationIndex / TheNavAreas.Count() );
					return true;
				}
			}

			Msg( "Finding earliest occupy times...DONE\n" );

#ifdef NAV_ANALYZE_LIGHT_INTENSITY
			bool shouldSkipLightComputation = ( m_generationMode == GENERATE_INCREMENTAL || engine->IsDedicatedServer() );
#else
			bool shouldSkipLightComputation = true;
#endif

			if ( shouldSkipLightComputation )
			{
				m_generationState = CUSTOM;	// no light intensity calcs for incremental generation or dedicated servers
			}
			else
			{
				m_generationState = FIND_LIGHT_INTENSITY;
				s_playerSettleTimer.Invalidate();
				CNavArea::MakeNewMarker();
				s_unlitAreas.RemoveAll();
				FOR_EACH_VEC( TheNavAreas, nit )
				{
					s_unlitAreas.AddToTail( TheNavAreas[nit] );
					s_unlitSeedAreas.AddToTail( TheNavAreas[nit] );
				}
			}

			m_generationIndex = 0;
			return true;
		}

		//---------------------------------------------------------------------------
		case FIND_LIGHT_INTENSITY:
		{
			host_thread_mode.SetValue( 0 );	// need non-threaded server for light calcs

			CBasePlayer *host = UTIL_GetListenServerHost();

			if ( !s_unlitAreas.Count() || !host )
			{
				Msg( "Finding light intensity...DONE\n" );

				m_generationState = CUSTOM;
				m_generationIndex = 0;
				return true;
			}

			if ( !s_playerSettleTimer.IsElapsed() )
				return true; // wait for eyePos to settle

			// Now try to compute lighting for remaining areas
			int sit = 0;
			while( sit < s_unlitAreas.Count() )
			{
				CNavArea *area = s_unlitAreas[sit];
				if ( area->ComputeLighting() )
				{
					s_unlitSeedAreas.FindAndRemove( area );
					s_unlitAreas.Remove( sit );

					continue;
				}
				else
				{
					++sit;
				}
			}

			if ( s_unlitAreas.Count() )
			{
				if ( s_unlitSeedAreas.Count() )
				{
					CNavArea *moveArea = s_unlitSeedAreas[0];
					s_unlitSeedAreas.FastRemove( 0 );

					//Msg( "Moving to new area %d to compute lighting for %d/%d areas\n", moveArea->GetID(), s_unlitAreas.Count(), TheNavAreas.Count() );

					Vector eyePos = moveArea->GetCenter();
					float height;
					if ( GetGroundHeight( eyePos, &height ) )
					{
						eyePos.z = height + HalfHumanHeight - StepHeight;	// players light from their centers, and we light from slightly below that, to allow for low ceilings
					}
					else
					{
						eyePos.z += HalfHumanHeight - StepHeight;	// players light from their centers, and we light from slightly below that, to allow for low ceilings
					}
					host->SetAbsOrigin( eyePos );
					AnalysisProgress( "Finding light intensity...", 100, 100 * (TheNavAreas.Count() - s_unlitAreas.Count()) / TheNavAreas.Count() );
					s_movedPlayerToArea = moveArea->GetID();
					s_playerSettleTimer.Start( 0.1f );
					return true;
				}
				else
				{
					Msg( "Finding light intensity...DONE (%d unlit areas)\n", s_unlitAreas.Count() );
					if ( s_unlitAreas.Count() )
					{
						Warning( "To see unlit areas:\n" );
						for ( int sit=0; sit<s_unlitAreas.Count(); ++sit )
						{
							CNavArea *area = s_unlitAreas[ sit ];
							Warning( "nav_unmark; nav_mark %d; nav_warp_to_mark;\n", area->GetID() );
						}
					}

					m_generationState = CUSTOM;
					m_generationIndex = 0;
				}
			}

			Msg( "Finding light intensity...DONE\n" );

			m_generationState = CUSTOM;
			m_generationIndex = 0;
			return true;
		}

		//---------------------------------------------------------------------------
		case CUSTOM:
		{
			if ( m_generationIndex == 0 )
			{
				BeginCustomAnalysis( m_generationMode == GENERATE_INCREMENTAL );
				Msg( "Start custom...\n ");
			}
			while( m_generationIndex < TheNavAreas.Count() )
			{
				CNavArea *area = TheNavAreas[ m_generationIndex ];
				++m_generationIndex;

				area->CustomAnalysis( m_generationMode == GENERATE_INCREMENTAL );

				// don't go over our time allotment
				if( Plat_FloatTime() - startTime > maxTime )
				{
					AnalysisProgress( "Custom game-specific analysis...", 100, 100 * m_generationIndex / TheNavAreas.Count() );
					return true;
				}
			}
			
			Msg( "Post custom...\n ");
			PostCustomAnalysis();

			EndCustomAnalysis();
			Msg( "Custom game-specific analysis...DONE\n" );

			m_generationState = SAVE_NAV_MESH;
			m_generationIndex = 0;
			ConVarRef mat_queue_mode( "mat_queue_mode" );
			mat_queue_mode.SetValue( -1 );
			host_thread_mode.SetValue( m_hostThreadModeRestoreValue );	// restore this
			return true;
		}

		//---------------------------------------------------------------------------
		case SAVE_NAV_MESH:
		{
			if ( m_generationMode == GENERATE_ANALYSIS_ONLY || m_generationMode == GENERATE_FULL )
			{
				m_isAnalyzed = true;
			}

			// generation complete!
			float generationTime = Plat_FloatTime() - m_generationStartTime;
			Msg( "Generation complete!  %0.1f seconds elapsed.\n", generationTime );
			bool restart = m_generationMode != GENERATE_INCREMENTAL;
			m_generationMode = GENERATE_NONE;
			m_isLoaded = true;
			ClearWalkableSeeds();

			HideAnalysisProgress();

			// save the mesh
			if (Save())
			{
				Msg( "Navigation map '%s' saved.\n", GetFilename() );
			}
			else
			{
				const char *filename = GetFilename();
				Msg( "ERROR: Cannot save navigation map '%s'.\n", (filename) ? filename : "(null)" );
			}

			if ( m_bQuitWhenFinished )
			{
				engine->ServerCommand( "quit\n" );
			}
			else if ( restart )
			{
				engine->ChangeLevel( STRING( gpGlobals->mapname ), NULL );
			}
			else
			{
				FOR_EACH_VEC( TheNavAreas, it )
				{
					TheNavAreas[ it ]->ResetNodes();
				}

#if !(DEBUG_NAV_NODES)
				// destroy navigation nodes created during map generation
				CNavNode *node, *next;
				for( node = CNavNode::m_list; node; node = next )
				{
					next = node->m_next;
					delete node;
				}
				CNavNode::m_list = NULL;
				CNavNode::m_listLength = 0;
				CNavNode::m_nextID = 1;
#endif // !(DEBUG_NAV_NODES)
			}

			return false;
		}
	}

	return false;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Define the name of player spawn entities
 */
void CNavMesh::SetPlayerSpawnName( const char *name )
{
	if (m_spawnName)
	{
		delete [] m_spawnName;
	}

	m_spawnName = new char [ strlen(name) + 1 ];
	strcpy( m_spawnName, name );
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Return name of player spawn entity
 */
const char *CNavMesh::GetPlayerSpawnName( void ) const
{
	if (m_spawnName)
		return m_spawnName;

	// default value
	return "info_player_start";
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Add a nav node and connect it.
 * Node Z positions are ground level.
 */
CNavNode *CNavMesh::AddNode( const Vector &destPos, const Vector &normal, NavDirType dir, CNavNode *source, bool isOnDisplacement, 
							float obstacleHeight, float obstacleStartDist, float obstacleEndDist )
{
	// check if a node exists at this location
	CNavNode *node = CNavNode::GetNode( destPos );
	
	// if no node exists, create one
	bool useNew = false;
	if (node == NULL)
	{
		node = new CNavNode( destPos, normal, source, isOnDisplacement );
		OnNodeAdded( node );
		useNew = true;
	}

	// connect source node to new node
	source->ConnectTo( node, dir, obstacleHeight, obstacleStartDist, obstacleEndDist );

	// optimization: if deltaZ changes very little, assume connection is commutative
	const float zTolerance = 50.0f;
	float deltaZ = source->GetPosition()->z - destPos.z;
	if (fabs( deltaZ ) < zTolerance)
	{
		if ( obstacleHeight > 0 )
		{
			obstacleHeight = MAX( obstacleHeight + deltaZ, 0 );
			Assert( obstacleHeight > 0 );
		}
		node->ConnectTo( source, OppositeDirection( dir ), obstacleHeight, GenerationStepSize - obstacleEndDist, GenerationStepSize - obstacleStartDist );
		node->MarkAsVisited( OppositeDirection( dir ) );
	}

	if (useNew)
	{
		// new node becomes current node
		m_currentNode = node;
	}

	node->CheckCrouch();

	// determine if there's a cliff nearby and set an attribute on this node
	for ( int i = 0; i < NUM_DIRECTIONS; i++ )
	{
		NavDirType dir = (NavDirType) i;
		if ( CheckCliff( node->GetPosition(), dir ) )
		{
			node->SetAttributes( node->GetAttributes() | NAV_MESH_CLIFF );
			break;
		}
	}

	return node;
}

//--------------------------------------------------------------------------------------------------------------
inline CNavNode *LadderEndSearch( const Vector *pos, NavDirType mountDir )
{
	Vector center = *pos;
	AddDirectionVector( &center, mountDir, HalfHumanWidth );

	//
	// Test the ladder dismount point first, then each cardinal direction one and two steps away
	//
	for( int d=(-1); d<2*NUM_DIRECTIONS; ++d )
	{
		Vector tryPos = center;

		if (d >= NUM_DIRECTIONS)
			AddDirectionVector( &tryPos, (NavDirType)(d - NUM_DIRECTIONS), 2.0f*GenerationStepSize );
		else if (d >= 0)
			AddDirectionVector( &tryPos, (NavDirType)d, GenerationStepSize );

		// step up a rung, to ensure adjacent floors are below us
		tryPos.z += GenerationStepSize;

		tryPos.x = TheNavMesh->SnapToGrid( tryPos.x );
		tryPos.y = TheNavMesh->SnapToGrid( tryPos.y );

		// adjust height to account for sloping areas
		Vector tryNormal;
		if (TheNavMesh->GetGroundHeight( tryPos, &tryPos.z, &tryNormal ) == false)
			continue;

		// make sure this point is not on the other side of a wall
		const float fudge = 4.0f;
		trace_t result;
		UTIL_TraceHull( center + Vector( 0, 0, fudge ), tryPos + Vector( 0, 0, fudge ), NavTraceMins, NavTraceMaxs, TheNavMesh->GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );
		if (result.fraction != 1.0f || result.startsolid)
			continue;

		// if no node exists here, create one and continue the search
		if (CNavNode::GetNode( tryPos ) == NULL)
		{
			return new CNavNode( tryPos, tryNormal, NULL, false );
		}
	}

	return NULL;
}


//--------------------------------------------------------------------------------------------------------------
bool CNavMesh::FindGroundForNode( Vector *pos, Vector *normal )
{
	CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_PLAYER_MOVEMENT, WALK_THRU_EVERYTHING );
	trace_t tr;
	Vector start( pos->x, pos->y, pos->z + VEC_DUCK_HULL_MAX.z - 0.1f );
	Vector end( *pos );
	end.z -= DeathDrop;

	UTIL_TraceHull(
		start,
		end,
		NavTraceMins,
		NavTraceMaxs,
		GetGenerationTraceMask(),
		&filter,
		&tr );

	*pos = tr.endpos;
	*normal = tr.plane.normal;

	return ( !tr.allsolid );
}


//--------------------------------------------------------------------------------------------------------------
void DrawTrace( const trace_t *trace )
{
	/*
	if ( trace->fraction > 0.0f && !trace->startsolid )
	{
		NDebugOverlay::SweptBox( trace->startpos, trace->endpos, NavTraceMins, NavTraceMaxs, vec3_angle, 0, 255, 0, 45, 100 );
	}
	else
	{
		NDebugOverlay::SweptBox( trace->startpos, trace->endpos, NavTraceMins, NavTraceMaxs, vec3_angle, 255, 0, 0, 45, 100 );
	}
	*/
}


//--------------------------------------------------------------------------------------------------------------
bool StayOnFloor( trace_t *trace, float zLimit /* = DeathDrop */ )
{
	Vector start( trace->endpos );
	Vector end( start );
	end.z -= zLimit;

	CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_NONE, WALK_THRU_EVERYTHING );
	UTIL_TraceHull( start, end, NavTraceMins, NavTraceMaxs, TheNavMesh->GetGenerationTraceMask(), &filter, trace );
	DrawTrace( trace );

	if ( trace->startsolid || trace->fraction >= 1.0f )
	{
		return false;
	}

	if ( trace->plane.normal.z < nav_slope_limit.GetFloat() )
	{
		return false;
	}

	return true;
}


//--------------------------------------------------------------------------------------------------------------
bool TraceAdjacentNode( int depth, const Vector& start, const Vector& end, trace_t *trace, float zLimit /* = DeathDrop */ )
{
	const float MinDistance = 1.0f;	// if we can't move at least this far, don't bother stepping up.

	CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_NONE, WALK_THRU_EVERYTHING );
	UTIL_TraceHull( start, end, NavTraceMins, NavTraceMaxs, TheNavMesh->GetGenerationTraceMask(), &filter, trace );
	DrawTrace( trace );

	// If we started in the ground for some reason, bail
	if ( trace->startsolid )
		return false;

	// If we made it, so try to find the floor
	if ( end.x == trace->endpos.x && end.y == trace->endpos.y )
	{
		return StayOnFloor( trace, zLimit );
	}

	// If we didn't make enough progress, bail
	if ( depth && start.AsVector2D().DistToSqr( trace->endpos.AsVector2D() ) < MinDistance * MinDistance )
	{
		return false;
	}

	// We made it more than MinDistance.  If the slope is too steep, we can't go on.
	if ( !StayOnFloor( trace, zLimit ) )
	{
		return false;
	}

	// Try to go up as if we stepped up, forward, and down.
	Vector testStart( trace->endpos );
	Vector testEnd( testStart );
	testEnd.z += StepHeight;
	UTIL_TraceHull( testStart, testEnd, NavTraceMins, NavTraceMaxs, TheNavMesh->GetGenerationTraceMask(), &filter, trace );
	DrawTrace( trace );

	Vector forwardTestStart = trace->endpos;
	Vector forwardTestEnd = end;
	forwardTestEnd.z = forwardTestStart.z;
	return TraceAdjacentNode( depth+1, forwardTestStart, forwardTestEnd, trace );
}


//--------------------------------------------------------------------------------------------------------
static bool IsNodeOverlapped( const Vector& pos, const Vector& offset )
{
	bool overlap = TheNavMesh->GetNavArea( pos + offset, HumanHeight ) != NULL;
	if ( !overlap )
	{
		Vector mins( -0.5f, -0.5f, -0.5f );
		Vector maxs( 0.5f, 0.5f, 0.5f );

		Vector start = pos;
		start.z += HalfHumanHeight;
		Vector end = start;
		end.x += offset.x * GenerationStepSize;
		end.y += offset.y * GenerationStepSize;
		trace_t trace;
		CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_NONE, WALK_THRU_EVERYTHING );
		UTIL_TraceHull( start, end, mins, maxs, TheNavMesh->GetGenerationTraceMask(), &filter, &trace );
		if ( trace.startsolid || trace.allsolid )
		{
			return true;
		}

		if ( trace.fraction < 0.1f )
		{
			return true;
		}

		start = trace.endpos;
		end.z -= HalfHumanHeight * 2;
		UTIL_TraceHull( start, end, mins, maxs, TheNavMesh->GetGenerationTraceMask(), &filter, &trace );
		if ( trace.startsolid || trace.allsolid )
		{
			return true;
		}
		
		if ( trace.fraction == 1.0f )
		{
			return true;
		}

		if ( trace.plane.normal.z < 0.7f )
		{
			return true;
		}
	}
	return overlap;
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Search the world and build a map of possible movements.
 * The algorithm begins at the bot's current location, and does a recursive search
 * outwards, tracking all valid steps and generating a directed graph of CNavNodes.
 *
 * Sample the map one "step" in a cardinal direction to learn the map.
 *
 * Returns true if sampling needs to continue, or false if done.
 */
bool CNavMesh::SampleStep( void )
{
	// take a step
	while( true )
	{
		if (m_currentNode == NULL)
		{
			// sampling is complete from current seed, try next one
			m_currentNode = GetNextWalkableSeedNode();

			if (m_currentNode == NULL)
			{
				if ( m_generationMode == GENERATE_INCREMENTAL || m_generationMode == GENERATE_SIMPLIFY )
				{
					return false;
				}

				// search is exhausted - continue search from ends of ladders
				for ( int i=0; i<m_ladders.Count(); ++i )
				{
					CNavLadder *ladder = m_ladders[i];

					// check ladder bottom
					if ((m_currentNode = LadderEndSearch( &ladder->m_bottom, ladder->GetDir() )) != 0)
						break;

					// check ladder top
					if ((m_currentNode = LadderEndSearch( &ladder->m_top, ladder->GetDir() )) != 0)
						break;
				}

				if (m_currentNode == NULL)
				{
					// all seeds exhausted, sampling complete
					return false;
				}
			}
		}

		//
		// Take a step from this node
		//
		for( int dir = NORTH; dir < NUM_DIRECTIONS; dir++ )
		{
			if (!m_currentNode->HasVisited( (NavDirType)dir ))
			{
				// have not searched in this direction yet

				// start at current node position
				Vector pos = *m_currentNode->GetPosition();

				// snap to grid
				int cx = SnapToGrid( pos.x );
				int cy = SnapToGrid( pos.y );

				// attempt to move to adjacent node
				switch( dir )
				{
					case NORTH:		cy -= GenerationStepSize; break;
					case SOUTH:		cy += GenerationStepSize; break;
					case EAST:		cx += GenerationStepSize; break;
					case WEST:		cx -= GenerationStepSize; break;
				}

				pos.x = cx;
				pos.y = cy;

				m_generationDir = (NavDirType)dir;

				// mark direction as visited
				m_currentNode->MarkAsVisited( m_generationDir );

				// sanity check to not generate across the world for incremental generation
				const float incrementalRange = nav_generate_incremental_range.GetFloat();
				if ( m_generationMode == GENERATE_INCREMENTAL && incrementalRange > 0 )
				{
					bool inRange = false;
					for ( int i=0; i<m_walkableSeeds.Count(); ++i )
					{
						const Vector &seedPos = m_walkableSeeds[i].pos;
						if ( (seedPos - pos).IsLengthLessThan( incrementalRange ) )
						{
							inRange = true;
							break;
						}
					}

					if ( !inRange )
					{
						return true;
					}
				}

				if ( m_generationMode == GENERATE_SIMPLIFY )
				{
					if ( !m_simplifyGenerationExtent.Contains( pos ) )
					{
						return true;
					}
				}

				// test if we can move to new position
				trace_t result;
				Vector from( *m_currentNode->GetPosition() );
				CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_NONE, WALK_THRU_EVERYTHING );
				Vector to, toNormal;
				float obstacleHeight = 0, obstacleStartDist = 0, obstacleEndDist = GenerationStepSize;
				if ( TraceAdjacentNode( 0, from, pos, &result ) )
				{
					to = result.endpos;
					toNormal = result.plane.normal;
				}
				else
				{
					// test going up ClimbUpHeight
					bool success = false;
					for ( float height = StepHeight; height <= ClimbUpHeight; height += 1.0f )
					{						
						trace_t tr;
						Vector start( from );
						Vector end( pos );
						start.z += height;
						end.z += height;
						UTIL_TraceHull( start, end, NavTraceMins, NavTraceMaxs, GetGenerationTraceMask(), &filter, &tr );
						if ( !tr.startsolid && tr.fraction == 1.0f )
						{
							if ( !StayOnFloor( &tr ) )
							{
								break;
							}

							to = tr.endpos;
							toNormal = tr.plane.normal;

							start = end = from;
							end.z += height;
							UTIL_TraceHull( start, end, NavTraceMins, NavTraceMaxs, GetGenerationTraceMask(), &filter, &tr );
							if ( tr.fraction < 1.0f )
							{
								break;
							}

							// keep track of far up we had to go to find a path to the next node
							obstacleHeight = height;
							success = true;
							break;
						}
						else
						{
							// Could not trace from node to node at this height, something is in the way.
							// Trace in the other direction to see if we hit something
							Vector vecToObstacleStart = tr.endpos - start;
							Assert( vecToObstacleStart.LengthSqr() <= Square( GenerationStepSize ) );
							if ( vecToObstacleStart.LengthSqr() <= Square( GenerationStepSize ) )
							{
								UTIL_TraceHull( end, start, NavTraceMins, NavTraceMaxs, GetGenerationTraceMask(), &filter, &tr );
								if ( !tr.startsolid && tr.fraction < 1.0 )
								{
									// We hit something going the other direction.  There is some obstacle between the two nodes.
									Vector vecToObstacleEnd = tr.endpos - start;
									Assert( vecToObstacleEnd.LengthSqr() <= Square( GenerationStepSize ) );
									if ( vecToObstacleEnd.LengthSqr() <= Square( GenerationStepSize )  )
									{
										// Remember the distances to start and end of the obstacle (with respect to the "from" node).
										// Keep track of the last distances to obstacle as we keep increasing the height we do a trace for.
										// If we do eventually clear the obstacle, these values will be the start and end distance to the
										// very tip of the obstacle.
										obstacleStartDist = vecToObstacleStart.Length();
										obstacleEndDist = vecToObstacleEnd.Length();
										if ( obstacleEndDist == 0 )
										{
											obstacleEndDist = GenerationStepSize;
										}
									}								
								}
							}
						}
					}

					if ( !success )
					{
						return true;
					}
				}

				// Don't generate nodes if we spill off the end of the world onto skybox
				if ( result.surface.flags & ( SURF_SKY|SURF_SKY2D ) )
				{
					return true;
				}

				// If we're incrementally generating, don't overlap existing nav areas.
				Vector testPos( to );
				bool overlapSE = IsNodeOverlapped( testPos, Vector(  1,  1, HalfHumanHeight ) );
				bool overlapSW = IsNodeOverlapped( testPos, Vector( -1,  1, HalfHumanHeight ) );
				bool overlapNE = IsNodeOverlapped( testPos, Vector(  1, -1, HalfHumanHeight ) );
				bool overlapNW = IsNodeOverlapped( testPos, Vector( -1, -1, HalfHumanHeight ) );
				if ( overlapSE && overlapSW && overlapNE && overlapNW && m_generationMode != GENERATE_SIMPLIFY )
				{
					return true;
				}

				int nTolerance = nav_generate_incremental_tolerance.GetInt();
				if ( nTolerance > 0 && m_generationMode == GENERATE_INCREMENTAL )
				{
					bool bValid = false;
					int zPos = to.z;
					for ( int i=0; i<m_walkableSeeds.Count(); ++i )
					{
						const Vector &seedPos = m_walkableSeeds[i].pos;
						int zMin = seedPos.z - nTolerance;
						int zMax = seedPos.z + nTolerance;

						if ( zPos >= zMin && zPos <= zMax )
						{
							bValid = true;
							break;
						}
					}

					if ( !bValid )
						return true;
				}


				bool isOnDisplacement = result.IsDispSurface();

				if ( nav_displacement_test.GetInt() > 0 )
				{
					// Test for nodes under displacement surfaces.
					// This happens during development, and is a pain because the space underneath a displacement
					// is not 'solid'.
					Vector start = to + Vector( 0, 0, 0 );
					Vector end = start + Vector( 0, 0, nav_displacement_test.GetInt() );
					UTIL_TraceHull( start, end, NavTraceMins, NavTraceMaxs, GetGenerationTraceMask(), &filter, &result );

					if ( result.fraction > 0 )
					{
						end = start;
						start = result.endpos;
						UTIL_TraceHull( start, end, NavTraceMins, NavTraceMaxs, GetGenerationTraceMask(), &filter, &result );
						if ( result.fraction < 1 )
						{
							// if we made it down to within StepHeight, maybe we're on a static prop
							if ( result.endpos.z > to.z + StepHeight )
							{
								return true;
							}
						}
					}
				}

				float deltaZ = to.z - m_currentNode->GetPosition()->z;
				// If there's an obstacle in the way and it's traversable, or the obstacle is not higher than the destination node itself minus a small epsilon
				// (meaning the obstacle was just the height change to get to the destination node, no extra obstacle between the two), clear obstacle height
				// and distances
				if ( ( obstacleHeight < MaxTraversableHeight ) || ( deltaZ > ( obstacleHeight - 2.0f ) ) )
				{
					obstacleHeight = 0;
					obstacleStartDist = 0;
					obstacleEndDist = GenerationStepSize;
				}

				// we can move here
				// create a new navigation node, and update current node pointer
				AddNode( to, toNormal, m_generationDir, m_currentNode, isOnDisplacement, obstacleHeight, obstacleStartDist, obstacleEndDist );

				return true;
			}
		}

		// all directions have been searched from this node - pop back to its parent and continue
		m_currentNode = m_currentNode->GetParent();
	}
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Add given walkable position to list of seed positions for map sampling
 */
void CNavMesh::AddWalkableSeed( const Vector &pos, const Vector &normal )
{
	WalkableSeedSpot seed;

	seed.pos.x = RoundToUnits( pos.x, GenerationStepSize );
	seed.pos.y = RoundToUnits( pos.y, GenerationStepSize );
	seed.pos.z = pos.z;
	seed.normal = normal;

	m_walkableSeeds.AddToTail( seed );
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Return the next walkable seed as a node
 */
CNavNode *CNavMesh::GetNextWalkableSeedNode( void )
{	
	if ( m_seedIdx >= m_walkableSeeds.Count() )
		return NULL;

	WalkableSeedSpot spot = m_walkableSeeds[ m_seedIdx ];
	++m_seedIdx;

	// check if a node exists at this location
	CNavNode *node = CNavNode::GetNode( spot.pos );
	if ( node )
		return NULL;

	return new CNavNode( spot.pos, spot.normal, NULL, false );
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Check LOS, ignoring any entities that we can walk through
 */
bool IsWalkableTraceLineClear( const Vector &from, const Vector &to, unsigned int flags )
{
	trace_t result;
	CBaseEntity *ignore = NULL;
	Vector useFrom = from;

	CTraceFilterWalkableEntities traceFilter( NULL, COLLISION_GROUP_NONE, flags );

	result.fraction = 0.0f;

	const int maxTries = 50;
	for( int t=0; t<maxTries; ++t )
	{
		UTIL_TraceLine( useFrom, to, MASK_NPCSOLID, &traceFilter, &result );

		// if we hit a walkable entity, try again
		if (result.fraction != 1.0f && IsEntityWalkable( result.m_pEnt, flags ))
		{
			ignore = result.m_pEnt;

			// start from just beyond where we hit to avoid infinite loops
			Vector dir = to - from;
			dir.NormalizeInPlace();
			useFrom = result.endpos + 5.0f * dir;
		}
		else
		{
			break;
		}
	}

	if (result.fraction == 1.0f)
		return true;

	return false;
}


//--------------------------------------------------------------------------------------------------------------
class Subdivider
{
public:
	Subdivider( int depth )
	{
		m_depth = depth;
	}

	bool operator() ( CNavArea *area )
	{
		SubdivideX( area, true, true, m_depth );
		
		return true;
	}

	void SubdivideX( CNavArea *area, bool canDivideX, bool canDivideY, int depth )
	{
		if (!canDivideX || depth <= 0)
			return;
			
		float split = area->GetSizeX() / 2.0f;
		
		if (split < GenerationStepSize)
		{
			if (canDivideY)
			{
				SubdivideY( area, false, canDivideY, depth );
			}
			return;
		}

		split += area->GetCorner( NORTH_WEST ).x;

		split = TheNavMesh->SnapToGrid( split );

		CNavArea *alpha, *beta;
		if (area->SplitEdit( false, split, &alpha, &beta ))
		{
			SubdivideY( alpha, canDivideX, canDivideY, depth );
			SubdivideY( beta, canDivideX, canDivideY, depth );
		}
	}


	void SubdivideY( CNavArea *area, bool canDivideX, bool canDivideY, int depth )
	{
		if (!canDivideY)
			return;
			
		float split = area->GetSizeY() / 2.0f;

		if (split < GenerationStepSize)
		{
			if (canDivideX)
			{
				SubdivideX( area, canDivideX, false, depth-1 );
			}
			return;
		}
		
		split += area->GetCorner( NORTH_WEST ).y;

		split = TheNavMesh->SnapToGrid( split );

		CNavArea *alpha, *beta;
		if (area->SplitEdit( true, split, &alpha, &beta ))
		{
			SubdivideX( alpha, canDivideX, canDivideY, depth-1 );
			SubdivideX( beta, canDivideX, canDivideY, depth-1 );
		}
	}
	
	int m_depth;
};


//--------------------------------------------------------------------------------------------------------------
/**
 * Subdivide each nav area in X and Y to create 4 new areas
 */
void CNavMesh::CommandNavSubdivide( const CCommand &args )
{
	int depth = 1;
	
	if (args.ArgC() == 2)
	{
		depth = atoi( args[1] );
	}

	Subdivider chop( depth );
	TheNavMesh->ForAllSelectedAreas( chop );
}

CON_COMMAND_F( nav_subdivide, "Subdivides all selected areas.", FCVAR_GAMEDLL | FCVAR_CHEAT )
{
	if ( !UTIL_IsCommandIssuedByServerAdmin() )
		return;

	TheNavMesh->CommandNavSubdivide( args );
}

//--------------------------------------------------------------------------------------------------------------
/**
* Debugging code to verify that all nav area connections are internally consistent
*/
void CNavMesh::ValidateNavAreaConnections( void )
{
	// iterate all nav areas
	NavConnect connect;

	for ( int it = 0; it < TheNavAreas.Count(); it++ )
	{
		CNavArea *area = TheNavAreas[ it ];

		for ( NavDirType dir = NORTH; dir < NUM_DIRECTIONS; dir = (NavDirType) ( ( (int) dir ) +1 ) )
		{
			const NavConnectVector *pOutgoing = area->GetAdjacentAreas(  dir );
			const NavConnectVector *pIncoming = area->GetIncomingConnections( dir );

			for ( int iConnect = 0; iConnect < pOutgoing->Count(); iConnect++ )
			{
				// make sure no area is on both the connection and incoming list
				CNavArea *areaOther = (*pOutgoing)[iConnect].area;
				connect.area = areaOther;
				if ( pIncoming->Find( connect ) != pIncoming->InvalidIndex() )
				{
					Msg( "Area %d has area %d on both 2-way and incoming list, should only be on one\n", area->GetID(), areaOther->GetID() );
					Assert( false );
				}

				// make sure there are no duplicate connections on the list
				for ( int iConnectCheck = iConnect+1; iConnectCheck < pOutgoing->Count(); iConnectCheck++ )
				{
					CNavArea *areaCheck = (*pOutgoing)[iConnectCheck].area;
					if ( areaOther == areaCheck )
					{
						Msg( "Area %d has multiple outgoing connections to area %d in direction %d\n", area->GetID(), areaOther->GetID(), dir );
						Assert( false );
					}
				}

				const NavConnectVector *pOutgoingOther = areaOther->GetAdjacentAreas( OppositeDirection( dir ) );
				const NavConnectVector *pIncomingOther = areaOther->GetIncomingConnections( OppositeDirection( dir ) );

				// if we have a one-way outgoing connection, make sure we are on the other area's incoming list
				connect.area = area;
				bool bIsTwoWay = pOutgoingOther->Find( connect ) != pOutgoingOther->InvalidIndex();				
				if ( !bIsTwoWay )
				{				
					connect.area = area;
					bool bOnOthersIncomingList = pIncomingOther->Find( connect ) != pIncomingOther->InvalidIndex();
					if ( !bOnOthersIncomingList )
					{
						Msg( "Area %d has one-way connect to area %d but does not appear on the latter's incoming list\n", area->GetID(), areaOther->GetID() );
					}					
				}
			}

			for ( int iConnect = 0; iConnect < pIncoming->Count(); iConnect++ )
			{
				CNavArea *areaOther = (*pIncoming)[iConnect].area;

				// make sure there are not duplicate areas on the incoming list
				for ( int iConnectCheck = iConnect+1; iConnectCheck < pIncoming->Count(); iConnectCheck++ )
				{
					CNavArea *areaCheck = (*pIncoming)[iConnectCheck].area;
					if ( areaOther == areaCheck )
					{
						Msg( "Area %d has multiple incoming connections to area %d in direction %d\n", area->GetID(), areaOther->GetID(), dir );
						Assert( false );
					}
				}

				const NavConnectVector *pOutgoingOther = areaOther->GetAdjacentAreas( OppositeDirection( dir ) );
				connect.area = area;
				bool bOnOthersOutgoingList = pOutgoingOther->Find( connect ) != pOutgoingOther->InvalidIndex();
				if ( !bOnOthersOutgoingList )
				{
					Msg( "Area %d has incoming connection from area %d but does not appear on latter's outgoing connection list\n", area->GetID(), areaOther->GetID() );
					Assert( false );
				}
			}
		}
	}
}

//--------------------------------------------------------------------------------------------------------------
/**
* Temp way to mark cliff areas after generation without regen'ing.  Any area that is adjacent to a cliff
* gets marked as a cliff.  This will leave some big areas marked as cliff just because one edge is adjacent to 
* a cliff so it's not great.  The code that does this at generation time is better because it ensures that
* areas next to cliffs don't get merged with no-cliff areas.
*/
void CNavMesh::PostProcessCliffAreas()
{
	for ( int it = 0; it < TheNavAreas.Count(); it++ )
	{
		CNavArea *area = TheNavAreas[ it ];
		if ( area->GetAttributes() & NAV_MESH_CLIFF )
			continue;

		for ( int i = 0; i < NUM_DIRECTIONS; i++ )
		{
			bool bHasCliff = false;
			NavDirType dir = (NavDirType) i;
			NavCornerType corner[2];

			// look at either corner along this edge
			corner[0] = (NavCornerType) i;
			corner[1] = (NavCornerType) ( ( i+ 1 ) % NUM_CORNERS );

			for ( int j = 0; j < 2; j++ )
			{
				Vector cornerPos = area->GetCorner( corner[j] );
				if ( CheckCliff( &cornerPos, dir ) )
				{
					bHasCliff = true;
					break;
				}
			}
			
			if ( bHasCliff )
			{
				area->SetAttributes( area->GetAttributes() | NAV_MESH_CLIFF );
				break;
			}
		}
	}
}

CON_COMMAND_F( nav_gen_cliffs_approx, "Mark cliff areas, post-processing approximation", FCVAR_CHEAT )
{
	if ( !UTIL_IsCommandIssuedByServerAdmin() )
		return;

	TheNavMesh->PostProcessCliffAreas();
}