123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536 |
- /*
- ===========================================================================
- Doom 3 BFG Edition GPL Source Code
- Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
- This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
- Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
- 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.
- 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.
- ===========================================================================
- */
- #pragma hdrstop
- #include "../../idlib/precompiled.h"
- #include "../Game_local.h"
- /*
- ===============================================================================
- Dynamic Obstacle Avoidance
- - assumes the AI lives inside a bounding box aligned with the gravity direction
- - obstacles in proximity of the AI are gathered
- - if obstacles are found the AAS walls are also considered as obstacles
- - every obstacle is represented by an oriented bounding box (OBB)
- - an OBB is projected onto a 2D plane orthogonal to AI's gravity direction
- - the 2D windings of the projections are expanded for the AI bbox
- - a path tree is build using clockwise and counter clockwise edge walks along the winding edges
- - the path tree is pruned and optimized
- - the shortest path is chosen for navigation
- ===============================================================================
- */
- const float MAX_OBSTACLE_RADIUS = 256.0f;
- const float PUSH_OUTSIDE_OBSTACLES = 0.5f;
- const float CLIP_BOUNDS_EPSILON = 10.0f;
- const int MAX_AAS_WALL_EDGES = 256;
- const int MAX_OBSTACLES = 256;
- const int MAX_PATH_NODES = 256;
- const int MAX_OBSTACLE_PATH = 64;
- typedef struct obstacle_s {
- idVec2 bounds[2];
- idWinding2D winding;
- idEntity * entity;
- } obstacle_t;
- typedef struct pathNode_s {
- int dir;
- idVec2 pos;
- idVec2 delta;
- float dist;
- int obstacle;
- int edgeNum;
- int numNodes;
- struct pathNode_s * parent;
- struct pathNode_s * children[2];
- struct pathNode_s * next;
- void Init();
- } pathNode_t;
- void pathNode_s::Init() {
- dir = 0;
- pos.Zero();
- delta.Zero();
- obstacle = -1;
- edgeNum = -1;
- numNodes = 0;
- parent = children[0] = children[1] = next = NULL;
- }
- idBlockAlloc<pathNode_t, 128> pathNodeAllocator;
- /*
- ============
- LineIntersectsPath
- ============
- */
- bool LineIntersectsPath( const idVec2 &start, const idVec2 &end, const pathNode_t *node ) {
- float d0, d1, d2, d3;
- idVec3 plane1, plane2;
- plane1 = idWinding2D::Plane2DFromPoints( start, end );
- d0 = plane1.x * node->pos.x + plane1.y * node->pos.y + plane1.z;
- while( node->parent ) {
- d1 = plane1.x * node->parent->pos.x + plane1.y * node->parent->pos.y + plane1.z;
- if ( IEEE_FLT_SIGNBITSET( d0 ) ^ IEEE_FLT_SIGNBITSET( d1 ) ) {
- plane2 = idWinding2D::Plane2DFromPoints( node->pos, node->parent->pos );
- d2 = plane2.x * start.x + plane2.y * start.y + plane2.z;
- d3 = plane2.x * end.x + plane2.y * end.y + plane2.z;
- if ( IEEE_FLT_SIGNBITSET( d2 ) ^ IEEE_FLT_SIGNBITSET( d3 ) ) {
- return true;
- }
- }
- d0 = d1;
- node = node->parent;
- }
- return false;
- }
- /*
- ============
- PointInsideObstacle
- ============
- */
- int PointInsideObstacle( const obstacle_t *obstacles, const int numObstacles, const idVec2 &point ) {
- int i;
- for ( i = 0; i < numObstacles; i++ ) {
- const idVec2 *bounds = obstacles[i].bounds;
- if ( point.x < bounds[0].x || point.y < bounds[0].y || point.x > bounds[1].x || point.y > bounds[1].y ) {
- continue;
- }
- if ( !obstacles[i].winding.PointInside( point, 0.1f ) ) {
- continue;
- }
- return i;
- }
- return -1;
- }
- /*
- ============
- GetPointOutsideObstacles
- ============
- */
- void GetPointOutsideObstacles( const obstacle_t *obstacles, const int numObstacles, idVec2 &point, int *obstacle, int *edgeNum ) {
- int i, j, k, n, bestObstacle, bestEdgeNum, queueStart, queueEnd, edgeNums[2];
- float d, bestd, scale[2];
- idVec3 plane, bestPlane;
- idVec2 newPoint, dir, bestPoint;
- int *queue;
- bool *obstacleVisited;
- idWinding2D w1, w2;
- if ( obstacle ) {
- *obstacle = -1;
- }
- if ( edgeNum ) {
- *edgeNum = -1;
- }
- bestObstacle = PointInsideObstacle( obstacles, numObstacles, point );
- if ( bestObstacle == -1 ) {
- return;
- }
- const idWinding2D &w = obstacles[bestObstacle].winding;
- bestd = idMath::INFINITY;
- bestEdgeNum = 0;
- for ( i = 0; i < w.GetNumPoints(); i++ ) {
- plane = idWinding2D::Plane2DFromPoints( w[(i+1)%w.GetNumPoints()], w[i], true );
- d = plane.x * point.x + plane.y * point.y + plane.z;
- if ( d < bestd ) {
- bestd = d;
- bestPlane = plane;
- bestEdgeNum = i;
- }
- // if this is a wall always try to pop out at the first edge
- if ( obstacles[bestObstacle].entity == NULL ) {
- break;
- }
- }
- newPoint = point - ( bestd + PUSH_OUTSIDE_OBSTACLES ) * bestPlane.ToVec2();
- if ( PointInsideObstacle( obstacles, numObstacles, newPoint ) == -1 ) {
- point = newPoint;
- if ( obstacle ) {
- *obstacle = bestObstacle;
- }
- if ( edgeNum ) {
- *edgeNum = bestEdgeNum;
- }
- return;
- }
- queue = (int *) _alloca( numObstacles * sizeof( queue[0] ) );
- obstacleVisited = (bool *) _alloca( numObstacles * sizeof( obstacleVisited[0] ) );
- queueStart = 0;
- queueEnd = 1;
- queue[0] = bestObstacle;
- memset( obstacleVisited, 0, numObstacles * sizeof( obstacleVisited[0] ) );
- assert( bestObstacle < numObstacles );
- obstacleVisited[bestObstacle] = true;
- bestd = idMath::INFINITY;
- for ( i = queue[0]; queueStart < queueEnd; i = queue[++queueStart] ) {
- w1 = obstacles[i].winding;
- w1.Expand( PUSH_OUTSIDE_OBSTACLES );
- for ( j = 0; j < numObstacles; j++ ) {
- // if the obstacle has been visited already
- if ( obstacleVisited[j] ) {
- continue;
- }
- // if the bounds do not intersect
- if ( obstacles[j].bounds[0].x > obstacles[i].bounds[1].x || obstacles[j].bounds[0].y > obstacles[i].bounds[1].y ||
- obstacles[j].bounds[1].x < obstacles[i].bounds[0].x || obstacles[j].bounds[1].y < obstacles[i].bounds[0].y ) {
- continue;
- }
- assert( queueEnd < numObstacles );
- queue[queueEnd++] = j;
- obstacleVisited[j] = true;
- w2 = obstacles[j].winding;
- w2.Expand( 0.2f );
- for ( k = 0; k < w1.GetNumPoints(); k++ ) {
- dir = w1[(k+1)%w1.GetNumPoints()] - w1[k];
- if ( !w2.RayIntersection( w1[k], dir, scale[0], scale[1], edgeNums ) ) {
- continue;
- }
- for ( n = 0; n < 2; n++ ) {
- newPoint = w1[k] + scale[n] * dir;
- if ( PointInsideObstacle( obstacles, numObstacles, newPoint ) == -1 ) {
- d = ( newPoint - point ).LengthSqr();
- if ( d < bestd ) {
- bestd = d;
- bestPoint = newPoint;
- bestEdgeNum = edgeNums[n];
- bestObstacle = j;
- }
- }
- }
- }
- }
- if ( bestd < idMath::INFINITY ) {
- point = bestPoint;
- if ( obstacle ) {
- *obstacle = bestObstacle;
- }
- if ( edgeNum ) {
- *edgeNum = bestEdgeNum;
- }
- return;
- }
- }
- gameLocal.Warning( "GetPointOutsideObstacles: no valid point found" );
- }
- /*
- ============
- GetFirstBlockingObstacle
- ============
- */
- bool GetFirstBlockingObstacle( const obstacle_t *obstacles, int numObstacles, int skipObstacle, const idVec2 &startPos, const idVec2 &delta, float &blockingScale, int &blockingObstacle, int &blockingEdgeNum ) {
- int i, edgeNums[2];
- float dist, scale1, scale2;
- idVec2 bounds[2];
- // get bounds for the current movement delta
- bounds[0] = startPos - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON );
- bounds[1] = startPos + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON );
- bounds[IEEE_FLT_SIGNBITNOTSET(delta.x)].x += delta.x;
- bounds[IEEE_FLT_SIGNBITNOTSET(delta.y)].y += delta.y;
- // test for obstacles blocking the path
- blockingScale = idMath::INFINITY;
- dist = delta.Length();
- for ( i = 0; i < numObstacles; i++ ) {
- if ( i == skipObstacle ) {
- continue;
- }
- if ( bounds[0].x > obstacles[i].bounds[1].x || bounds[0].y > obstacles[i].bounds[1].y ||
- bounds[1].x < obstacles[i].bounds[0].x || bounds[1].y < obstacles[i].bounds[0].y ) {
- continue;
- }
- if ( obstacles[i].winding.RayIntersection( startPos, delta, scale1, scale2, edgeNums ) ) {
- if ( scale1 < blockingScale && scale1 * dist > -0.01f && scale2 * dist > 0.01f ) {
- blockingScale = scale1;
- blockingObstacle = i;
- blockingEdgeNum = edgeNums[0];
- }
- }
- }
- return ( blockingScale < 1.0f );
- }
- /*
- ============
- GetObstacles
- ============
- */
- 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 ) {
- int i, j, numListedClipModels, numObstacles, numVerts, clipMask, blockingObstacle, blockingEdgeNum;
- int wallEdges[MAX_AAS_WALL_EDGES], numWallEdges, verts[2], lastVerts[2], nextVerts[2];
- float stepHeight, headHeight, blockingScale, min, max;
- idVec3 seekDelta, silVerts[32], start, end, nextStart, nextEnd;
- idVec2 expBounds[2], edgeDir, edgeNormal, nextEdgeDir, nextEdgeNormal, lastEdgeNormal;
- idVec2 obDelta;
- idPhysics *obPhys;
- idBox box;
- idEntity *obEnt;
- idClipModel *clipModel;
- idClipModel *clipModelList[ MAX_GENTITIES ];
- numObstacles = 0;
- seekDelta = seekPos - startPos;
- expBounds[0] = physics->GetBounds()[0].ToVec2() - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON );
- expBounds[1] = physics->GetBounds()[1].ToVec2() + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON );
- physics->GetAbsBounds().AxisProjection( -physics->GetGravityNormal(), stepHeight, headHeight );
- stepHeight += aas->GetSettings()->maxStepHeight;
- // clip bounds for the obstacle search space
- clipBounds[0] = clipBounds[1] = startPos;
- clipBounds.AddPoint( seekPos );
- clipBounds.ExpandSelf( MAX_OBSTACLE_RADIUS );
- clipMask = physics->GetClipMask();
- // find all obstacles touching the clip bounds
- numListedClipModels = gameLocal.clip.ClipModelsTouchingBounds( clipBounds, clipMask, clipModelList, MAX_GENTITIES );
- for ( i = 0; i < numListedClipModels && numObstacles < MAX_OBSTACLES; i++ ) {
- clipModel = clipModelList[i];
- obEnt = clipModel->GetEntity();
- if ( !clipModel->IsTraceModel() ) {
- continue;
- }
- if ( obEnt->IsType( idActor::Type ) ) {
- obPhys = obEnt->GetPhysics();
- // ignore myself, my enemy, and dead bodies
- if ( ( obPhys == physics ) || ( obEnt == ignore ) || ( obEnt->health <= 0 ) ) {
- continue;
- }
- // if the actor is moving
- idVec3 v1 = obPhys->GetLinearVelocity();
- if ( v1.LengthSqr() > Square( 10.0f ) ) {
- idVec3 v2 = physics->GetLinearVelocity();
- if ( v2.LengthSqr() > Square( 10.0f ) ) {
- // if moving in about the same direction
- if ( v1 * v2 > 0.0f ) {
- continue;
- }
- }
- }
- } else if ( obEnt->IsType( idMoveable::Type ) ) {
- // moveables are considered obstacles
- } else {
- // ignore everything else
- continue;
- }
- // check if we can step over the object
- clipModel->GetAbsBounds().AxisProjection( -physics->GetGravityNormal(), min, max );
- if ( max < stepHeight || min > headHeight ) {
- // can step over this one
- continue;
- }
- // project a box containing the obstacle onto the floor plane
- box = idBox( clipModel->GetBounds(), clipModel->GetOrigin(), clipModel->GetAxis() );
- numVerts = box.GetParallelProjectionSilhouetteVerts( physics->GetGravityNormal(), silVerts );
- // create a 2D winding for the obstacle;
- obstacle_t &obstacle = obstacles[numObstacles++];
- obstacle.winding.Clear();
- for ( j = 0; j < numVerts; j++ ) {
- obstacle.winding.AddPoint( silVerts[j].ToVec2() );
- }
- if ( ai_showObstacleAvoidance.GetBool() ) {
- for ( j = 0; j < numVerts; j++ ) {
- silVerts[j].z = startPos.z;
- }
- for ( j = 0; j < numVerts; j++ ) {
- gameRenderWorld->DebugArrow( colorWhite, silVerts[j], silVerts[(j+1)%numVerts], 4 );
- }
- }
- // expand the 2D winding for collision with a 2D box
- obstacle.winding.ExpandForAxialBox( expBounds );
- obstacle.winding.GetBounds( obstacle.bounds );
- obstacle.entity = obEnt;
- }
- // if there are no dynamic obstacles the path should be through valid AAS space
- if ( numObstacles == 0 ) {
- return 0;
- }
- // if the current path doesn't intersect any dynamic obstacles the path should be through valid AAS space
- if ( PointInsideObstacle( obstacles, numObstacles, startPos.ToVec2() ) == -1 ) {
- if ( !GetFirstBlockingObstacle( obstacles, numObstacles, -1, startPos.ToVec2(), seekDelta.ToVec2(), blockingScale, blockingObstacle, blockingEdgeNum ) ) {
- return 0;
- }
- }
- // create obstacles for AAS walls
- if ( aas ) {
- float halfBoundsSize = ( expBounds[ 1 ].x - expBounds[ 0 ].x ) * 0.5f;
- numWallEdges = aas->GetWallEdges( areaNum, clipBounds, TFL_WALK, wallEdges, MAX_AAS_WALL_EDGES );
- aas->SortWallEdges( wallEdges, numWallEdges );
- lastVerts[0] = lastVerts[1] = 0;
- lastEdgeNormal.Zero();
- nextVerts[0] = nextVerts[1] = 0;
- for ( i = 0; i < numWallEdges && numObstacles < MAX_OBSTACLES; i++ ) {
- aas->GetEdge( wallEdges[i], start, end );
- aas->GetEdgeVertexNumbers( wallEdges[i], verts );
- edgeDir = end.ToVec2() - start.ToVec2();
- edgeDir.Normalize();
- edgeNormal.x = edgeDir.y;
- edgeNormal.y = -edgeDir.x;
- if ( i < numWallEdges-1 ) {
- aas->GetEdge( wallEdges[i+1], nextStart, nextEnd );
- aas->GetEdgeVertexNumbers( wallEdges[i+1], nextVerts );
- nextEdgeDir = nextEnd.ToVec2() - nextStart.ToVec2();
- nextEdgeDir.Normalize();
- nextEdgeNormal.x = nextEdgeDir.y;
- nextEdgeNormal.y = -nextEdgeDir.x;
- }
- obstacle_t &obstacle = obstacles[numObstacles++];
- obstacle.winding.Clear();
- obstacle.winding.AddPoint( end.ToVec2() );
- obstacle.winding.AddPoint( start.ToVec2() );
- obstacle.winding.AddPoint( start.ToVec2() - edgeDir - edgeNormal * halfBoundsSize );
- obstacle.winding.AddPoint( end.ToVec2() + edgeDir - edgeNormal * halfBoundsSize );
- if ( lastVerts[1] == verts[0] ) {
- obstacle.winding[2] -= lastEdgeNormal * halfBoundsSize;
- } else {
- obstacle.winding[1] -= edgeDir;
- }
- if ( verts[1] == nextVerts[0] ) {
- obstacle.winding[3] -= nextEdgeNormal * halfBoundsSize;
- } else {
- obstacle.winding[0] += edgeDir;
- }
- obstacle.winding.GetBounds( obstacle.bounds );
- obstacle.entity = NULL;
- memcpy( lastVerts, verts, sizeof( lastVerts ) );
- lastEdgeNormal = edgeNormal;
- }
- }
- // show obstacles
- if ( ai_showObstacleAvoidance.GetBool() ) {
- for ( i = 0; i < numObstacles; i++ ) {
- obstacle_t &obstacle = obstacles[i];
- for ( j = 0; j < obstacle.winding.GetNumPoints(); j++ ) {
- silVerts[j].ToVec2() = obstacle.winding[j];
- silVerts[j].z = startPos.z;
- }
- for ( j = 0; j < obstacle.winding.GetNumPoints(); j++ ) {
- gameRenderWorld->DebugArrow( colorGreen, silVerts[j], silVerts[(j+1)%obstacle.winding.GetNumPoints()], 4 );
- }
- }
- }
- return numObstacles;
- }
- /*
- ============
- FreePathTree_r
- ============
- */
- void FreePathTree_r( pathNode_t *node ) {
- if ( node->children[0] ) {
- FreePathTree_r( node->children[0] );
- }
- if ( node->children[1] ) {
- FreePathTree_r( node->children[1] );
- }
- pathNodeAllocator.Free( node );
- }
- /*
- ============
- DrawPathTree
- ============
- */
- void DrawPathTree( const pathNode_t *root, const float height ) {
- int i;
- idVec3 start, end;
- const pathNode_t *node;
- for ( node = root; node; node = node->next ) {
- for ( i = 0; i < 2; i++ ) {
- if ( node->children[i] ) {
- start.ToVec2() = node->pos;
- start.z = height;
- end.ToVec2() = node->children[i]->pos;
- end.z = height;
- gameRenderWorld->DebugArrow( node->edgeNum == -1 ? colorYellow : i ? colorBlue : colorRed, start, end, 1 );
- break;
- }
- }
- }
- }
- /*
- ============
- GetPathNodeDelta
- ============
- */
- bool GetPathNodeDelta( pathNode_t *node, const obstacle_t *obstacles, const idVec2 &seekPos, bool blocked ) {
- int numPoints, edgeNum;
- bool facing;
- idVec2 seekDelta, dir;
- pathNode_t *n;
- numPoints = obstacles[node->obstacle].winding.GetNumPoints();
- // get delta along the current edge
- while( 1 ) {
- edgeNum = ( node->edgeNum + node->dir ) % numPoints;
- node->delta = obstacles[node->obstacle].winding[edgeNum] - node->pos;
- if ( node->delta.LengthSqr() > 0.01f ) {
- break;
- }
- node->edgeNum = ( node->edgeNum + numPoints + ( 2 * node->dir - 1 ) ) % numPoints;
- }
- // if not blocked
- if ( !blocked ) {
- // test if the current edge faces the goal
- seekDelta = seekPos - node->pos;
- facing = ( ( 2 * node->dir - 1 ) * ( node->delta.x * seekDelta.y - node->delta.y * seekDelta.x ) ) >= 0.0f;
- // if the current edge faces goal and the line from the current
- // position to the goal does not intersect the current path
- if ( facing && !LineIntersectsPath( node->pos, seekPos, node->parent ) ) {
- node->delta = seekPos - node->pos;
- node->edgeNum = -1;
- }
- }
- // if the delta is along the obstacle edge
- if ( node->edgeNum != -1 ) {
- // if the edge is found going from this node to the root node
- for ( n = node->parent; n; n = n->parent ) {
- if ( node->obstacle != n->obstacle || node->edgeNum != n->edgeNum ) {
- continue;
- }
- // test whether or not the edge segments actually overlap
- if ( n->pos * node->delta > ( node->pos + node->delta ) * node->delta ) {
- continue;
- }
- if ( node->pos * node->delta > ( n->pos + n->delta ) * node->delta ) {
- continue;
- }
- break;
- }
- if ( n ) {
- return false;
- }
- }
- return true;
- }
- /*
- ============
- BuildPathTree
- ============
- */
- pathNode_t *BuildPathTree( const obstacle_t *obstacles, int numObstacles, const idBounds &clipBounds, const idVec2 &startPos, const idVec2 &seekPos, obstaclePath_t &path ) {
- int blockingEdgeNum, blockingObstacle, obstaclePoints, bestNumNodes = MAX_OBSTACLE_PATH;
- float blockingScale;
- pathNode_t *root, *node, *child;
- // gcc 4.0
- idQueueTemplate<pathNode_t, offsetof( pathNode_t, next ) > pathNodeQueue, treeQueue;
- root = pathNodeAllocator.Alloc();
- root->Init();
- root->pos = startPos;
- root->delta = seekPos - root->pos;
- root->numNodes = 0;
- pathNodeQueue.Add( root );
- for ( node = pathNodeQueue.Get(); node != NULL && pathNodeAllocator.GetAllocCount() < MAX_PATH_NODES; node = pathNodeQueue.Get() ) {
- treeQueue.Add( node );
- // if this path has more than twice the number of nodes than the best path so far
- if ( node->numNodes > bestNumNodes * 2 ) {
- continue;
- }
- // don't move outside of the clip bounds
- idVec2 endPos = node->pos + node->delta;
- if ( endPos.x - CLIP_BOUNDS_EPSILON < clipBounds[0].x || endPos.x + CLIP_BOUNDS_EPSILON > clipBounds[1].x ||
- endPos.y - CLIP_BOUNDS_EPSILON < clipBounds[0].y || endPos.y + CLIP_BOUNDS_EPSILON > clipBounds[1].y ) {
- continue;
- }
- // if an obstacle is blocking the path
- if ( GetFirstBlockingObstacle( obstacles, numObstacles, node->obstacle, node->pos, node->delta, blockingScale, blockingObstacle, blockingEdgeNum ) ) {
- if ( path.firstObstacle == NULL ) {
- path.firstObstacle = obstacles[blockingObstacle].entity;
- }
- node->delta *= blockingScale;
- if ( node->edgeNum == -1 ) {
- node->children[0] = pathNodeAllocator.Alloc();
- node->children[0]->Init();
- node->children[1] = pathNodeAllocator.Alloc();
- node->children[1]->Init();
- node->children[0]->dir = 0;
- node->children[1]->dir = 1;
- node->children[0]->parent = node->children[1]->parent = node;
- node->children[0]->pos = node->children[1]->pos = node->pos + node->delta;
- node->children[0]->obstacle = node->children[1]->obstacle = blockingObstacle;
- node->children[0]->edgeNum = node->children[1]->edgeNum = blockingEdgeNum;
- node->children[0]->numNodes = node->children[1]->numNodes = node->numNodes + 1;
- if ( GetPathNodeDelta( node->children[0], obstacles, seekPos, true ) ) {
- pathNodeQueue.Add( node->children[0] );
- }
- if ( GetPathNodeDelta( node->children[1], obstacles, seekPos, true ) ) {
- pathNodeQueue.Add( node->children[1] );
- }
- } else {
- node->children[node->dir] = child = pathNodeAllocator.Alloc();
- child->Init();
- child->dir = node->dir;
- child->parent = node;
- child->pos = node->pos + node->delta;
- child->obstacle = blockingObstacle;
- child->edgeNum = blockingEdgeNum;
- child->numNodes = node->numNodes + 1;
- if ( GetPathNodeDelta( child, obstacles, seekPos, true ) ) {
- pathNodeQueue.Add( child );
- }
- }
- } else {
- node->children[node->dir] = child = pathNodeAllocator.Alloc();
- child->Init();
- child->dir = node->dir;
- child->parent = node;
- child->pos = node->pos + node->delta;
- child->numNodes = node->numNodes + 1;
- // there is a free path towards goal
- if ( node->edgeNum == -1 ) {
- if ( node->numNodes < bestNumNodes ) {
- bestNumNodes = node->numNodes;
- }
- continue;
- }
- child->obstacle = node->obstacle;
- obstaclePoints = obstacles[node->obstacle].winding.GetNumPoints();
- child->edgeNum = ( node->edgeNum + obstaclePoints + ( 2 * node->dir - 1 ) ) % obstaclePoints;
- if ( GetPathNodeDelta( child, obstacles, seekPos, false ) ) {
- pathNodeQueue.Add( child );
- }
- }
- }
- return root;
- }
- /*
- ============
- PrunePathTree
- ============
- */
- void PrunePathTree( pathNode_t *root, const idVec2 &seekPos ) {
- int i;
- float bestDist;
- pathNode_t *node, *lastNode, *n, *bestNode;
- node = root;
- while( node ) {
- node->dist = ( seekPos - node->pos ).LengthSqr();
- if ( node->children[0] ) {
- node = node->children[0];
- } else if ( node->children[1] ) {
- node = node->children[1];
- } else {
- // find the node closest to the goal along this path
- bestDist = idMath::INFINITY;
- bestNode = node;
- for ( n = node; n; n = n->parent ) {
- if ( n->children[0] && n->children[1] ) {
- break;
- }
- if ( n->dist < bestDist ) {
- bestDist = n->dist;
- bestNode = n;
- }
- }
- // free tree down from the best node
- for ( i = 0; i < 2; i++ ) {
- if ( bestNode->children[i] ) {
- FreePathTree_r( bestNode->children[i] );
- bestNode->children[i] = NULL;
- }
- }
- for ( lastNode = bestNode, node = bestNode->parent; node; lastNode = node, node = node->parent ) {
- if ( node->children[1] && ( node->children[1] != lastNode ) ) {
- node = node->children[1];
- break;
- }
- }
- }
- }
- }
- /*
- ============
- OptimizePath
- ============
- */
- int OptimizePath( const pathNode_t *root, const pathNode_t *leafNode, const obstacle_t *obstacles, int numObstacles, idVec2 optimizedPath[MAX_OBSTACLE_PATH] ) {
- int i, numPathPoints, edgeNums[2];
- const pathNode_t *curNode, *nextNode;
- idVec2 curPos, curDelta, bounds[2];
- float scale1, scale2, curLength;
- optimizedPath[0] = root->pos;
- numPathPoints = 1;
- for ( nextNode = curNode = root; curNode != leafNode; curNode = nextNode ) {
- for ( nextNode = leafNode; nextNode->parent != curNode; nextNode = nextNode->parent ) {
- // can only take shortcuts when going from one object to another
- if ( nextNode->obstacle == curNode->obstacle ) {
- continue;
- }
- curPos = curNode->pos;
- curDelta = nextNode->pos - curPos;
- curLength = curDelta.Length();
- // get bounds for the current movement delta
- bounds[0] = curPos - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON );
- bounds[1] = curPos + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON );
- bounds[IEEE_FLT_SIGNBITNOTSET(curDelta.x)].x += curDelta.x;
- bounds[IEEE_FLT_SIGNBITNOTSET(curDelta.y)].y += curDelta.y;
- // test if the shortcut intersects with any obstacles
- for ( i = 0; i < numObstacles; i++ ) {
- if ( bounds[0].x > obstacles[i].bounds[1].x || bounds[0].y > obstacles[i].bounds[1].y ||
- bounds[1].x < obstacles[i].bounds[0].x || bounds[1].y < obstacles[i].bounds[0].y ) {
- continue;
- }
- if ( obstacles[i].winding.RayIntersection( curPos, curDelta, scale1, scale2, edgeNums ) ) {
- if ( scale1 >= 0.0f && scale1 <= 1.0f && ( i != nextNode->obstacle || scale1 * curLength < curLength - 0.5f ) ) {
- break;
- }
- if ( scale2 >= 0.0f && scale2 <= 1.0f && ( i != nextNode->obstacle || scale2 * curLength < curLength - 0.5f ) ) {
- break;
- }
- }
- }
- if ( i >= numObstacles ) {
- break;
- }
- }
- // store the next position along the optimized path
- optimizedPath[numPathPoints++] = nextNode->pos;
- }
- return numPathPoints;
- }
- /*
- ============
- PathLength
- ============
- */
- float PathLength( idVec2 optimizedPath[MAX_OBSTACLE_PATH], int numPathPoints, const idVec2 &curDir ) {
- int i;
- float pathLength;
- // calculate the path length
- pathLength = 0.0f;
- for ( i = 0; i < numPathPoints-1; i++ ) {
- pathLength += ( optimizedPath[i+1] - optimizedPath[i] ).LengthFast();
- }
- // add penalty if this path does not go in the current direction
- if ( curDir * ( optimizedPath[1] - optimizedPath[0] ) < 0.0f ) {
- pathLength += 100.0f;
- }
- return pathLength;
- }
- /*
- ============
- FindOptimalPath
- Returns true if there is a path all the way to the goal.
- ============
- */
- bool FindOptimalPath( const pathNode_t *root, const obstacle_t *obstacles, int numObstacles, const float height, const idVec3 &curDir, idVec3 &seekPos ) {
- int i, numPathPoints, bestNumPathPoints;
- const pathNode_t *node, *lastNode, *bestNode;
- idVec2 optimizedPath[MAX_OBSTACLE_PATH];
- float pathLength, bestPathLength;
- bool pathToGoalExists, optimizedPathCalculated;
- seekPos.Zero();
- seekPos.z = height;
- pathToGoalExists = false;
- optimizedPathCalculated = false;
- bestNode = root;
- bestNumPathPoints = 0;
- bestPathLength = idMath::INFINITY;
- node = root;
- while( node ) {
- pathToGoalExists |= ( node->dist < 0.1f );
- if ( node->dist <= bestNode->dist ) {
- if ( idMath::Fabs( node->dist - bestNode->dist ) < 0.1f ) {
- if ( !optimizedPathCalculated ) {
- bestNumPathPoints = OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath );
- bestPathLength = PathLength( optimizedPath, bestNumPathPoints, curDir.ToVec2() );
- seekPos.ToVec2() = optimizedPath[1];
- }
- numPathPoints = OptimizePath( root, node, obstacles, numObstacles, optimizedPath );
- pathLength = PathLength( optimizedPath, numPathPoints, curDir.ToVec2() );
- if ( pathLength < bestPathLength ) {
- bestNode = node;
- bestNumPathPoints = numPathPoints;
- bestPathLength = pathLength;
- seekPos.ToVec2() = optimizedPath[1];
- }
- optimizedPathCalculated = true;
- } else {
- bestNode = node;
- optimizedPathCalculated = false;
- }
- }
- if ( node->children[0] ) {
- node = node->children[0];
- } else if ( node->children[1] ) {
- node = node->children[1];
- } else {
- for ( lastNode = node, node = node->parent; node; lastNode = node, node = node->parent ) {
- if ( node->children[1] && node->children[1] != lastNode ) {
- node = node->children[1];
- break;
- }
- }
- }
- }
- if ( root != NULL ) {
- if ( !pathToGoalExists ) {
- if ( root->children[0] != NULL ) {
- seekPos.ToVec2() = root->children[0]->pos;
- } else {
- seekPos.ToVec2() = root->pos;
- }
- } else if ( !optimizedPathCalculated ) {
- OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath );
- seekPos.ToVec2() = optimizedPath[1];
- }
- if ( ai_showObstacleAvoidance.GetBool() ) {
- idVec3 start, end;
- start.z = end.z = height + 4.0f;
- numPathPoints = OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath );
- for ( i = 0; i < numPathPoints-1; i++ ) {
- start.ToVec2() = optimizedPath[i];
- end.ToVec2() = optimizedPath[i+1];
- gameRenderWorld->DebugArrow( colorCyan, start, end, 1 );
- }
- }
- }
- return pathToGoalExists;
- }
- /*
- ============
- idAI::FindPathAroundObstacles
- Finds a path around dynamic obstacles using a path tree with clockwise and counter clockwise edge walks.
- ============
- */
- bool idAI::FindPathAroundObstacles( const idPhysics *physics, const idAAS *aas, const idEntity *ignore, const idVec3 &startPos, const idVec3 &seekPos, obstaclePath_t &path ) {
- int numObstacles, areaNum, insideObstacle;
- obstacle_t obstacles[MAX_OBSTACLES];
- idBounds clipBounds;
- idBounds bounds;
- pathNode_t *root;
- bool pathToGoalExists;
- path.seekPos = seekPos;
- path.firstObstacle = NULL;
- path.startPosOutsideObstacles = startPos;
- path.startPosObstacle = NULL;
- path.seekPosOutsideObstacles = seekPos;
- path.seekPosObstacle = NULL;
- if ( !aas ) {
- return true;
- }
- bounds[1] = aas->GetSettings()->boundingBoxes[0][1];
- bounds[0] = -bounds[1];
- bounds[1].z = 32.0f;
- // get the AAS area number and a valid point inside that area
- areaNum = aas->PointReachableAreaNum( path.startPosOutsideObstacles, bounds, (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) );
- aas->PushPointIntoAreaNum( areaNum, path.startPosOutsideObstacles );
- // get all the nearby obstacles
- numObstacles = GetObstacles( physics, aas, ignore, areaNum, path.startPosOutsideObstacles, path.seekPosOutsideObstacles, obstacles, MAX_OBSTACLES, clipBounds );
- // get a source position outside the obstacles
- GetPointOutsideObstacles( obstacles, numObstacles, path.startPosOutsideObstacles.ToVec2(), &insideObstacle, NULL );
- if ( insideObstacle != -1 ) {
- path.startPosObstacle = obstacles[insideObstacle].entity;
- }
- // get a goal position outside the obstacles
- GetPointOutsideObstacles( obstacles, numObstacles, path.seekPosOutsideObstacles.ToVec2(), &insideObstacle, NULL );
- if ( insideObstacle != -1 ) {
- path.seekPosObstacle = obstacles[insideObstacle].entity;
- }
- // if start and destination are pushed to the same point, we don't have a path around the obstacle
- if ( ( path.seekPosOutsideObstacles.ToVec2() - path.startPosOutsideObstacles.ToVec2() ).LengthSqr() < Square( 1.0f ) ) {
- if ( ( seekPos.ToVec2() - startPos.ToVec2() ).LengthSqr() > Square( 2.0f ) ) {
- return false;
- }
- }
- // build a path tree
- root = BuildPathTree( obstacles, numObstacles, clipBounds, path.startPosOutsideObstacles.ToVec2(), path.seekPosOutsideObstacles.ToVec2(), path );
- // draw the path tree
- if ( ai_showObstacleAvoidance.GetBool() ) {
- DrawPathTree( root, physics->GetOrigin().z );
- }
- // prune the tree
- PrunePathTree( root, path.seekPosOutsideObstacles.ToVec2() );
- // find the optimal path
- pathToGoalExists = FindOptimalPath( root, obstacles, numObstacles, physics->GetOrigin().z, physics->GetLinearVelocity(), path.seekPos );
- // free the tree
- FreePathTree_r( root );
- return pathToGoalExists;
- }
- /*
- ============
- idAI::FreeObstacleAvoidanceNodes
- ============
- */
- void idAI::FreeObstacleAvoidanceNodes() {
- pathNodeAllocator.Shutdown();
- }
- /*
- ===============================================================================
- Path Prediction
- Uses the AAS to quickly and accurately predict a path for a certain
- period of time based on an initial position and velocity.
- ===============================================================================
- */
- const float OVERCLIP = 1.001f;
- const int MAX_FRAME_SLIDE = 5;
- typedef struct pathTrace_s {
- float fraction;
- idVec3 endPos;
- idVec3 normal;
- const idEntity * blockingEntity;
- } pathTrace_t;
- /*
- ============
- PathTrace
- Returns true if a stop event was triggered.
- ============
- */
- bool PathTrace( const idEntity *ent, const idAAS *aas, const idVec3 &start, const idVec3 &end, int stopEvent, struct pathTrace_s &trace, predictedPath_t &path ) {
- trace_t clipTrace;
- aasTrace_t aasTrace;
- memset( &trace, 0, sizeof( trace ) );
- if ( !aas || !aas->GetSettings() ) {
- gameLocal.clip.Translation( clipTrace, start, end, ent->GetPhysics()->GetClipModel(),
- ent->GetPhysics()->GetClipModel()->GetAxis(), MASK_MONSTERSOLID, ent );
- // NOTE: could do (expensive) ledge detection here for when there is no AAS file
- trace.fraction = clipTrace.fraction;
- trace.endPos = clipTrace.endpos;
- trace.normal = clipTrace.c.normal;
- trace.blockingEntity = gameLocal.entities[ clipTrace.c.entityNum ];
- } else {
- aasTrace.getOutOfSolid = true;
- if ( stopEvent & SE_ENTER_LEDGE_AREA ) {
- aasTrace.flags |= AREA_LEDGE;
- }
- if ( stopEvent & SE_ENTER_OBSTACLE ) {
- aasTrace.travelFlags |= TFL_INVALID;
- }
- aas->Trace( aasTrace, start, end );
- gameLocal.clip.TranslationEntities( clipTrace, start, aasTrace.endpos, ent->GetPhysics()->GetClipModel(),
- ent->GetPhysics()->GetClipModel()->GetAxis(), MASK_MONSTERSOLID, ent );
- if ( clipTrace.fraction >= 1.0f ) {
- trace.fraction = aasTrace.fraction;
- trace.endPos = aasTrace.endpos;
- trace.normal = aas->GetPlane( aasTrace.planeNum ).Normal();
- trace.blockingEntity = gameLocal.world;
- if ( aasTrace.fraction < 1.0f ) {
- if ( stopEvent & SE_ENTER_LEDGE_AREA ) {
- if ( aas->AreaFlags( aasTrace.blockingAreaNum ) & AREA_LEDGE ) {
- path.endPos = trace.endPos;
- path.endNormal = trace.normal;
- path.endEvent = SE_ENTER_LEDGE_AREA;
- path.blockingEntity = trace.blockingEntity;
- if ( ai_debugMove.GetBool() ) {
- gameRenderWorld->DebugLine( colorRed, start, aasTrace.endpos );
- }
- return true;
- }
- }
- if ( stopEvent & SE_ENTER_OBSTACLE ) {
- if ( aas->AreaTravelFlags( aasTrace.blockingAreaNum ) & TFL_INVALID ) {
- path.endPos = trace.endPos;
- path.endNormal = trace.normal;
- path.endEvent = SE_ENTER_OBSTACLE;
- path.blockingEntity = trace.blockingEntity;
- if ( ai_debugMove.GetBool() ) {
- gameRenderWorld->DebugLine( colorRed, start, aasTrace.endpos );
- }
- return true;
- }
- }
- }
- } else {
- trace.fraction = clipTrace.fraction;
- trace.endPos = clipTrace.endpos;
- trace.normal = clipTrace.c.normal;
- trace.blockingEntity = gameLocal.entities[ clipTrace.c.entityNum ];
- }
- }
- if ( trace.fraction >= 1.0f ) {
- trace.blockingEntity = NULL;
- }
- return false;
- }
- /*
- ============
- idAI::PredictPath
- Can also be used when there is no AAS file available however ledges are not detected.
- ============
- */
- bool idAI::PredictPath( const idEntity *ent, const idAAS *aas, const idVec3 &start, const idVec3 &velocity, int totalTime, int frameTime, int stopEvent, predictedPath_t &path ) {
- int i, j, step, numFrames, curFrameTime;
- idVec3 delta, curStart, curEnd, curVelocity, lastEnd, stepUp, tmpStart;
- idVec3 gravity, gravityDir, invGravityDir;
- float maxStepHeight, minFloorCos;
- pathTrace_t trace;
- if ( aas && aas->GetSettings() ) {
- gravity = aas->GetSettings()->gravity;
- gravityDir = aas->GetSettings()->gravityDir;
- invGravityDir = aas->GetSettings()->invGravityDir;
- maxStepHeight = aas->GetSettings()->maxStepHeight;
- minFloorCos = aas->GetSettings()->minFloorCos;
- } else {
- gravity = DEFAULT_GRAVITY_VEC3;
- gravityDir = idVec3( 0, 0, -1 );
- invGravityDir = idVec3( 0, 0, 1 );
- maxStepHeight = 14.0f;
- minFloorCos = 0.7f;
- }
- path.endPos = start;
- path.endVelocity = velocity;
- path.endNormal.Zero();
- path.endEvent = 0;
- path.endTime = 0;
- path.blockingEntity = NULL;
- curStart = start;
- curVelocity = velocity;
- numFrames = ( totalTime + frameTime - 1 ) / frameTime;
- curFrameTime = frameTime;
- for ( i = 0; i < numFrames; i++ ) {
- if ( i == numFrames-1 ) {
- curFrameTime = totalTime - i * curFrameTime;
- }
- delta = curVelocity * curFrameTime * 0.001f;
- path.endVelocity = curVelocity;
- path.endTime = i * frameTime;
- // allow sliding along a few surfaces per frame
- for ( j = 0; j < MAX_FRAME_SLIDE; j++ ) {
- idVec3 lineStart = curStart;
- // allow stepping up three times per frame
- for ( step = 0; step < 3; step++ ) {
- curEnd = curStart + delta;
- if ( PathTrace( ent, aas, curStart, curEnd, stopEvent, trace, path ) ) {
- return true;
- }
- if ( step ) {
- // step down at end point
- tmpStart = trace.endPos;
- curEnd = tmpStart - stepUp;
- if ( PathTrace( ent, aas, tmpStart, curEnd, stopEvent, trace, path ) ) {
- return true;
- }
- // if not moved any further than without stepping up, or if not on a floor surface
- if ( (lastEnd - start).LengthSqr() > (trace.endPos - start).LengthSqr() - 0.1f ||
- ( trace.normal * invGravityDir ) < minFloorCos ) {
- if ( stopEvent & SE_BLOCKED ) {
- path.endPos = lastEnd;
- path.endEvent = SE_BLOCKED;
- if ( ai_debugMove.GetBool() ) {
- gameRenderWorld->DebugLine( colorRed, lineStart, lastEnd );
- }
- return true;
- }
- curStart = lastEnd;
- break;
- }
- }
- path.endNormal = trace.normal;
- path.blockingEntity = trace.blockingEntity;
- // if the trace is not blocked or blocked by a floor surface
- if ( trace.fraction >= 1.0f || ( trace.normal * invGravityDir ) > minFloorCos ) {
- curStart = trace.endPos;
- break;
- }
- // save last result
- lastEnd = trace.endPos;
- // step up
- stepUp = invGravityDir * maxStepHeight;
- if ( PathTrace( ent, aas, curStart, curStart + stepUp, stopEvent, trace, path ) ) {
- return true;
- }
- stepUp *= trace.fraction;
- curStart = trace.endPos;
- }
- if ( ai_debugMove.GetBool() ) {
- gameRenderWorld->DebugLine( colorRed, lineStart, curStart );
- }
- if ( trace.fraction >= 1.0f ) {
- break;
- }
- delta.ProjectOntoPlane( trace.normal, OVERCLIP );
- curVelocity.ProjectOntoPlane( trace.normal, OVERCLIP );
- if ( stopEvent & SE_BLOCKED ) {
- // if going backwards
- if ( (curVelocity - gravityDir * curVelocity * gravityDir ) *
- (velocity - gravityDir * velocity * gravityDir) < 0.0f ) {
- path.endPos = curStart;
- path.endEvent = SE_BLOCKED;
- return true;
- }
- }
- }
- if ( j >= MAX_FRAME_SLIDE ) {
- if ( stopEvent & SE_BLOCKED ) {
- path.endPos = curStart;
- path.endEvent = SE_BLOCKED;
- return true;
- }
- }
- // add gravity
- curVelocity += gravity * frameTime * 0.001f;
- }
- path.endTime = totalTime;
- path.endVelocity = curVelocity;
- path.endPos = curStart;
- path.endEvent = 0;
- return false;
- }
- /*
- ===============================================================================
- Trajectory Prediction
- Finds the best collision free trajectory for a clip model based on an
- initial position, target position and speed.
- ===============================================================================
- */
- /*
- =====================
- Ballistics
- get the ideal aim pitch angle in order to hit the target
- also get the time it takes for the projectile to arrive at the target
- =====================
- */
- int Ballistics( const idVec3 &start, const idVec3 &end, float speed, float gravity, ballistics_t bal[2] ) {
- int n, i;
- float x, y, a, b, c, d, sqrtd, inva, p[2];
- x = ( end.ToVec2() - start.ToVec2() ).Length();
- y = end[2] - start[2];
- a = 4.0f * y * y + 4.0f * x * x;
- b = -4.0f * speed * speed - 4.0f * y * gravity;
- c = gravity * gravity;
- d = b * b - 4.0f * a * c;
- if ( d <= 0.0f || a == 0.0f ) {
- return 0;
- }
- sqrtd = idMath::Sqrt( d );
- inva = 0.5f / a;
- p[0] = ( - b + sqrtd ) * inva;
- p[1] = ( - b - sqrtd ) * inva;
- n = 0;
- for ( i = 0; i < 2; i++ ) {
- if ( p[i] <= 0.0f ) {
- continue;
- }
- d = idMath::Sqrt( p[i] );
- bal[n].angle = atan2( 0.5f * ( 2.0f * y * p[i] - gravity ) / d, d * x );
- bal[n].time = x / ( cos( bal[n].angle ) * speed );
- bal[n].angle = idMath::AngleNormalize180( RAD2DEG( bal[n].angle ) );
- n++;
- }
- return n;
- }
- #if 0
- // not used
- /*
- =====================
- HeightForTrajectory
- Returns the maximum hieght of a given trajectory
- =====================
- */
- static float HeightForTrajectory( const idVec3 &start, float zVel, float gravity ) {
- float maxHeight, t;
- t = zVel / gravity;
- // maximum height of projectile
- maxHeight = start.z - 0.5f * gravity * ( t * t );
-
- return maxHeight;
- }
- #endif
- /*
- =====================
- idAI::TestTrajectory
- =====================
- */
- 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 ) {
- int i, numSegments;
- float maxHeight, t, t2;
- idVec3 points[5];
- trace_t trace;
- bool result;
- t = zVel / gravity;
- // maximum height of projectile
- maxHeight = start.z - 0.5f * gravity * ( t * t );
- // time it takes to fall from the top to the end height
- t = idMath::Sqrt( ( maxHeight - end.z ) / ( 0.5f * -gravity ) );
- // start of parabolic
- points[0] = start;
- if ( t < time ) {
- numSegments = 4;
- // point in the middle between top and start
- t2 = ( time - t ) * 0.5f;
- points[1].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time );
- points[1].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2;
- // top of parabolic
- t2 = time - t;
- points[2].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time );
- points[2].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2;
- // point in the middel between top and end
- t2 = time - t * 0.5f;
- points[3].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time );
- points[3].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2;
- } else {
- numSegments = 2;
- // point halfway through
- t2 = time * 0.5f;
- points[1].ToVec2() = start.ToVec2() + ( end.ToVec2() - start.ToVec2() ) * 0.5f;
- points[1].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2;
- }
- // end of parabolic
- points[numSegments] = end;
- if ( drawtime ) {
- for ( i = 0; i < numSegments; i++ ) {
- gameRenderWorld->DebugLine( colorRed, points[i], points[i+1], drawtime );
- }
- }
- // make sure projectile doesn't go higher than we want it to go
- for ( i = 0; i < numSegments; i++ ) {
- if ( points[i].z > max_height ) {
- // goes higher than we want to allow
- return false;
- }
- }
- result = true;
- for ( i = 0; i < numSegments; i++ ) {
- gameLocal.clip.Translation( trace, points[i], points[i+1], clip, mat3_identity, clipmask, ignore );
- if ( trace.fraction < 1.0f ) {
- if ( gameLocal.GetTraceEntity( trace ) == targetEntity ) {
- result = true;
- } else {
- result = false;
- }
- break;
- }
- }
- if ( drawtime ) {
- if ( clip ) {
- gameRenderWorld->DebugBounds( result ? colorGreen : colorYellow, clip->GetBounds().Expand( 1.0f ), trace.endpos, drawtime );
- } else {
- idBounds bnds( trace.endpos );
- bnds.ExpandSelf( 1.0f );
- gameRenderWorld->DebugBounds( result ? colorGreen : colorYellow, bnds, vec3_zero, drawtime );
- }
- }
- return result;
- }
- /*
- =====================
- idAI::PredictTrajectory
- returns true if there is a collision free trajectory for the clip model
- aimDir is set to the ideal aim direction in order to hit the target
- =====================
- */
- 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 ) {
- int n, i, j;
- float zVel, a, t, pitch, s, c;
- trace_t trace;
- ballistics_t ballistics[2];
- idVec3 dir[2];
- idVec3 velocity;
- idVec3 lastPos, pos;
- if ( targetEntity == NULL ) {
- return false;
- }
- // check if the projectile starts inside the target
- if ( targetEntity->GetPhysics()->GetAbsBounds().IntersectsBounds( clip->GetBounds().Translate( firePos ) ) ) {
- aimDir = target - firePos;
- aimDir.Normalize();
- return true;
- }
- // if no velocity or the projectile is not affected by gravity
- if ( projectileSpeed <= 0.0f || projGravity == vec3_origin ) {
- aimDir = target - firePos;
- aimDir.Normalize();
- gameLocal.clip.Translation( trace, firePos, target, clip, mat3_identity, clipmask, ignore );
- if ( drawtime ) {
- gameRenderWorld->DebugLine( colorRed, firePos, target, drawtime );
- idBounds bnds( trace.endpos );
- bnds.ExpandSelf( 1.0f );
- gameRenderWorld->DebugBounds( ( trace.fraction >= 1.0f || ( gameLocal.GetTraceEntity( trace ) == targetEntity ) ) ? colorGreen : colorYellow, bnds, vec3_zero, drawtime );
- }
- return ( trace.fraction >= 1.0f || ( gameLocal.GetTraceEntity( trace ) == targetEntity ) );
- }
- n = Ballistics( firePos, target, projectileSpeed, projGravity[2], ballistics );
- if ( n == 0 ) {
- // there is no valid trajectory
- aimDir = target - firePos;
- aimDir.Normalize();
- return false;
- }
- // make sure the first angle is the smallest
- if ( n == 2 ) {
- if ( ballistics[1].angle < ballistics[0].angle ) {
- a = ballistics[0].angle; ballistics[0].angle = ballistics[1].angle; ballistics[1].angle = a;
- t = ballistics[0].time; ballistics[0].time = ballistics[1].time; ballistics[1].time = t;
- }
- }
- // test if there is a collision free trajectory
- for ( i = 0; i < n; i++ ) {
- pitch = DEG2RAD( ballistics[i].angle );
- idMath::SinCos( pitch, s, c );
- dir[i] = target - firePos;
- dir[i].z = 0.0f;
- dir[i] *= c * idMath::InvSqrt( dir[i].LengthSqr() );
- dir[i].z = s;
- zVel = projectileSpeed * dir[i].z;
- if ( ai_debugTrajectory.GetBool() ) {
- t = ballistics[i].time / 100.0f;
- velocity = dir[i] * projectileSpeed;
- lastPos = firePos;
- pos = firePos;
- for ( j = 1; j < 100; j++ ) {
- pos += velocity * t;
- velocity += projGravity * t;
- gameRenderWorld->DebugLine( colorCyan, lastPos, pos );
- lastPos = pos;
- }
- }
- if ( TestTrajectory( firePos, target, zVel, projGravity[2], ballistics[i].time, firePos.z + max_height, clip, clipmask, ignore, targetEntity, drawtime ) ) {
- aimDir = dir[i];
- return true;
- }
- }
- aimDir = dir[0];
- // there is no collision free trajectory
- return false;
- }
|