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 }