AI_pathing.cpp 45 KB

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