DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

AI_pathing.cpp (45850B)


      1 /*
      2 ===========================================================================
      3 
      4 Doom 3 BFG Edition GPL Source Code
      5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 
      6 
      7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").  
      8 
      9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
     10 it under the terms of the GNU General Public License as published by
     11 the Free Software Foundation, either version 3 of the License, or
     12 (at your option) any later version.
     13 
     14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
     15 but WITHOUT ANY WARRANTY; without even the implied warranty of
     16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     17 GNU General Public License for more details.
     18 
     19 You should have received a copy of the GNU General Public License
     20 along with Doom 3 BFG Edition Source Code.  If not, see <http://www.gnu.org/licenses/>.
     21 
     22 In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code.  If not, please request a copy in writing from id Software at the address below.
     23 
     24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
     25 
     26 ===========================================================================
     27 */
     28 
     29 #pragma hdrstop
     30 #include "../../idlib/precompiled.h"
     31 
     32 
     33 #include "../Game_local.h"
     34 
     35 /*
     36 ===============================================================================
     37 
     38 	Dynamic Obstacle Avoidance
     39 
     40 	- assumes the AI lives inside a bounding box aligned with the gravity direction
     41 	- obstacles in proximity of the AI are gathered
     42 	- if obstacles are found the AAS walls are also considered as obstacles
     43 	- every obstacle is represented by an oriented bounding box (OBB)
     44 	- an OBB is projected onto a 2D plane orthogonal to AI's gravity direction
     45 	- the 2D windings of the projections are expanded for the AI bbox
     46 	- a path tree is build using clockwise and counter clockwise edge walks along the winding edges
     47 	- the path tree is pruned and optimized
     48 	- the shortest path is chosen for navigation
     49 
     50 ===============================================================================
     51 */
     52 
     53 const float MAX_OBSTACLE_RADIUS			= 256.0f;
     54 const float PUSH_OUTSIDE_OBSTACLES		= 0.5f;
     55 const float CLIP_BOUNDS_EPSILON			= 10.0f;
     56 const int 	MAX_AAS_WALL_EDGES			= 256;
     57 const int 	MAX_OBSTACLES				= 256;
     58 const int	MAX_PATH_NODES				= 256;
     59 const int 	MAX_OBSTACLE_PATH			= 64;
     60 
     61 typedef struct obstacle_s {
     62 	idVec2				bounds[2];
     63 	idWinding2D			winding;
     64 	idEntity *			entity;
     65 } obstacle_t;
     66 
     67 typedef struct pathNode_s {
     68 	int					dir;
     69 	idVec2				pos;
     70 	idVec2				delta;
     71 	float				dist;
     72 	int					obstacle;
     73 	int					edgeNum;
     74 	int					numNodes;
     75 	struct pathNode_s *	parent;
     76 	struct pathNode_s *	children[2];
     77 	struct pathNode_s *	next;
     78 	void				Init();
     79 } pathNode_t;
     80 
     81 void pathNode_s::Init() {
     82 	dir = 0;
     83 	pos.Zero();
     84 	delta.Zero();
     85 	obstacle = -1;
     86 	edgeNum = -1;
     87 	numNodes = 0;
     88 	parent = children[0] = children[1] = next = NULL;
     89 }
     90 
     91 idBlockAlloc<pathNode_t, 128>	pathNodeAllocator;
     92 
     93 
     94 /*
     95 ============
     96 LineIntersectsPath
     97 ============
     98 */
     99 bool LineIntersectsPath( const idVec2 &start, const idVec2 &end, const pathNode_t *node ) {
    100 	float d0, d1, d2, d3;
    101 	idVec3 plane1, plane2;
    102 
    103 	plane1 = idWinding2D::Plane2DFromPoints( start, end );
    104 	d0 = plane1.x * node->pos.x + plane1.y * node->pos.y + plane1.z;
    105 	while( node->parent ) {
    106 		d1 = plane1.x * node->parent->pos.x + plane1.y * node->parent->pos.y + plane1.z;
    107 		if ( IEEE_FLT_SIGNBITSET( d0 ) ^ IEEE_FLT_SIGNBITSET( d1 ) ) {
    108 			plane2 = idWinding2D::Plane2DFromPoints( node->pos, node->parent->pos );
    109 			d2 = plane2.x * start.x + plane2.y * start.y + plane2.z;
    110 			d3 = plane2.x * end.x + plane2.y * end.y + plane2.z;
    111 			if ( IEEE_FLT_SIGNBITSET( d2 ) ^ IEEE_FLT_SIGNBITSET( d3 ) ) {
    112 				return true;
    113 			}
    114 		}
    115 		d0 = d1;
    116 		node = node->parent;
    117 	}
    118 	return false;
    119 }
    120 
    121 /*
    122 ============
    123 PointInsideObstacle
    124 ============
    125 */
    126 int PointInsideObstacle( const obstacle_t *obstacles, const int numObstacles, const idVec2 &point ) {
    127 	int i;
    128 
    129 	for ( i = 0; i < numObstacles; i++ ) {
    130 
    131 		const idVec2 *bounds = obstacles[i].bounds;
    132 		if ( point.x < bounds[0].x || point.y < bounds[0].y || point.x > bounds[1].x || point.y > bounds[1].y ) {
    133 			continue;
    134 		}
    135 
    136 		if ( !obstacles[i].winding.PointInside( point, 0.1f ) ) {
    137 			continue;
    138 		}
    139 
    140 		return i;
    141 	}
    142 
    143 	return -1;
    144 }
    145 
    146 /*
    147 ============
    148 GetPointOutsideObstacles
    149 ============
    150 */
    151 void GetPointOutsideObstacles( const obstacle_t *obstacles, const int numObstacles, idVec2 &point, int *obstacle, int *edgeNum ) {
    152 	int i, j, k, n, bestObstacle, bestEdgeNum, queueStart, queueEnd, edgeNums[2];
    153 	float d, bestd, scale[2];
    154 	idVec3 plane, bestPlane;
    155 	idVec2 newPoint, dir, bestPoint;
    156 	int *queue;
    157 	bool *obstacleVisited;
    158 	idWinding2D w1, w2;
    159 
    160 	if ( obstacle ) {
    161 		*obstacle = -1;
    162 	}
    163 	if ( edgeNum ) {
    164 		*edgeNum = -1;
    165 	}
    166 
    167 	bestObstacle = PointInsideObstacle( obstacles, numObstacles, point );
    168 	if ( bestObstacle == -1 ) {
    169 		return;
    170 	}
    171 
    172 	const idWinding2D &w = obstacles[bestObstacle].winding;
    173 	bestd = idMath::INFINITY;
    174 	bestEdgeNum = 0;
    175 	for ( i = 0; i < w.GetNumPoints(); i++ ) {
    176 		plane = idWinding2D::Plane2DFromPoints( w[(i+1)%w.GetNumPoints()], w[i], true );
    177 		d = plane.x * point.x + plane.y * point.y + plane.z;
    178 		if ( d < bestd ) {
    179 			bestd = d;
    180 			bestPlane = plane;
    181 			bestEdgeNum = i;
    182 		}
    183 		// if this is a wall always try to pop out at the first edge
    184 		if ( obstacles[bestObstacle].entity == NULL ) {
    185 			break;
    186 		}
    187 	}
    188 
    189 	newPoint = point - ( bestd + PUSH_OUTSIDE_OBSTACLES ) * bestPlane.ToVec2();
    190 	if ( PointInsideObstacle( obstacles, numObstacles, newPoint ) == -1 ) {
    191 		point = newPoint;
    192 		if ( obstacle ) {
    193 			*obstacle = bestObstacle;
    194 		}
    195 		if ( edgeNum ) {
    196 			*edgeNum = bestEdgeNum;
    197 		}
    198 		return;
    199 	}
    200 
    201 	queue = (int *) _alloca( numObstacles * sizeof( queue[0] ) );
    202 	obstacleVisited = (bool *) _alloca( numObstacles * sizeof( obstacleVisited[0] ) );
    203 
    204 	queueStart = 0;
    205 	queueEnd = 1;
    206 	queue[0] = bestObstacle;
    207 
    208 	memset( obstacleVisited, 0, numObstacles * sizeof( obstacleVisited[0] ) );
    209 	assert( bestObstacle < numObstacles );
    210 	obstacleVisited[bestObstacle] = true;
    211 
    212 	bestd = idMath::INFINITY;
    213 	for ( i = queue[0]; queueStart < queueEnd; i = queue[++queueStart] ) {
    214 		w1 = obstacles[i].winding;
    215 		w1.Expand( PUSH_OUTSIDE_OBSTACLES );
    216 
    217 		for ( j = 0; j < numObstacles; j++ ) {
    218 			// if the obstacle has been visited already
    219 			if ( obstacleVisited[j] ) {
    220 				continue;
    221 			}
    222 			// if the bounds do not intersect
    223 			if ( obstacles[j].bounds[0].x > obstacles[i].bounds[1].x || obstacles[j].bounds[0].y > obstacles[i].bounds[1].y ||
    224 					obstacles[j].bounds[1].x < obstacles[i].bounds[0].x || obstacles[j].bounds[1].y < obstacles[i].bounds[0].y ) {
    225 				continue;
    226 			}
    227 
    228 			assert( queueEnd < numObstacles );
    229 			queue[queueEnd++] = j;
    230 			obstacleVisited[j] = true;
    231 
    232 			w2 = obstacles[j].winding;
    233 			w2.Expand( 0.2f );
    234 
    235 			for ( k = 0; k < w1.GetNumPoints(); k++ ) {
    236 				dir = w1[(k+1)%w1.GetNumPoints()] - w1[k];
    237 				if ( !w2.RayIntersection( w1[k], dir, scale[0], scale[1], edgeNums ) ) {
    238 					continue;
    239 				}
    240 				for ( n = 0; n < 2; n++ ) {
    241 					newPoint = w1[k] + scale[n] * dir;
    242 					if ( PointInsideObstacle( obstacles, numObstacles, newPoint ) == -1 ) {
    243 						d = ( newPoint - point ).LengthSqr();
    244 						if ( d < bestd ) {
    245 							bestd = d;
    246 							bestPoint = newPoint;
    247 							bestEdgeNum = edgeNums[n];
    248 							bestObstacle = j;
    249 						}
    250 					}
    251 				}
    252 			}
    253 		}
    254 
    255 		if ( bestd < idMath::INFINITY ) {
    256 			point = bestPoint;
    257 			if ( obstacle ) {
    258 				*obstacle = bestObstacle;
    259 			}
    260 			if ( edgeNum ) {
    261 				*edgeNum = bestEdgeNum;
    262 			}
    263 			return;
    264 		}
    265 	}
    266 	gameLocal.Warning( "GetPointOutsideObstacles: no valid point found" ); 
    267 }
    268 
    269 /*
    270 ============
    271 GetFirstBlockingObstacle
    272 ============
    273 */
    274 bool GetFirstBlockingObstacle( const obstacle_t *obstacles, int numObstacles, int skipObstacle, const idVec2 &startPos, const idVec2 &delta, float &blockingScale, int &blockingObstacle, int &blockingEdgeNum ) {
    275 	int i, edgeNums[2];
    276 	float dist, scale1, scale2;
    277 	idVec2 bounds[2];
    278 
    279 	// get bounds for the current movement delta
    280 	bounds[0] = startPos - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON );
    281 	bounds[1] = startPos + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON );
    282 	bounds[IEEE_FLT_SIGNBITNOTSET(delta.x)].x += delta.x;
    283 	bounds[IEEE_FLT_SIGNBITNOTSET(delta.y)].y += delta.y;
    284 
    285 	// test for obstacles blocking the path
    286 	blockingScale = idMath::INFINITY;
    287 	dist = delta.Length();
    288 	for ( i = 0; i < numObstacles; i++ ) {
    289 		if ( i == skipObstacle ) {
    290 			continue;
    291 		}
    292 		if ( bounds[0].x > obstacles[i].bounds[1].x || bounds[0].y > obstacles[i].bounds[1].y ||
    293 				bounds[1].x < obstacles[i].bounds[0].x || bounds[1].y < obstacles[i].bounds[0].y ) {
    294 			continue;
    295 		}
    296 		if ( obstacles[i].winding.RayIntersection( startPos, delta, scale1, scale2, edgeNums ) ) {
    297 			if ( scale1 < blockingScale && scale1 * dist > -0.01f && scale2 * dist > 0.01f ) {
    298 				blockingScale = scale1;
    299 				blockingObstacle = i;
    300 				blockingEdgeNum = edgeNums[0];
    301 			}
    302 		}
    303 	}
    304 	return ( blockingScale < 1.0f );
    305 }
    306 
    307 /*
    308 ============
    309 GetObstacles
    310 ============
    311 */
    312 int GetObstacles( const idPhysics *physics, const idAAS *aas, const idEntity *ignore, int areaNum, const idVec3 &startPos, const idVec3 &seekPos, obstacle_t *obstacles, int maxObstacles, idBounds &clipBounds ) {
    313 	int i, j, numListedClipModels, numObstacles, numVerts, clipMask, blockingObstacle, blockingEdgeNum;
    314 	int wallEdges[MAX_AAS_WALL_EDGES], numWallEdges, verts[2], lastVerts[2], nextVerts[2];
    315 	float stepHeight, headHeight, blockingScale, min, max;
    316 	idVec3 seekDelta, silVerts[32], start, end, nextStart, nextEnd;
    317 	idVec2 expBounds[2], edgeDir, edgeNormal, nextEdgeDir, nextEdgeNormal, lastEdgeNormal;
    318 	idVec2 obDelta;
    319 	idPhysics *obPhys;
    320 	idBox box;
    321 	idEntity *obEnt;
    322 	idClipModel *clipModel;
    323 	idClipModel *clipModelList[ MAX_GENTITIES ];
    324 
    325 	numObstacles = 0;
    326 
    327 	seekDelta = seekPos - startPos;
    328 	expBounds[0] = physics->GetBounds()[0].ToVec2() - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON );
    329 	expBounds[1] = physics->GetBounds()[1].ToVec2() + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON );
    330 
    331 	physics->GetAbsBounds().AxisProjection( -physics->GetGravityNormal(), stepHeight, headHeight );
    332 	stepHeight += aas->GetSettings()->maxStepHeight;
    333 
    334 	// clip bounds for the obstacle search space
    335 	clipBounds[0] = clipBounds[1] = startPos;
    336 	clipBounds.AddPoint( seekPos );
    337 	clipBounds.ExpandSelf( MAX_OBSTACLE_RADIUS );
    338 	clipMask = physics->GetClipMask();
    339 
    340 	// find all obstacles touching the clip bounds
    341 	numListedClipModels = gameLocal.clip.ClipModelsTouchingBounds( clipBounds, clipMask, clipModelList, MAX_GENTITIES );
    342 
    343 	for ( i = 0; i < numListedClipModels && numObstacles < MAX_OBSTACLES; i++ ) {
    344 		clipModel = clipModelList[i];
    345 		obEnt = clipModel->GetEntity();
    346 
    347 		if ( !clipModel->IsTraceModel() ) {
    348 			continue;
    349 		}
    350 
    351 		if ( obEnt->IsType( idActor::Type ) ) {
    352 			obPhys = obEnt->GetPhysics();
    353 			// ignore myself, my enemy, and dead bodies
    354 			if ( ( obPhys == physics ) || ( obEnt == ignore ) || ( obEnt->health <= 0 ) ) {
    355 				continue;
    356 			}
    357 			// if the actor is moving
    358 			idVec3 v1 = obPhys->GetLinearVelocity();
    359 			if ( v1.LengthSqr() > Square( 10.0f ) ) {
    360 				idVec3 v2 = physics->GetLinearVelocity();
    361 				if ( v2.LengthSqr() > Square( 10.0f ) ) {
    362 					// if moving in about the same direction
    363 					if ( v1 * v2 > 0.0f ) {
    364 						continue;
    365 					}
    366 				}
    367 			}
    368 		} else if ( obEnt->IsType( idMoveable::Type ) ) {
    369 			// moveables are considered obstacles
    370 		} else {
    371 			// ignore everything else
    372 			continue;
    373 		}
    374 
    375 		// check if we can step over the object
    376 		clipModel->GetAbsBounds().AxisProjection( -physics->GetGravityNormal(), min, max );
    377 		if ( max < stepHeight || min > headHeight ) {
    378 			// can step over this one
    379 			continue;
    380 		}
    381 
    382 		// project a box containing the obstacle onto the floor plane
    383 		box = idBox( clipModel->GetBounds(), clipModel->GetOrigin(), clipModel->GetAxis() );
    384 		numVerts = box.GetParallelProjectionSilhouetteVerts( physics->GetGravityNormal(), silVerts );
    385 
    386 		// create a 2D winding for the obstacle;
    387 		obstacle_t &obstacle = obstacles[numObstacles++];
    388 		obstacle.winding.Clear();
    389 		for ( j = 0; j < numVerts; j++ ) {
    390 			obstacle.winding.AddPoint( silVerts[j].ToVec2() );
    391 		}
    392 
    393 		if ( ai_showObstacleAvoidance.GetBool() ) {
    394 			for ( j = 0; j < numVerts; j++ ) {
    395 				silVerts[j].z = startPos.z;
    396 			}
    397 			for ( j = 0; j < numVerts; j++ ) {
    398 				gameRenderWorld->DebugArrow( colorWhite, silVerts[j], silVerts[(j+1)%numVerts], 4 );
    399 			}
    400 		}
    401 
    402 		// expand the 2D winding for collision with a 2D box
    403 		obstacle.winding.ExpandForAxialBox( expBounds );
    404 		obstacle.winding.GetBounds( obstacle.bounds );
    405 		obstacle.entity = obEnt;
    406 	}
    407 
    408 	// if there are no dynamic obstacles the path should be through valid AAS space
    409 	if ( numObstacles == 0 ) {
    410 		return 0;
    411 	}
    412 
    413 	// if the current path doesn't intersect any dynamic obstacles the path should be through valid AAS space
    414 	if ( PointInsideObstacle( obstacles, numObstacles, startPos.ToVec2() ) == -1 ) {
    415 		if ( !GetFirstBlockingObstacle( obstacles, numObstacles, -1, startPos.ToVec2(), seekDelta.ToVec2(), blockingScale, blockingObstacle, blockingEdgeNum ) ) {
    416 			return 0;
    417 		}
    418 	}
    419 
    420 	// create obstacles for AAS walls
    421 	if ( aas ) {
    422 		float halfBoundsSize = ( expBounds[ 1 ].x - expBounds[ 0 ].x ) * 0.5f;
    423 
    424 		numWallEdges = aas->GetWallEdges( areaNum, clipBounds, TFL_WALK, wallEdges, MAX_AAS_WALL_EDGES );
    425 		aas->SortWallEdges( wallEdges, numWallEdges );
    426 
    427 		lastVerts[0] = lastVerts[1] = 0;
    428 		lastEdgeNormal.Zero();
    429 		nextVerts[0] = nextVerts[1] = 0;
    430 		for ( i = 0; i < numWallEdges && numObstacles < MAX_OBSTACLES; i++ ) {
    431             aas->GetEdge( wallEdges[i], start, end );
    432 			aas->GetEdgeVertexNumbers( wallEdges[i], verts );
    433 			edgeDir = end.ToVec2() - start.ToVec2();
    434 			edgeDir.Normalize();
    435 			edgeNormal.x = edgeDir.y;
    436 			edgeNormal.y = -edgeDir.x;
    437 			if ( i < numWallEdges-1 ) {
    438 				aas->GetEdge( wallEdges[i+1], nextStart, nextEnd );
    439 				aas->GetEdgeVertexNumbers( wallEdges[i+1], nextVerts );
    440 				nextEdgeDir = nextEnd.ToVec2() - nextStart.ToVec2();
    441 				nextEdgeDir.Normalize();
    442 				nextEdgeNormal.x = nextEdgeDir.y;
    443 				nextEdgeNormal.y = -nextEdgeDir.x;
    444 			}
    445 
    446 			obstacle_t &obstacle = obstacles[numObstacles++];
    447 			obstacle.winding.Clear();
    448 			obstacle.winding.AddPoint( end.ToVec2() );
    449 			obstacle.winding.AddPoint( start.ToVec2() );
    450 			obstacle.winding.AddPoint( start.ToVec2() - edgeDir - edgeNormal * halfBoundsSize );
    451 			obstacle.winding.AddPoint( end.ToVec2() + edgeDir - edgeNormal * halfBoundsSize );
    452 			if ( lastVerts[1] == verts[0] ) {
    453 				obstacle.winding[2] -= lastEdgeNormal * halfBoundsSize;
    454 			} else {
    455 				obstacle.winding[1] -= edgeDir;
    456 			}
    457 			if ( verts[1] == nextVerts[0] ) {
    458 				obstacle.winding[3] -= nextEdgeNormal * halfBoundsSize;
    459 			} else {
    460 				obstacle.winding[0] += edgeDir;
    461 			}
    462 			obstacle.winding.GetBounds( obstacle.bounds );
    463 			obstacle.entity = NULL;
    464 
    465 			memcpy( lastVerts, verts, sizeof( lastVerts ) );
    466 			lastEdgeNormal = edgeNormal;
    467 		}
    468 	}
    469 
    470 	// show obstacles
    471 	if ( ai_showObstacleAvoidance.GetBool() ) {
    472 		for ( i = 0; i < numObstacles; i++ ) {
    473 			obstacle_t &obstacle = obstacles[i];
    474 			for ( j = 0; j < obstacle.winding.GetNumPoints(); j++ ) {
    475 				silVerts[j].ToVec2() = obstacle.winding[j];
    476 				silVerts[j].z = startPos.z;
    477 			}
    478 			for ( j = 0; j < obstacle.winding.GetNumPoints(); j++ ) {
    479 				gameRenderWorld->DebugArrow( colorGreen, silVerts[j], silVerts[(j+1)%obstacle.winding.GetNumPoints()], 4 );
    480 			}
    481 		}
    482 	}
    483 
    484 	return numObstacles;
    485 }
    486 
    487 /*
    488 ============
    489 FreePathTree_r
    490 ============
    491 */
    492 void FreePathTree_r( pathNode_t *node ) {
    493 	if ( node->children[0] ) {
    494 		FreePathTree_r( node->children[0] );
    495 	}
    496 	if ( node->children[1] ) {
    497 		FreePathTree_r( node->children[1] );
    498 	}
    499 	pathNodeAllocator.Free( node );
    500 }
    501 
    502 /*
    503 ============
    504 DrawPathTree
    505 ============
    506 */
    507 void DrawPathTree( const pathNode_t *root, const float height ) {
    508 	int i;
    509 	idVec3 start, end;
    510 	const pathNode_t *node;
    511 
    512 	for ( node = root; node; node = node->next ) {
    513 		for ( i = 0; i < 2; i++ ) {
    514 			if ( node->children[i] ) {
    515 				start.ToVec2() = node->pos;
    516 				start.z = height;
    517 				end.ToVec2() = node->children[i]->pos;
    518 				end.z = height;
    519 				gameRenderWorld->DebugArrow( node->edgeNum == -1 ? colorYellow : i ? colorBlue : colorRed, start, end, 1 );
    520 				break;
    521 			}
    522 		}
    523 	}
    524 }
    525 
    526 /*
    527 ============
    528 GetPathNodeDelta
    529 ============
    530 */
    531 bool GetPathNodeDelta( pathNode_t *node, const obstacle_t *obstacles, const idVec2 &seekPos, bool blocked ) {
    532 	int numPoints, edgeNum;
    533 	bool facing;
    534 	idVec2 seekDelta, dir;
    535 	pathNode_t *n;
    536 
    537 	numPoints = obstacles[node->obstacle].winding.GetNumPoints();
    538 
    539 	// get delta along the current edge
    540 	while( 1 ) {
    541 		edgeNum = ( node->edgeNum + node->dir ) % numPoints;
    542 		node->delta = obstacles[node->obstacle].winding[edgeNum] - node->pos;
    543 		if ( node->delta.LengthSqr() > 0.01f ) {
    544 			break;
    545 		}
    546 		node->edgeNum = ( node->edgeNum + numPoints + ( 2 * node->dir - 1 ) ) % numPoints;
    547 	}
    548 
    549 	// if not blocked
    550 	if ( !blocked ) {
    551 
    552 		// test if the current edge faces the goal
    553 		seekDelta = seekPos - node->pos;
    554 		facing = ( ( 2 * node->dir - 1 ) * ( node->delta.x * seekDelta.y - node->delta.y * seekDelta.x ) ) >= 0.0f;
    555 
    556 		// if the current edge faces goal and the line from the current
    557 		// position to the goal does not intersect the current path
    558 		if ( facing && !LineIntersectsPath( node->pos, seekPos, node->parent ) ) {
    559 			node->delta = seekPos - node->pos;
    560 			node->edgeNum = -1;
    561 		}
    562 	}
    563 
    564 	// if the delta is along the obstacle edge
    565 	if ( node->edgeNum != -1 ) {
    566 		// if the edge is found going from this node to the root node
    567 		for ( n = node->parent; n; n = n->parent ) {
    568 
    569 			if ( node->obstacle != n->obstacle || node->edgeNum != n->edgeNum ) {
    570 				continue;
    571 			}
    572 
    573 			// test whether or not the edge segments actually overlap
    574 			if ( n->pos * node->delta > ( node->pos + node->delta ) * node->delta ) {
    575 				continue;
    576 			}
    577 			if ( node->pos * node->delta > ( n->pos + n->delta ) * node->delta ) {
    578 				continue;
    579 			}
    580 
    581 			break;
    582 		}
    583 		if ( n ) {
    584 			return false;
    585 		}
    586 	}
    587 	return true;
    588 }
    589 
    590 /*
    591 ============
    592 BuildPathTree
    593 ============
    594 */
    595 pathNode_t *BuildPathTree( const obstacle_t *obstacles, int numObstacles, const idBounds &clipBounds, const idVec2 &startPos, const idVec2 &seekPos, obstaclePath_t &path ) {
    596 	int blockingEdgeNum, blockingObstacle, obstaclePoints, bestNumNodes = MAX_OBSTACLE_PATH;
    597 	float blockingScale;
    598 	pathNode_t *root, *node, *child;
    599 	// gcc 4.0
    600 	idQueueTemplate<pathNode_t, offsetof( pathNode_t, next ) > pathNodeQueue, treeQueue;
    601 
    602 	root = pathNodeAllocator.Alloc();
    603 	root->Init();
    604 	root->pos = startPos;
    605 
    606 	root->delta = seekPos - root->pos;
    607 	root->numNodes = 0;
    608 	pathNodeQueue.Add( root );
    609 
    610 	for ( node = pathNodeQueue.Get(); node != NULL && pathNodeAllocator.GetAllocCount() < MAX_PATH_NODES; node = pathNodeQueue.Get() ) {
    611 
    612 		treeQueue.Add( node );
    613 
    614 		// if this path has more than twice the number of nodes than the best path so far
    615 		if ( node->numNodes > bestNumNodes * 2 ) {
    616 			continue;
    617 		}
    618 
    619 		// don't move outside of the clip bounds
    620 		idVec2 endPos = node->pos + node->delta;
    621 		if ( endPos.x - CLIP_BOUNDS_EPSILON < clipBounds[0].x || endPos.x + CLIP_BOUNDS_EPSILON > clipBounds[1].x ||
    622 				endPos.y - CLIP_BOUNDS_EPSILON < clipBounds[0].y || endPos.y + CLIP_BOUNDS_EPSILON > clipBounds[1].y ) {
    623 			continue;
    624 		}
    625 
    626 		// if an obstacle is blocking the path
    627 		if ( GetFirstBlockingObstacle( obstacles, numObstacles, node->obstacle, node->pos, node->delta, blockingScale, blockingObstacle, blockingEdgeNum ) ) {
    628 
    629 			if ( path.firstObstacle == NULL ) {
    630 				path.firstObstacle = obstacles[blockingObstacle].entity;
    631 			}
    632 
    633 			node->delta *= blockingScale;
    634 
    635 			if ( node->edgeNum == -1 ) {
    636 				node->children[0] = pathNodeAllocator.Alloc();
    637 				node->children[0]->Init();
    638 				node->children[1] = pathNodeAllocator.Alloc();
    639 				node->children[1]->Init();
    640 				node->children[0]->dir = 0;
    641 				node->children[1]->dir = 1;
    642 				node->children[0]->parent = node->children[1]->parent = node;
    643 				node->children[0]->pos = node->children[1]->pos = node->pos + node->delta;
    644 				node->children[0]->obstacle = node->children[1]->obstacle = blockingObstacle;
    645 				node->children[0]->edgeNum = node->children[1]->edgeNum = blockingEdgeNum;
    646 				node->children[0]->numNodes = node->children[1]->numNodes = node->numNodes + 1;
    647 				if ( GetPathNodeDelta( node->children[0], obstacles, seekPos, true ) ) {
    648 					pathNodeQueue.Add( node->children[0] );
    649 				}
    650 				if ( GetPathNodeDelta( node->children[1], obstacles, seekPos, true ) ) {
    651 					pathNodeQueue.Add( node->children[1] );
    652 				}
    653 			} else {
    654 				node->children[node->dir] = child = pathNodeAllocator.Alloc();
    655 				child->Init();
    656 				child->dir = node->dir;
    657 				child->parent = node;
    658 				child->pos = node->pos + node->delta;
    659 				child->obstacle = blockingObstacle;
    660 				child->edgeNum = blockingEdgeNum;
    661 				child->numNodes = node->numNodes + 1;
    662 				if ( GetPathNodeDelta( child, obstacles, seekPos, true ) ) {
    663 					pathNodeQueue.Add( child );
    664 				}
    665 			}
    666 		} else {
    667 			node->children[node->dir] = child = pathNodeAllocator.Alloc();
    668 			child->Init();
    669 			child->dir = node->dir;
    670 			child->parent = node;
    671 			child->pos = node->pos + node->delta;
    672 			child->numNodes = node->numNodes + 1;
    673 
    674 			// there is a free path towards goal
    675 			if ( node->edgeNum == -1 ) {
    676 				if ( node->numNodes < bestNumNodes ) {
    677 					bestNumNodes = node->numNodes;
    678 				}
    679 				continue;
    680 			}
    681 
    682 			child->obstacle = node->obstacle;
    683 			obstaclePoints = obstacles[node->obstacle].winding.GetNumPoints();
    684 			child->edgeNum = ( node->edgeNum + obstaclePoints + ( 2 * node->dir - 1 ) ) % obstaclePoints;
    685 
    686 			if ( GetPathNodeDelta( child, obstacles, seekPos, false ) ) {
    687 				pathNodeQueue.Add( child );
    688 			}
    689 		}
    690 	}
    691 
    692 	return root;
    693 }
    694 
    695 /*
    696 ============
    697 PrunePathTree
    698 ============
    699 */
    700 void PrunePathTree( pathNode_t *root, const idVec2 &seekPos ) {
    701 	int i;
    702 	float bestDist;
    703 	pathNode_t *node, *lastNode, *n, *bestNode;
    704 
    705 	node = root;
    706 	while( node ) {
    707 
    708 		node->dist = ( seekPos - node->pos ).LengthSqr();
    709 
    710 		if ( node->children[0] ) {
    711 			node = node->children[0];
    712 		} else if ( node->children[1] ) {
    713 			node = node->children[1];
    714 		} else {
    715 
    716 			// find the node closest to the goal along this path
    717 			bestDist = idMath::INFINITY;
    718 			bestNode = node;
    719 			for ( n = node; n; n = n->parent ) {
    720 				if ( n->children[0] && n->children[1] ) {
    721 					break;
    722 				}
    723 				if ( n->dist < bestDist ) {
    724 					bestDist = n->dist;
    725 					bestNode = n;
    726 				}
    727 			}
    728 
    729 			// free tree down from the best node
    730 			for ( i = 0; i < 2; i++ ) {
    731 				if ( bestNode->children[i] ) {
    732 					FreePathTree_r( bestNode->children[i] );
    733 					bestNode->children[i] = NULL;
    734 				}
    735 			}
    736 
    737 			for ( lastNode = bestNode, node = bestNode->parent; node; lastNode = node, node = node->parent ) {
    738 				if ( node->children[1] && ( node->children[1] != lastNode ) ) {
    739 					node = node->children[1];
    740 					break;
    741 				}
    742 			}
    743 		}
    744 	}
    745 }
    746 
    747 /*
    748 ============
    749 OptimizePath
    750 ============
    751 */
    752 int OptimizePath( const pathNode_t *root, const pathNode_t *leafNode, const obstacle_t *obstacles, int numObstacles, idVec2 optimizedPath[MAX_OBSTACLE_PATH] ) {
    753 	int i, numPathPoints, edgeNums[2];
    754 	const pathNode_t *curNode, *nextNode;
    755 	idVec2 curPos, curDelta, bounds[2];
    756 	float scale1, scale2, curLength;
    757 
    758 	optimizedPath[0] = root->pos;
    759 	numPathPoints = 1;
    760 
    761 	for ( nextNode = curNode = root; curNode != leafNode; curNode = nextNode ) {
    762 
    763 		for ( nextNode = leafNode; nextNode->parent != curNode; nextNode = nextNode->parent ) {
    764 
    765 			// can only take shortcuts when going from one object to another
    766 			if ( nextNode->obstacle == curNode->obstacle ) {
    767 				continue;
    768 			}
    769 
    770 			curPos = curNode->pos;
    771 			curDelta = nextNode->pos - curPos;
    772 			curLength = curDelta.Length();
    773 
    774 			// get bounds for the current movement delta
    775 			bounds[0] = curPos - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON );
    776 			bounds[1] = curPos + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON );
    777 			bounds[IEEE_FLT_SIGNBITNOTSET(curDelta.x)].x += curDelta.x;
    778 			bounds[IEEE_FLT_SIGNBITNOTSET(curDelta.y)].y += curDelta.y;
    779 
    780 			// test if the shortcut intersects with any obstacles
    781 			for ( i = 0; i < numObstacles; i++ ) {
    782 				if ( bounds[0].x > obstacles[i].bounds[1].x || bounds[0].y > obstacles[i].bounds[1].y ||
    783 						bounds[1].x < obstacles[i].bounds[0].x || bounds[1].y < obstacles[i].bounds[0].y ) {
    784 					continue;
    785 				}
    786 				if ( obstacles[i].winding.RayIntersection( curPos, curDelta, scale1, scale2, edgeNums ) ) {
    787 					if ( scale1 >= 0.0f && scale1 <= 1.0f && ( i != nextNode->obstacle || scale1 * curLength < curLength - 0.5f ) ) {
    788 						break;
    789 					}
    790 					if ( scale2 >= 0.0f && scale2 <= 1.0f && ( i != nextNode->obstacle || scale2 * curLength < curLength - 0.5f ) ) {
    791 						break;
    792 					}
    793 				}
    794 			}
    795 			if ( i >= numObstacles ) {
    796 				break;
    797 			}
    798 		}
    799 
    800 		// store the next position along the optimized path
    801 		optimizedPath[numPathPoints++] = nextNode->pos;
    802 	}
    803 
    804 	return numPathPoints;
    805 }
    806 
    807 /*
    808 ============
    809 PathLength
    810 ============
    811 */
    812 float PathLength( idVec2 optimizedPath[MAX_OBSTACLE_PATH], int numPathPoints, const idVec2 &curDir ) {
    813 	int i;
    814 	float pathLength;
    815 
    816 	// calculate the path length
    817 	pathLength = 0.0f;
    818 	for ( i = 0; i < numPathPoints-1; i++ ) {
    819 		pathLength += ( optimizedPath[i+1] - optimizedPath[i] ).LengthFast();
    820 	}
    821 
    822 	// add penalty if this path does not go in the current direction
    823 	if ( curDir * ( optimizedPath[1] - optimizedPath[0] ) < 0.0f ) {
    824 		pathLength += 100.0f;
    825 	}
    826 	return pathLength;
    827 }
    828 
    829 /*
    830 ============
    831 FindOptimalPath
    832 
    833   Returns true if there is a path all the way to the goal.
    834 ============
    835 */
    836 bool FindOptimalPath( const pathNode_t *root, const obstacle_t *obstacles, int numObstacles, const float height, const idVec3 &curDir, idVec3 &seekPos ) {
    837 	int i, numPathPoints, bestNumPathPoints;
    838 	const pathNode_t *node, *lastNode, *bestNode;
    839 	idVec2 optimizedPath[MAX_OBSTACLE_PATH];
    840 	float pathLength, bestPathLength;
    841 	bool pathToGoalExists, optimizedPathCalculated;
    842 
    843 	seekPos.Zero();
    844 	seekPos.z = height;
    845 
    846 	pathToGoalExists = false;
    847 	optimizedPathCalculated = false;
    848 
    849 	bestNode = root;
    850 	bestNumPathPoints = 0;
    851 	bestPathLength = idMath::INFINITY;
    852 
    853 	node = root;
    854 	while( node ) {
    855 
    856 		pathToGoalExists |= ( node->dist < 0.1f );
    857 
    858 		if ( node->dist <= bestNode->dist ) {
    859 
    860 			if ( idMath::Fabs( node->dist - bestNode->dist ) < 0.1f ) {
    861 
    862 				if ( !optimizedPathCalculated ) {
    863 					bestNumPathPoints = OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath );
    864 					bestPathLength = PathLength( optimizedPath, bestNumPathPoints, curDir.ToVec2() );
    865 					seekPos.ToVec2() = optimizedPath[1];
    866 				}
    867 
    868 				numPathPoints = OptimizePath( root, node, obstacles, numObstacles, optimizedPath );
    869 				pathLength = PathLength( optimizedPath, numPathPoints, curDir.ToVec2() );
    870 
    871 				if ( pathLength < bestPathLength ) {
    872 					bestNode = node;
    873 					bestNumPathPoints = numPathPoints;
    874 					bestPathLength = pathLength;
    875 					seekPos.ToVec2() = optimizedPath[1];
    876 				}
    877 				optimizedPathCalculated = true;
    878 
    879 			} else {
    880 
    881 				bestNode = node;
    882 				optimizedPathCalculated = false;
    883 			}
    884 		}
    885 
    886 		if ( node->children[0] ) {
    887 			node = node->children[0];
    888 		} else if ( node->children[1] ) {
    889 			node = node->children[1];
    890 		} else {
    891 			for ( lastNode = node, node = node->parent; node; lastNode = node, node = node->parent ) {
    892 				if ( node->children[1] && node->children[1] != lastNode ) {
    893 					node = node->children[1];
    894 					break;
    895 				}
    896 			}
    897 		}
    898 	}
    899 
    900 	if ( root != NULL ) {
    901 		if ( !pathToGoalExists ) {
    902 			if ( root->children[0] != NULL ) {
    903 				seekPos.ToVec2() = root->children[0]->pos;
    904 			} else {
    905 				seekPos.ToVec2() = root->pos;
    906 			}
    907 		} else if ( !optimizedPathCalculated ) {
    908 			OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath );
    909 			seekPos.ToVec2() = optimizedPath[1];
    910 		}
    911 
    912 		if ( ai_showObstacleAvoidance.GetBool() ) {
    913 			idVec3 start, end;
    914 			start.z = end.z = height + 4.0f;
    915 			numPathPoints = OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath );
    916 			for ( i = 0; i < numPathPoints-1; i++ ) {
    917 				start.ToVec2() = optimizedPath[i];
    918 				end.ToVec2() = optimizedPath[i+1];
    919 				gameRenderWorld->DebugArrow( colorCyan, start, end, 1 );
    920 			}
    921 		}
    922 	}
    923 
    924 	return pathToGoalExists;
    925 }
    926 
    927 /*
    928 ============
    929 idAI::FindPathAroundObstacles
    930 
    931   Finds a path around dynamic obstacles using a path tree with clockwise and counter clockwise edge walks.
    932 ============
    933 */
    934 bool idAI::FindPathAroundObstacles( const idPhysics *physics, const idAAS *aas, const idEntity *ignore, const idVec3 &startPos, const idVec3 &seekPos, obstaclePath_t &path ) {
    935 	int numObstacles, areaNum, insideObstacle;
    936 	obstacle_t obstacles[MAX_OBSTACLES];
    937 	idBounds clipBounds;
    938 	idBounds bounds;
    939 	pathNode_t *root;
    940 	bool pathToGoalExists;
    941 
    942 	path.seekPos = seekPos;
    943 	path.firstObstacle = NULL;
    944 	path.startPosOutsideObstacles = startPos;
    945 	path.startPosObstacle = NULL;
    946 	path.seekPosOutsideObstacles = seekPos;
    947 	path.seekPosObstacle = NULL;
    948 
    949 	if ( !aas ) {
    950 		return true;
    951 	}
    952 
    953 	bounds[1] = aas->GetSettings()->boundingBoxes[0][1];
    954 	bounds[0] = -bounds[1];
    955 	bounds[1].z = 32.0f;
    956 
    957 	// get the AAS area number and a valid point inside that area
    958 	areaNum = aas->PointReachableAreaNum( path.startPosOutsideObstacles, bounds, (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) );
    959 	aas->PushPointIntoAreaNum( areaNum, path.startPosOutsideObstacles );
    960 
    961 	// get all the nearby obstacles
    962 	numObstacles = GetObstacles( physics, aas, ignore, areaNum, path.startPosOutsideObstacles, path.seekPosOutsideObstacles, obstacles, MAX_OBSTACLES, clipBounds );
    963 
    964 	// get a source position outside the obstacles
    965 	GetPointOutsideObstacles( obstacles, numObstacles, path.startPosOutsideObstacles.ToVec2(), &insideObstacle, NULL );
    966 	if ( insideObstacle != -1 ) {
    967 		path.startPosObstacle = obstacles[insideObstacle].entity;
    968 	}
    969 
    970 	// get a goal position outside the obstacles
    971 	GetPointOutsideObstacles( obstacles, numObstacles, path.seekPosOutsideObstacles.ToVec2(), &insideObstacle, NULL );
    972 	if ( insideObstacle != -1 ) {
    973 		path.seekPosObstacle = obstacles[insideObstacle].entity;
    974 	}
    975 
    976 	// if start and destination are pushed to the same point, we don't have a path around the obstacle
    977 	if ( ( path.seekPosOutsideObstacles.ToVec2() - path.startPosOutsideObstacles.ToVec2() ).LengthSqr() < Square( 1.0f ) ) {
    978 		if ( ( seekPos.ToVec2() - startPos.ToVec2() ).LengthSqr() > Square( 2.0f ) ) {
    979 			return false;
    980 		}
    981 	}
    982 
    983 	// build a path tree
    984 	root = BuildPathTree( obstacles, numObstacles, clipBounds, path.startPosOutsideObstacles.ToVec2(), path.seekPosOutsideObstacles.ToVec2(), path );
    985 
    986 	// draw the path tree
    987 	if ( ai_showObstacleAvoidance.GetBool() ) {
    988 		DrawPathTree( root, physics->GetOrigin().z );
    989 	}
    990 
    991 	// prune the tree
    992 	PrunePathTree( root, path.seekPosOutsideObstacles.ToVec2() );
    993 
    994 	// find the optimal path
    995 	pathToGoalExists = FindOptimalPath( root, obstacles, numObstacles, physics->GetOrigin().z, physics->GetLinearVelocity(), path.seekPos );
    996 
    997 	// free the tree
    998 	FreePathTree_r( root );
    999 
   1000 	return pathToGoalExists;
   1001 }
   1002 
   1003 /*
   1004 ============
   1005 idAI::FreeObstacleAvoidanceNodes
   1006 ============
   1007 */
   1008 void idAI::FreeObstacleAvoidanceNodes() {
   1009 	pathNodeAllocator.Shutdown();
   1010 }
   1011 
   1012 
   1013 /*
   1014 ===============================================================================
   1015 
   1016 	Path Prediction
   1017 
   1018 	Uses the AAS to quickly and accurately predict a path for a certain
   1019 	period of time based on an initial position and velocity.
   1020 
   1021 ===============================================================================
   1022 */
   1023 
   1024 const float OVERCLIP			= 1.001f;
   1025 const int MAX_FRAME_SLIDE		= 5;
   1026 
   1027 typedef struct pathTrace_s {
   1028 	float					fraction;
   1029 	idVec3					endPos;
   1030 	idVec3					normal;
   1031 	const idEntity *		blockingEntity;
   1032 } pathTrace_t;
   1033 
   1034 /*
   1035 ============
   1036 PathTrace
   1037 
   1038   Returns true if a stop event was triggered.
   1039 ============
   1040 */
   1041 bool PathTrace( const idEntity *ent, const idAAS *aas, const idVec3 &start, const idVec3 &end, int stopEvent, struct pathTrace_s &trace, predictedPath_t &path ) {
   1042 	trace_t clipTrace;
   1043 	aasTrace_t aasTrace;
   1044 
   1045 	memset( &trace, 0, sizeof( trace ) );
   1046 
   1047 	if ( !aas || !aas->GetSettings() ) {
   1048 
   1049 		gameLocal.clip.Translation( clipTrace, start, end, ent->GetPhysics()->GetClipModel(),
   1050 									ent->GetPhysics()->GetClipModel()->GetAxis(), MASK_MONSTERSOLID, ent );
   1051 
   1052 		// NOTE: could do (expensive) ledge detection here for when there is no AAS file
   1053 
   1054 		trace.fraction = clipTrace.fraction;
   1055 		trace.endPos = clipTrace.endpos;
   1056 		trace.normal = clipTrace.c.normal;
   1057 		trace.blockingEntity = gameLocal.entities[ clipTrace.c.entityNum ];
   1058 	} else {
   1059 		aasTrace.getOutOfSolid = true;
   1060 		if ( stopEvent & SE_ENTER_LEDGE_AREA ) {
   1061 			aasTrace.flags |= AREA_LEDGE;
   1062 		}
   1063 		if ( stopEvent & SE_ENTER_OBSTACLE ) {
   1064 			aasTrace.travelFlags |= TFL_INVALID;
   1065 		}
   1066 
   1067 		aas->Trace( aasTrace, start, end );
   1068 
   1069 		gameLocal.clip.TranslationEntities( clipTrace, start, aasTrace.endpos, ent->GetPhysics()->GetClipModel(),
   1070 											ent->GetPhysics()->GetClipModel()->GetAxis(), MASK_MONSTERSOLID, ent );
   1071 
   1072 		if ( clipTrace.fraction >= 1.0f ) {
   1073 
   1074 			trace.fraction = aasTrace.fraction;
   1075 			trace.endPos = aasTrace.endpos;
   1076 			trace.normal = aas->GetPlane( aasTrace.planeNum ).Normal();
   1077 			trace.blockingEntity = gameLocal.world;
   1078 
   1079 			if ( aasTrace.fraction < 1.0f ) {
   1080 				if ( stopEvent & SE_ENTER_LEDGE_AREA ) {
   1081 					if ( aas->AreaFlags( aasTrace.blockingAreaNum ) & AREA_LEDGE ) {
   1082 						path.endPos = trace.endPos;
   1083 						path.endNormal = trace.normal;
   1084 						path.endEvent = SE_ENTER_LEDGE_AREA;
   1085 						path.blockingEntity = trace.blockingEntity;
   1086 
   1087 						if ( ai_debugMove.GetBool() ) {
   1088 							gameRenderWorld->DebugLine( colorRed, start, aasTrace.endpos );
   1089 						}
   1090 						return true;
   1091 					}
   1092 				}
   1093 				if ( stopEvent & SE_ENTER_OBSTACLE ) {
   1094 					if ( aas->AreaTravelFlags( aasTrace.blockingAreaNum ) & TFL_INVALID ) {
   1095 						path.endPos = trace.endPos;
   1096 						path.endNormal = trace.normal;
   1097 						path.endEvent = SE_ENTER_OBSTACLE;
   1098 						path.blockingEntity = trace.blockingEntity;
   1099 
   1100 						if ( ai_debugMove.GetBool() ) {
   1101 							gameRenderWorld->DebugLine( colorRed, start, aasTrace.endpos );
   1102 						}
   1103 						return true;
   1104 					}
   1105 				}
   1106 			}
   1107 		} else {
   1108 			trace.fraction = clipTrace.fraction;
   1109 			trace.endPos = clipTrace.endpos;
   1110 			trace.normal = clipTrace.c.normal;
   1111 			trace.blockingEntity = gameLocal.entities[ clipTrace.c.entityNum ];
   1112 		}
   1113 	}
   1114 
   1115 	if ( trace.fraction >= 1.0f ) {
   1116 		trace.blockingEntity = NULL;
   1117 	}
   1118 
   1119 	return false;
   1120 }
   1121 
   1122 /*
   1123 ============
   1124 idAI::PredictPath
   1125 
   1126   Can also be used when there is no AAS file available however ledges are not detected.
   1127 ============
   1128 */
   1129 bool idAI::PredictPath( const idEntity *ent, const idAAS *aas, const idVec3 &start, const idVec3 &velocity, int totalTime, int frameTime, int stopEvent, predictedPath_t &path ) {
   1130 	int i, j, step, numFrames, curFrameTime;
   1131 	idVec3 delta, curStart, curEnd, curVelocity, lastEnd, stepUp, tmpStart;
   1132 	idVec3 gravity, gravityDir, invGravityDir;
   1133 	float maxStepHeight, minFloorCos;
   1134 	pathTrace_t trace;
   1135 
   1136 	if ( aas && aas->GetSettings() ) {
   1137 		gravity = aas->GetSettings()->gravity;
   1138 		gravityDir = aas->GetSettings()->gravityDir;
   1139 		invGravityDir = aas->GetSettings()->invGravityDir;
   1140 		maxStepHeight = aas->GetSettings()->maxStepHeight;
   1141 		minFloorCos = aas->GetSettings()->minFloorCos;
   1142 	} else {
   1143 		gravity = DEFAULT_GRAVITY_VEC3;
   1144 		gravityDir = idVec3( 0, 0, -1 );
   1145 		invGravityDir = idVec3( 0, 0, 1 );
   1146 		maxStepHeight = 14.0f;
   1147 		minFloorCos = 0.7f;
   1148 	}
   1149 
   1150 	path.endPos = start;
   1151 	path.endVelocity = velocity;
   1152 	path.endNormal.Zero();
   1153 	path.endEvent = 0;
   1154 	path.endTime = 0;
   1155 	path.blockingEntity = NULL;
   1156 
   1157 	curStart = start;
   1158 	curVelocity = velocity;
   1159 
   1160 	numFrames = ( totalTime + frameTime - 1 ) / frameTime;
   1161 	curFrameTime = frameTime;
   1162 	for ( i = 0; i < numFrames; i++ ) {
   1163 
   1164 		if ( i == numFrames-1 ) {
   1165 			curFrameTime = totalTime - i * curFrameTime;
   1166 		}
   1167 
   1168 		delta = curVelocity * curFrameTime * 0.001f;
   1169 
   1170 		path.endVelocity = curVelocity;
   1171 		path.endTime = i * frameTime;
   1172 
   1173 		// allow sliding along a few surfaces per frame
   1174 		for ( j = 0; j < MAX_FRAME_SLIDE; j++ ) {
   1175 
   1176 			idVec3 lineStart = curStart;
   1177 
   1178 			// allow stepping up three times per frame
   1179 			for ( step = 0; step < 3; step++ ) {
   1180 
   1181 				curEnd = curStart + delta;
   1182 				if ( PathTrace( ent, aas, curStart, curEnd, stopEvent, trace, path ) ) {
   1183 					return true;
   1184 				}
   1185 
   1186 				if ( step ) {
   1187 
   1188 					// step down at end point
   1189 					tmpStart = trace.endPos;
   1190 					curEnd = tmpStart - stepUp;
   1191 					if ( PathTrace( ent, aas, tmpStart, curEnd, stopEvent, trace, path ) ) {
   1192 						return true;
   1193 					}
   1194 
   1195 					// if not moved any further than without stepping up, or if not on a floor surface
   1196 					if ( (lastEnd - start).LengthSqr() > (trace.endPos - start).LengthSqr() - 0.1f ||
   1197 								( trace.normal * invGravityDir ) < minFloorCos ) {
   1198 						if ( stopEvent & SE_BLOCKED ) {
   1199 							path.endPos = lastEnd;
   1200 							path.endEvent = SE_BLOCKED;
   1201 
   1202 							if ( ai_debugMove.GetBool() ) {
   1203 								gameRenderWorld->DebugLine( colorRed, lineStart, lastEnd );
   1204 							}
   1205 
   1206 							return true;
   1207 						}
   1208 
   1209 						curStart = lastEnd;
   1210 						break;
   1211 					}
   1212 				}
   1213 
   1214 				path.endNormal = trace.normal;
   1215 				path.blockingEntity = trace.blockingEntity;
   1216 
   1217 				// if the trace is not blocked or blocked by a floor surface
   1218 				if ( trace.fraction >= 1.0f || ( trace.normal * invGravityDir ) > minFloorCos ) {
   1219 					curStart = trace.endPos;
   1220 					break;
   1221 				}
   1222 
   1223 				// save last result
   1224 				lastEnd = trace.endPos;
   1225 
   1226 				// step up
   1227 				stepUp = invGravityDir * maxStepHeight;
   1228 				if ( PathTrace( ent, aas, curStart, curStart + stepUp, stopEvent, trace, path ) ) {
   1229 					return true;
   1230 				}
   1231 				stepUp *= trace.fraction;
   1232 				curStart = trace.endPos;
   1233 			}
   1234 
   1235 			if ( ai_debugMove.GetBool() ) {
   1236 				gameRenderWorld->DebugLine( colorRed, lineStart, curStart );
   1237 			}
   1238 
   1239 			if ( trace.fraction >= 1.0f ) {
   1240 				break;
   1241 			}
   1242 
   1243 			delta.ProjectOntoPlane( trace.normal, OVERCLIP );
   1244 			curVelocity.ProjectOntoPlane( trace.normal, OVERCLIP );
   1245 
   1246 			if ( stopEvent & SE_BLOCKED ) {
   1247 				// if going backwards
   1248 				if ( (curVelocity - gravityDir * curVelocity * gravityDir ) *
   1249 						(velocity - gravityDir * velocity * gravityDir) < 0.0f ) {
   1250 					path.endPos = curStart;
   1251 					path.endEvent = SE_BLOCKED;
   1252 
   1253 					return true;
   1254 				}
   1255 			}
   1256 		}
   1257 
   1258 		if ( j >= MAX_FRAME_SLIDE ) {
   1259 			if ( stopEvent & SE_BLOCKED ) {
   1260 				path.endPos = curStart;
   1261 				path.endEvent = SE_BLOCKED;
   1262 				return true;
   1263 			}
   1264 		}
   1265 
   1266 		// add gravity
   1267 		curVelocity += gravity * frameTime * 0.001f;
   1268 	}
   1269 
   1270 	path.endTime = totalTime;
   1271 	path.endVelocity = curVelocity;
   1272 	path.endPos = curStart;
   1273 	path.endEvent = 0;
   1274 
   1275 	return false;
   1276 }
   1277 
   1278 
   1279 /*
   1280 ===============================================================================
   1281 
   1282 	Trajectory Prediction
   1283 
   1284 	Finds the best collision free trajectory for a clip model based on an
   1285 	initial position, target position and speed.
   1286 
   1287 ===============================================================================
   1288 */
   1289 
   1290 /*
   1291 =====================
   1292 Ballistics
   1293 
   1294   get the ideal aim pitch angle in order to hit the target
   1295   also get the time it takes for the projectile to arrive at the target
   1296 =====================
   1297 */
   1298 
   1299 int Ballistics( const idVec3 &start, const idVec3 &end, float speed, float gravity, ballistics_t bal[2] ) {
   1300 	int n, i;
   1301 	float x, y, a, b, c, d, sqrtd, inva, p[2];
   1302 
   1303 	x = ( end.ToVec2() - start.ToVec2() ).Length();
   1304 	y = end[2] - start[2];
   1305 
   1306 	a = 4.0f * y * y + 4.0f * x * x;
   1307 	b = -4.0f * speed * speed - 4.0f * y * gravity;
   1308 	c = gravity * gravity;
   1309 
   1310 	d = b * b - 4.0f * a * c;
   1311 	if ( d <= 0.0f || a == 0.0f ) {
   1312 		return 0;
   1313 	}
   1314 	sqrtd = idMath::Sqrt( d );
   1315 	inva = 0.5f / a;
   1316 	p[0] = ( - b + sqrtd ) * inva;
   1317 	p[1] = ( - b - sqrtd ) * inva;
   1318 	n = 0;
   1319 	for ( i = 0; i < 2; i++ ) {
   1320 		if ( p[i] <= 0.0f ) {
   1321 			continue;
   1322 		}
   1323 		d = idMath::Sqrt( p[i] );
   1324 		bal[n].angle = atan2( 0.5f * ( 2.0f * y * p[i] - gravity ) / d, d * x );
   1325 		bal[n].time = x / ( cos( bal[n].angle ) * speed );
   1326 		bal[n].angle = idMath::AngleNormalize180( RAD2DEG( bal[n].angle ) );
   1327 		n++;
   1328 	}
   1329 
   1330 	return n;
   1331 }
   1332 
   1333 #if 0 
   1334 // not used
   1335 /*
   1336 =====================
   1337 HeightForTrajectory
   1338 
   1339 Returns the maximum hieght of a given trajectory
   1340 =====================
   1341 */
   1342 static float HeightForTrajectory( const idVec3 &start, float zVel, float gravity ) {
   1343 	float maxHeight, t;
   1344 
   1345 	t = zVel / gravity;
   1346 	// maximum height of projectile
   1347 	maxHeight = start.z - 0.5f * gravity * ( t * t );
   1348 	
   1349 	return maxHeight;
   1350 }
   1351 #endif
   1352 
   1353 /*
   1354 =====================
   1355 idAI::TestTrajectory
   1356 =====================
   1357 */
   1358 bool idAI::TestTrajectory( const idVec3 &start, const idVec3 &end, float zVel, float gravity, float time, float max_height, const idClipModel *clip, int clipmask, const idEntity *ignore, const idEntity *targetEntity, int drawtime ) {
   1359 	int i, numSegments;
   1360 	float maxHeight, t, t2;
   1361 	idVec3 points[5];
   1362 	trace_t trace;
   1363 	bool result;
   1364 
   1365 	t = zVel / gravity;
   1366 	// maximum height of projectile
   1367 	maxHeight = start.z - 0.5f * gravity * ( t * t );
   1368 	// time it takes to fall from the top to the end height
   1369 	t = idMath::Sqrt( ( maxHeight - end.z ) / ( 0.5f * -gravity ) );
   1370 
   1371 	// start of parabolic
   1372 	points[0] = start;
   1373 
   1374 	if ( t < time ) {
   1375 		numSegments = 4;
   1376 		// point in the middle between top and start
   1377 		t2 = ( time - t ) * 0.5f;
   1378 		points[1].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time );
   1379 		points[1].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2;
   1380 		// top of parabolic
   1381 		t2 = time - t;
   1382 		points[2].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time );
   1383 		points[2].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2;
   1384 		// point in the middel between top and end
   1385 		t2 = time - t * 0.5f;
   1386 		points[3].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time );
   1387 		points[3].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2;
   1388 	} else {
   1389 		numSegments = 2;
   1390 		// point halfway through
   1391 		t2 = time * 0.5f;
   1392 		points[1].ToVec2() = start.ToVec2() + ( end.ToVec2() - start.ToVec2() ) * 0.5f;
   1393 		points[1].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2;
   1394 	}
   1395 
   1396 	// end of parabolic
   1397 	points[numSegments] = end;
   1398 
   1399 	if ( drawtime ) {
   1400 		for ( i = 0; i < numSegments; i++ ) {
   1401 			gameRenderWorld->DebugLine( colorRed, points[i], points[i+1], drawtime );
   1402 		}
   1403 	}
   1404 
   1405 	// make sure projectile doesn't go higher than we want it to go
   1406 	for ( i = 0; i < numSegments; i++ ) {
   1407 		if ( points[i].z > max_height ) {
   1408 			// goes higher than we want to allow
   1409 			return false;
   1410 		}
   1411 	}
   1412 
   1413 	result = true;
   1414 	for ( i = 0; i < numSegments; i++ ) {
   1415 		gameLocal.clip.Translation( trace, points[i], points[i+1], clip, mat3_identity, clipmask, ignore );
   1416 		if ( trace.fraction < 1.0f ) {
   1417 			if ( gameLocal.GetTraceEntity( trace ) == targetEntity ) {
   1418 				result = true;
   1419 			} else {
   1420 				result = false;
   1421 			}
   1422 			break;
   1423 		}
   1424 	}
   1425 
   1426 	if ( drawtime ) {
   1427 		if ( clip ) {
   1428 			gameRenderWorld->DebugBounds( result ? colorGreen : colorYellow, clip->GetBounds().Expand( 1.0f ), trace.endpos, drawtime );
   1429 		} else {
   1430 			idBounds bnds( trace.endpos );
   1431 			bnds.ExpandSelf( 1.0f );
   1432 			gameRenderWorld->DebugBounds( result ? colorGreen : colorYellow, bnds, vec3_zero, drawtime );
   1433 		}
   1434 	}
   1435 
   1436 	return result;
   1437 }
   1438 
   1439 /*
   1440 =====================
   1441 idAI::PredictTrajectory
   1442 
   1443   returns true if there is a collision free trajectory for the clip model
   1444   aimDir is set to the ideal aim direction in order to hit the target
   1445 =====================
   1446 */
   1447 bool idAI::PredictTrajectory( const idVec3 &firePos, const idVec3 &target, float projectileSpeed, const idVec3 &projGravity, const idClipModel *clip, int clipmask, float max_height, const idEntity *ignore, const idEntity *targetEntity, int drawtime, idVec3 &aimDir ) {
   1448 	int n, i, j;
   1449 	float zVel, a, t, pitch, s, c;
   1450 	trace_t trace;
   1451 	ballistics_t ballistics[2];
   1452 	idVec3 dir[2];
   1453 	idVec3 velocity;
   1454 	idVec3 lastPos, pos;
   1455 
   1456 	if ( targetEntity == NULL ) {
   1457 		return false;
   1458 	}
   1459 
   1460 	// check if the projectile starts inside the target
   1461 	if ( targetEntity->GetPhysics()->GetAbsBounds().IntersectsBounds( clip->GetBounds().Translate( firePos ) ) ) {
   1462 		aimDir = target - firePos;
   1463 		aimDir.Normalize();
   1464 		return true;
   1465 	}
   1466 
   1467 	// if no velocity or the projectile is not affected by gravity
   1468 	if ( projectileSpeed <= 0.0f || projGravity == vec3_origin ) {
   1469 
   1470 		aimDir = target - firePos;
   1471 		aimDir.Normalize();
   1472 
   1473 		gameLocal.clip.Translation( trace, firePos, target, clip, mat3_identity, clipmask, ignore );
   1474 
   1475 		if ( drawtime ) {
   1476 			gameRenderWorld->DebugLine( colorRed, firePos, target, drawtime );
   1477 			idBounds bnds( trace.endpos );
   1478 			bnds.ExpandSelf( 1.0f );
   1479 			gameRenderWorld->DebugBounds( ( trace.fraction >= 1.0f || ( gameLocal.GetTraceEntity( trace ) == targetEntity ) ) ? colorGreen : colorYellow, bnds, vec3_zero, drawtime );
   1480 		}
   1481 
   1482 		return ( trace.fraction >= 1.0f || ( gameLocal.GetTraceEntity( trace ) == targetEntity ) );
   1483 	}
   1484 
   1485 	n = Ballistics( firePos, target, projectileSpeed, projGravity[2], ballistics );
   1486 	if ( n == 0 ) {
   1487 		// there is no valid trajectory
   1488 		aimDir = target - firePos;
   1489 		aimDir.Normalize();
   1490 		return false;
   1491 	}
   1492 
   1493 	// make sure the first angle is the smallest
   1494 	if ( n == 2 ) {
   1495 		if ( ballistics[1].angle < ballistics[0].angle ) {
   1496 			a = ballistics[0].angle; ballistics[0].angle = ballistics[1].angle; ballistics[1].angle = a;
   1497 			t = ballistics[0].time; ballistics[0].time = ballistics[1].time; ballistics[1].time = t;
   1498 		}
   1499 	}
   1500 
   1501 	// test if there is a collision free trajectory
   1502 	for ( i = 0; i < n; i++ ) {
   1503 		pitch = DEG2RAD( ballistics[i].angle );
   1504 		idMath::SinCos( pitch, s, c );
   1505 		dir[i] = target - firePos;
   1506 		dir[i].z = 0.0f;
   1507 		dir[i] *= c * idMath::InvSqrt( dir[i].LengthSqr() );
   1508 		dir[i].z = s;
   1509 
   1510 		zVel = projectileSpeed * dir[i].z;
   1511 
   1512 		if ( ai_debugTrajectory.GetBool() ) {
   1513 			t = ballistics[i].time / 100.0f;
   1514 			velocity = dir[i] * projectileSpeed;
   1515 			lastPos = firePos;
   1516 			pos = firePos;
   1517 			for ( j = 1; j < 100; j++ ) {
   1518 				pos += velocity * t;
   1519 				velocity += projGravity * t;
   1520 				gameRenderWorld->DebugLine( colorCyan, lastPos, pos );
   1521 				lastPos = pos;
   1522 			}
   1523 		}
   1524 
   1525 		if ( TestTrajectory( firePos, target, zVel, projGravity[2], ballistics[i].time, firePos.z + max_height, clip, clipmask, ignore, targetEntity, drawtime ) ) {
   1526 			aimDir = dir[i];
   1527 			return true;
   1528 		}
   1529 	}
   1530 
   1531 	aimDir = dir[0];
   1532 
   1533 	// there is no collision free trajectory
   1534 	return false;
   1535 }