AASReach.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946
  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 "AASFile.h"
  23. #include "AASFile_local.h"
  24. #include "AASReach.h"
  25. #define INSIDEUNITS 2.0f
  26. #define INSIDEUNITS_WALKEND 0.5f
  27. #define INSIDEUNITS_WALKSTART 0.1f
  28. #define INSIDEUNITS_SWIMEND 0.5f
  29. #define INSIDEUNITS_FLYEND 0.5f
  30. #define INSIDEUNITS_WATERJUMP 15.0f
  31. /*
  32. ================
  33. idAASReach::ReachabilityExists
  34. ================
  35. */
  36. bool idAASReach::ReachabilityExists( int fromAreaNum, int toAreaNum ) {
  37. aasArea_t *area;
  38. idReachability *reach;
  39. area = &file->areas[fromAreaNum];
  40. for ( reach = area->reach; reach; reach = reach->next ) {
  41. if ( reach->toAreaNum == toAreaNum ) {
  42. return true;
  43. }
  44. }
  45. return false;
  46. }
  47. /*
  48. ================
  49. idAASReach::CanSwimInArea
  50. ================
  51. */
  52. ID_INLINE bool idAASReach::CanSwimInArea( int areaNum ) {
  53. return ( file->areas[areaNum].contents & AREACONTENTS_WATER ) != 0;
  54. }
  55. /*
  56. ================
  57. idAASReach::AreaHasFloor
  58. ================
  59. */
  60. ID_INLINE bool idAASReach::AreaHasFloor( int areaNum ) {
  61. return ( file->areas[areaNum].flags & AREA_FLOOR ) != 0;
  62. }
  63. /*
  64. ================
  65. idAASReach::AreaIsClusterPortal
  66. ================
  67. */
  68. ID_INLINE bool idAASReach::AreaIsClusterPortal( int areaNum ) {
  69. return ( file->areas[areaNum].contents & AREACONTENTS_CLUSTERPORTAL ) != 0;
  70. }
  71. /*
  72. ================
  73. idAASReach::AddReachabilityToArea
  74. ================
  75. */
  76. void idAASReach::AddReachabilityToArea( idReachability *reach, int areaNum ) {
  77. aasArea_t *area;
  78. area = &file->areas[areaNum];
  79. reach->next = area->reach;
  80. area->reach = reach;
  81. numReachabilities++;
  82. }
  83. /*
  84. ================
  85. idAASReach::Reachability_Fly
  86. ================
  87. */
  88. void idAASReach::Reachability_Fly( int areaNum ) {
  89. int i, faceNum, otherAreaNum;
  90. aasArea_t *area;
  91. aasFace_t *face;
  92. idReachability_Fly *reach;
  93. area = &file->areas[areaNum];
  94. for ( i = 0; i < area->numFaces; i++ ) {
  95. faceNum = file->faceIndex[area->firstFace + i];
  96. face = &file->faces[abs(faceNum)];
  97. otherAreaNum = face->areas[INTSIGNBITNOTSET(faceNum)];
  98. if ( otherAreaNum == 0 ) {
  99. continue;
  100. }
  101. if ( ReachabilityExists( areaNum, otherAreaNum ) ) {
  102. continue;
  103. }
  104. // create reachability going through this face
  105. reach = new idReachability_Fly();
  106. reach->travelType = TFL_FLY;
  107. reach->toAreaNum = otherAreaNum;
  108. reach->fromAreaNum = areaNum;
  109. reach->edgeNum = 0;
  110. reach->travelTime = 1;
  111. reach->start = file->FaceCenter( abs(faceNum) );
  112. if ( faceNum < 0 ) {
  113. reach->end = reach->start + file->planeList[face->planeNum].Normal() * INSIDEUNITS_FLYEND;
  114. } else {
  115. reach->end = reach->start - file->planeList[face->planeNum].Normal() * INSIDEUNITS_FLYEND;
  116. }
  117. AddReachabilityToArea( reach, areaNum );
  118. }
  119. }
  120. /*
  121. ================
  122. idAASReach::Reachability_Swim
  123. ================
  124. */
  125. void idAASReach::Reachability_Swim( int areaNum ) {
  126. int i, faceNum, otherAreaNum;
  127. aasArea_t *area;
  128. aasFace_t *face;
  129. idReachability_Swim *reach;
  130. if ( !CanSwimInArea( areaNum ) ) {
  131. return;
  132. }
  133. area = &file->areas[areaNum];
  134. for ( i = 0; i < area->numFaces; i++ ) {
  135. faceNum = file->faceIndex[area->firstFace + i];
  136. face = &file->faces[abs(faceNum)];
  137. otherAreaNum = face->areas[INTSIGNBITNOTSET(faceNum)];
  138. if ( otherAreaNum == 0 ) {
  139. continue;
  140. }
  141. if ( !CanSwimInArea( otherAreaNum ) ) {
  142. continue;
  143. }
  144. if ( ReachabilityExists( areaNum, otherAreaNum ) ) {
  145. continue;
  146. }
  147. // create reachability going through this face
  148. reach = new idReachability_Swim();
  149. reach->travelType = TFL_SWIM;
  150. reach->toAreaNum = otherAreaNum;
  151. reach->fromAreaNum = areaNum;
  152. reach->edgeNum = 0;
  153. reach->travelTime = 1;
  154. reach->start = file->FaceCenter( abs(faceNum) );
  155. if ( faceNum < 0 ) {
  156. reach->end = reach->start + file->planeList[face->planeNum].Normal() * INSIDEUNITS_SWIMEND;
  157. } else {
  158. reach->end = reach->start - file->planeList[face->planeNum].Normal() * INSIDEUNITS_SWIMEND;
  159. }
  160. AddReachabilityToArea( reach, areaNum );
  161. }
  162. }
  163. /*
  164. ================
  165. idAASReach::Reachability_EqualFloorHeight
  166. ================
  167. */
  168. void idAASReach::Reachability_EqualFloorHeight( int areaNum ) {
  169. int i, k, l, m, n, faceNum, face1Num, face2Num, otherAreaNum, edge1Num, edge2Num;
  170. aasArea_t *area, *otherArea;
  171. aasFace_t *face, *face1, *face2;
  172. idReachability_Walk *reach;
  173. if ( !AreaHasFloor( areaNum ) ) {
  174. return;
  175. }
  176. area = &file->areas[areaNum];
  177. for ( i = 0; i < area->numFaces; i++ ) {
  178. faceNum = file->faceIndex[area->firstFace + i];
  179. face = &file->faces[abs(faceNum)];
  180. otherAreaNum = face->areas[INTSIGNBITNOTSET(faceNum)];
  181. if ( !AreaHasFloor( otherAreaNum ) ) {
  182. continue;
  183. }
  184. otherArea = &file->areas[otherAreaNum];
  185. for ( k = 0; k < area->numFaces; k++ ) {
  186. face1Num = file->faceIndex[area->firstFace + k];
  187. face1 = &file->faces[abs(face1Num)];
  188. if ( !( face1->flags & FACE_FLOOR ) ) {
  189. continue;
  190. }
  191. for ( l = 0; l < otherArea->numFaces; l++ ) {
  192. face2Num = file->faceIndex[otherArea->firstFace + l];
  193. face2 = &file->faces[abs(face2Num)];
  194. if ( !( face2->flags & FACE_FLOOR ) ) {
  195. continue;
  196. }
  197. for ( m = 0; m < face1->numEdges; m++ ) {
  198. edge1Num = abs(file->edgeIndex[face1->firstEdge + m]);
  199. for ( n = 0; n < face2->numEdges; n++ ) {
  200. edge2Num = abs(file->edgeIndex[face2->firstEdge + n]);
  201. if ( edge1Num == edge2Num ) {
  202. break;
  203. }
  204. }
  205. if ( n < face2->numEdges ) {
  206. break;
  207. }
  208. }
  209. if ( m < face1->numEdges ) {
  210. break;
  211. }
  212. }
  213. if ( l < otherArea->numFaces ) {
  214. break;
  215. }
  216. }
  217. if ( k < area->numFaces ) {
  218. // create reachability
  219. reach = new idReachability_Walk();
  220. reach->travelType = TFL_WALK;
  221. reach->toAreaNum = otherAreaNum;
  222. reach->fromAreaNum = areaNum;
  223. reach->edgeNum = abs( edge1Num );
  224. reach->travelTime = 1;
  225. reach->start = file->EdgeCenter( edge1Num );
  226. if ( faceNum < 0 ) {
  227. reach->end = reach->start + file->planeList[face->planeNum].Normal() * INSIDEUNITS_WALKEND;
  228. }
  229. else {
  230. reach->end = reach->start - file->planeList[face->planeNum].Normal() * INSIDEUNITS_WALKEND;
  231. }
  232. AddReachabilityToArea( reach, areaNum );
  233. }
  234. }
  235. }
  236. /*
  237. ================
  238. idAASReach::Reachability_Step_Barrier_WaterJump_WalkOffLedge
  239. ================
  240. */
  241. bool idAASReach::Reachability_Step_Barrier_WaterJump_WalkOffLedge( int area1num, int area2num ) {
  242. int i, j, k, l, edge1Num, edge2Num, areas[10];
  243. int floor_bestArea1FloorEdgeNum, floor_bestArea2FloorEdgeNum, floor_foundReach;
  244. int water_bestArea1FloorEdgeNum, water_bestArea2FloorEdgeNum, water_foundReach;
  245. int side1, faceSide1, floorFace1Num;
  246. float dist, dist1, dist2, diff, invGravityDot, orthogonalDot;
  247. float x1, x2, x3, x4, y1, y2, y3, y4, tmp, y;
  248. float length, floor_bestLength, water_bestLength, floor_bestDist, water_bestDist;
  249. idVec3 v1, v2, v3, v4, tmpv, p1area1, p1area2, p2area1, p2area2;
  250. idVec3 normal, orthogonal, edgeVec, start, end;
  251. idVec3 floor_bestStart, floor_bestEnd, floor_bestNormal;
  252. idVec3 water_bestStart, water_bestEnd, water_bestNormal;
  253. idVec3 testPoint;
  254. idPlane *plane;
  255. aasArea_t *area1, *area2;
  256. aasFace_t *floorFace1, *floorFace2, *floor_bestFace1, *water_bestFace1;
  257. aasEdge_t *edge1, *edge2;
  258. idReachability_Walk *walkReach;
  259. idReachability_BarrierJump *barrierJumpReach;
  260. idReachability_WaterJump *waterJumpReach;
  261. idReachability_WalkOffLedge *walkOffLedgeReach;
  262. aasTrace_t trace;
  263. // must be able to walk or swim in the first area
  264. if ( !AreaHasFloor( area1num ) && !CanSwimInArea( area1num ) ) {
  265. return false;
  266. }
  267. if ( !AreaHasFloor( area2num ) && !CanSwimInArea( area2num ) ) {
  268. return false;
  269. }
  270. area1 = &file->areas[area1num];
  271. area2 = &file->areas[area2num];
  272. // if the areas are not near anough in the x-y direction
  273. for ( i = 0; i < 2; i++ ) {
  274. if ( area1->bounds[0][i] > area2->bounds[1][i] + 2.0f ) {
  275. return false;
  276. }
  277. if ( area1->bounds[1][i] < area2->bounds[0][i] - 2.0f ) {
  278. return false;
  279. }
  280. }
  281. floor_foundReach = false;
  282. floor_bestDist = 99999;
  283. floor_bestLength = 0;
  284. floor_bestArea2FloorEdgeNum = 0;
  285. water_foundReach = false;
  286. water_bestDist = 99999;
  287. water_bestLength = 0;
  288. water_bestArea2FloorEdgeNum = 0;
  289. for ( i = 0; i < area1->numFaces; i++ ) {
  290. floorFace1Num = file->faceIndex[area1->firstFace + i];
  291. faceSide1 = floorFace1Num < 0;
  292. floorFace1 = &file->faces[abs(floorFace1Num)];
  293. // if this isn't a floor face
  294. if ( !(floorFace1->flags & FACE_FLOOR) ) {
  295. // if we can swim in the first area
  296. if ( CanSwimInArea( area1num ) ) {
  297. // face plane must be more or less horizontal
  298. plane = &file->planeList[ floorFace1->planeNum ^ (!faceSide1) ];
  299. if ( plane->Normal() * file->settings.invGravityDir < file->settings.minFloorCos ) {
  300. continue;
  301. }
  302. }
  303. else {
  304. // if we can't swim in the area it must be a ground face
  305. continue;
  306. }
  307. }
  308. for ( k = 0; k < floorFace1->numEdges; k++ ) {
  309. edge1Num = file->edgeIndex[floorFace1->firstEdge + k];
  310. side1 = (edge1Num < 0);
  311. // NOTE: for water faces we must take the side area 1 is on into
  312. // account because the face is shared and doesn't have to be oriented correctly
  313. if ( !(floorFace1->flags & FACE_FLOOR) ) {
  314. side1 = (side1 == faceSide1);
  315. }
  316. edge1Num = abs(edge1Num);
  317. edge1 = &file->edges[edge1Num];
  318. // vertices of the edge
  319. v1 = file->vertices[edge1->vertexNum[!side1]];
  320. v2 = file->vertices[edge1->vertexNum[side1]];
  321. // get a vertical plane through the edge
  322. // NOTE: normal is pointing into area 2 because the face edges are stored counter clockwise
  323. edgeVec = v2 - v1;
  324. normal = edgeVec.Cross( file->settings.invGravityDir );
  325. normal.Normalize();
  326. dist = normal * v1;
  327. // check the faces from the second area
  328. for ( j = 0; j < area2->numFaces; j++ ) {
  329. floorFace2 = &file->faces[abs(file->faceIndex[area2->firstFace + j])];
  330. // must be a ground face
  331. if ( !(floorFace2->flags & FACE_FLOOR) ) {
  332. continue;
  333. }
  334. // check the edges of this ground face
  335. for ( l = 0; l < floorFace2->numEdges; l++ ) {
  336. edge2Num = abs(file->edgeIndex[floorFace2->firstEdge + l]);
  337. edge2 = &file->edges[edge2Num];
  338. // vertices of the edge
  339. v3 = file->vertices[edge2->vertexNum[0]];
  340. v4 = file->vertices[edge2->vertexNum[1]];
  341. // check the distance between the two points and the vertical plane through the edge of area1
  342. diff = normal * v3 - dist;
  343. if ( diff < -0.2f || diff > 0.2f ) {
  344. continue;
  345. }
  346. diff = normal * v4 - dist;
  347. if ( diff < -0.2f || diff > 0.2f ) {
  348. continue;
  349. }
  350. // project the two ground edges into the step side plane
  351. // and calculate the shortest distance between the two
  352. // edges if they overlap in the direction orthogonal to
  353. // the gravity direction
  354. orthogonal = file->settings.invGravityDir.Cross( normal );
  355. invGravityDot = file->settings.invGravityDir * file->settings.invGravityDir;
  356. orthogonalDot = orthogonal * orthogonal;
  357. // projection into the step plane
  358. // NOTE: since gravity is vertical this is just the z coordinate
  359. y1 = v1[2];//(v1 * file->settings.invGravity) / invGravityDot;
  360. y2 = v2[2];//(v2 * file->settings.invGravity) / invGravityDot;
  361. y3 = v3[2];//(v3 * file->settings.invGravity) / invGravityDot;
  362. y4 = v4[2];//(v4 * file->settings.invGravity) / invGravityDot;
  363. x1 = (v1 * orthogonal) / orthogonalDot;
  364. x2 = (v2 * orthogonal) / orthogonalDot;
  365. x3 = (v3 * orthogonal) / orthogonalDot;
  366. x4 = (v4 * orthogonal) / orthogonalDot;
  367. if ( x1 > x2 ) {
  368. tmp = x1; x1 = x2; x2 = tmp;
  369. tmp = y1; y1 = y2; y2 = tmp;
  370. tmpv = v1; v1 = v2; v2 = tmpv;
  371. }
  372. if ( x3 > x4 ) {
  373. tmp = x3; x3 = x4; x4 = tmp;
  374. tmp = y3; y3 = y4; y4 = tmp;
  375. tmpv = v3; v3 = v4; v4 = tmpv;
  376. }
  377. // if the two projected edge lines have no overlap
  378. if ( x2 <= x3 || x4 <= x1 ) {
  379. continue;
  380. }
  381. // if the two lines fully overlap
  382. if ( (x1 - 0.5f < x3 && x4 < x2 + 0.5f) && (x3 - 0.5f < x1 && x2 < x4 + 0.5f) ) {
  383. dist1 = y3 - y1;
  384. dist2 = y4 - y2;
  385. p1area1 = v1;
  386. p2area1 = v2;
  387. p1area2 = v3;
  388. p2area2 = v4;
  389. }
  390. else {
  391. // if the points are equal
  392. if ( x1 > x3 - 0.1f && x1 < x3 + 0.1f ) {
  393. dist1 = y3 - y1;
  394. p1area1 = v1;
  395. p1area2 = v3;
  396. }
  397. else if ( x1 < x3 ) {
  398. y = y1 + (x3 - x1) * (y2 - y1) / (x2 - x1);
  399. dist1 = y3 - y;
  400. p1area1 = v3;
  401. p1area1[2] = y;
  402. p1area2 = v3;
  403. }
  404. else {
  405. y = y3 + (x1 - x3) * (y4 - y3) / (x4 - x3);
  406. dist1 = y - y1;
  407. p1area1 = v1;
  408. p1area2 = v1;
  409. p1area2[2] = y;
  410. }
  411. // if the points are equal
  412. if ( x2 > x4 - 0.1f && x2 < x4 + 0.1f ) {
  413. dist2 = y4 - y2;
  414. p2area1 = v2;
  415. p2area2 = v4;
  416. }
  417. else if ( x2 < x4 ) {
  418. y = y3 + (x2 - x3) * (y4 - y3) / (x4 - x3);
  419. dist2 = y - y2;
  420. p2area1 = v2;
  421. p2area2 = v2;
  422. p2area2[2] = y;
  423. }
  424. else {
  425. y = y1 + (x4 - x1) * (y2 - y1) / (x2 - x1);
  426. dist2 = y4 - y;
  427. p2area1 = v4;
  428. p2area1[2] = y;
  429. p2area2 = v4;
  430. }
  431. }
  432. // if both distances are pretty much equal then we take the middle of the points
  433. if ( dist1 > dist2 - 1.0f && dist1 < dist2 + 1.0f ) {
  434. dist = dist1;
  435. start = ( p1area1 + p2area1 ) * 0.5f;
  436. end = ( p1area2 + p2area2 ) * 0.5f;
  437. }
  438. else if (dist1 < dist2) {
  439. dist = dist1;
  440. start = p1area1;
  441. end = p1area2;
  442. }
  443. else {
  444. dist = dist2;
  445. start = p2area1;
  446. end = p2area2;
  447. }
  448. // get the length of the overlapping part of the edges of the two areas
  449. length = (p2area2 - p1area2).Length();
  450. if ( floorFace1->flags & FACE_FLOOR ) {
  451. // if the vertical distance is smaller
  452. if ( dist < floor_bestDist ||
  453. // or the vertical distance is pretty much the same
  454. // but the overlapping part of the edges is longer
  455. (dist < floor_bestDist + 1.0f && length > floor_bestLength) ) {
  456. floor_bestDist = dist;
  457. floor_bestLength = length;
  458. floor_foundReach = true;
  459. floor_bestArea1FloorEdgeNum = edge1Num;
  460. floor_bestArea2FloorEdgeNum = edge2Num;
  461. floor_bestFace1 = floorFace1;
  462. floor_bestStart = start;
  463. floor_bestNormal = normal;
  464. floor_bestEnd = end;
  465. }
  466. }
  467. else {
  468. // if the vertical distance is smaller
  469. if ( dist < water_bestDist ||
  470. //or the vertical distance is pretty much the same
  471. //but the overlapping part of the edges is longer
  472. (dist < water_bestDist + 1.0f && length > water_bestLength) ) {
  473. water_bestDist = dist;
  474. water_bestLength = length;
  475. water_foundReach = true;
  476. water_bestArea1FloorEdgeNum = edge1Num;
  477. water_bestArea2FloorEdgeNum = edge2Num;
  478. water_bestFace1 = floorFace1;
  479. water_bestStart = start; // best start point in area1
  480. water_bestNormal = normal; // normal is pointing into area2
  481. water_bestEnd = end; // best point towards area2
  482. }
  483. }
  484. }
  485. }
  486. }
  487. }
  488. //
  489. // NOTE: swim reachabilities should already be filtered out
  490. //
  491. // Steps
  492. //
  493. // ---------
  494. // | step height -> TFL_WALK
  495. // --------|
  496. //
  497. // ---------
  498. // ~~~~~~~~| step height and low water -> TFL_WALK
  499. // --------|
  500. //
  501. // ~~~~~~~~~~~~~~~~~~
  502. // ---------
  503. // | step height and low water up to the step -> TFL_WALK
  504. // --------|
  505. //
  506. // check for a step reachability
  507. if ( floor_foundReach ) {
  508. // if area2 is higher but lower than the maximum step height
  509. // NOTE: floor_bestDist >= 0 also catches equal floor reachabilities
  510. if ( floor_bestDist >= 0 && floor_bestDist < file->settings.maxStepHeight ) {
  511. // create walk reachability from area1 to area2
  512. walkReach = new idReachability_Walk();
  513. walkReach->travelType = TFL_WALK;
  514. walkReach->toAreaNum = area2num;
  515. walkReach->fromAreaNum = area1num;
  516. walkReach->start = floor_bestStart + INSIDEUNITS_WALKSTART * floor_bestNormal;
  517. walkReach->end = floor_bestEnd + INSIDEUNITS_WALKEND * floor_bestNormal;
  518. walkReach->edgeNum = abs( floor_bestArea1FloorEdgeNum );
  519. walkReach->travelTime = 0;
  520. if ( area2->flags & AREA_CROUCH ) {
  521. walkReach->travelTime += file->settings.tt_startCrouching;
  522. }
  523. AddReachabilityToArea( walkReach, area1num );
  524. return true;
  525. }
  526. }
  527. //
  528. // Water Jumps
  529. //
  530. // ---------
  531. // |
  532. // ~~~~~~~~|
  533. // |
  534. // | higher than step height and water up to waterjump height -> TFL_WATERJUMP
  535. // --------|
  536. //
  537. // ~~~~~~~~~~~~~~~~~~
  538. // ---------
  539. // |
  540. // |
  541. // |
  542. // | higher than step height and low water up to the step -> TFL_WATERJUMP
  543. // --------|
  544. //
  545. // check for a waterjump reachability
  546. if ( water_foundReach ) {
  547. // get a test point a little bit towards area1
  548. testPoint = water_bestEnd - INSIDEUNITS * water_bestNormal;
  549. // go down the maximum waterjump height
  550. testPoint[2] -= file->settings.maxWaterJumpHeight;
  551. // if there IS water the sv_maxwaterjump height below the bestend point
  552. if ( area1->flags & AREA_LIQUID ) {
  553. // don't create rediculous water jump reachabilities from areas very far below the water surface
  554. if ( water_bestDist < file->settings.maxWaterJumpHeight + 24 ) {
  555. // water jumping from or towards a crouch only areas is not possible
  556. if ( !(area1->flags & AREA_CROUCH) && !(area2->flags & AREA_CROUCH) ) {
  557. // create water jump reachability from area1 to area2
  558. waterJumpReach = new idReachability_WaterJump();
  559. waterJumpReach->travelType = TFL_WATERJUMP;
  560. waterJumpReach->toAreaNum = area2num;
  561. waterJumpReach->fromAreaNum = area1num;
  562. waterJumpReach->start = water_bestStart;
  563. waterJumpReach->end = water_bestEnd + INSIDEUNITS_WATERJUMP * water_bestNormal;
  564. waterJumpReach->edgeNum = abs( floor_bestArea1FloorEdgeNum );
  565. waterJumpReach->travelTime = file->settings.tt_waterJump;
  566. AddReachabilityToArea( waterJumpReach, area1num );
  567. return true;
  568. }
  569. }
  570. }
  571. }
  572. //
  573. // Barrier Jumps
  574. //
  575. // ---------
  576. // |
  577. // |
  578. // |
  579. // | higher than max step height lower than max barrier height -> TFL_BARRIERJUMP
  580. // --------|
  581. //
  582. // ---------
  583. // |
  584. // |
  585. // |
  586. // ~~~~~~~~| higher than max step height lower than max barrier height
  587. // --------| and a thin layer of water in the area to jump from -> TFL_BARRIERJUMP
  588. //
  589. // check for a barrier jump reachability
  590. if ( floor_foundReach ) {
  591. //if area2 is higher but lower than the maximum barrier jump height
  592. if ( floor_bestDist > 0 && floor_bestDist < file->settings.maxBarrierHeight ) {
  593. //if no water in area1 or a very thin layer of water on the ground
  594. if ( !water_foundReach || (floor_bestDist - water_bestDist < 16) ) {
  595. // cannot perform a barrier jump towards or from a crouch area
  596. if ( !(area1->flags & AREA_CROUCH) && !(area2->flags & AREA_CROUCH) ) {
  597. // create barrier jump reachability from area1 to area2
  598. barrierJumpReach = new idReachability_BarrierJump();
  599. barrierJumpReach->travelType = TFL_BARRIERJUMP;
  600. barrierJumpReach->toAreaNum = area2num;
  601. barrierJumpReach->fromAreaNum = area1num;
  602. barrierJumpReach->start = floor_bestStart + INSIDEUNITS_WALKSTART * floor_bestNormal;
  603. barrierJumpReach->end = floor_bestEnd + INSIDEUNITS_WALKEND * floor_bestNormal;
  604. barrierJumpReach->edgeNum = abs( floor_bestArea1FloorEdgeNum );
  605. barrierJumpReach->travelTime = file->settings.tt_barrierJump;
  606. AddReachabilityToArea( barrierJumpReach, area1num );
  607. return true;
  608. }
  609. }
  610. }
  611. }
  612. //
  613. // Walk and Walk Off Ledge
  614. //
  615. // --------|
  616. // | can walk or step back -> TFL_WALK
  617. // ---------
  618. //
  619. // --------|
  620. // |
  621. // |
  622. // |
  623. // | cannot walk/step back -> TFL_WALKOFFLEDGE
  624. // ---------
  625. //
  626. // --------|
  627. // |
  628. // |~~~~~~~~
  629. // |
  630. // | cannot step back but can waterjump back -> TFL_WALKOFFLEDGE
  631. // --------- FIXME: create TFL_WALK reach??
  632. //
  633. // check for a walk or walk off ledge reachability
  634. if ( floor_foundReach ) {
  635. if ( floor_bestDist < 0 ) {
  636. if ( floor_bestDist > -file->settings.maxStepHeight ) {
  637. // create walk reachability from area1 to area2
  638. walkReach = new idReachability_Walk();
  639. walkReach->travelType = TFL_WALK;
  640. walkReach->toAreaNum = area2num;
  641. walkReach->fromAreaNum = area1num;
  642. walkReach->start = floor_bestStart + INSIDEUNITS_WALKSTART * floor_bestNormal;
  643. walkReach->end = floor_bestEnd + INSIDEUNITS_WALKEND * floor_bestNormal;
  644. walkReach->edgeNum = abs( floor_bestArea1FloorEdgeNum );
  645. walkReach->travelTime = 1;
  646. AddReachabilityToArea( walkReach, area1num );
  647. return true;
  648. }
  649. // if no maximum fall height set or less than the max
  650. if ( !file->settings.maxFallHeight || idMath::Fabs(floor_bestDist) < file->settings.maxFallHeight ) {
  651. // trace a bounding box vertically to check for solids
  652. floor_bestEnd += INSIDEUNITS * floor_bestNormal;
  653. start = floor_bestEnd;
  654. start[2] = floor_bestStart[2];
  655. end = floor_bestEnd;
  656. end[2] += 4;
  657. trace.areas = areas;
  658. trace.maxAreas = sizeof(areas) / sizeof(int);
  659. file->Trace( trace, start, end );
  660. // if the trace didn't start in solid and nothing was hit
  661. if ( trace.lastAreaNum && trace.fraction >= 1.0f ) {
  662. // the trace end point must be in the goal area
  663. if ( trace.lastAreaNum == area2num ) {
  664. // don't create reachability if going through a cluster portal
  665. for (i = 0; i < trace.numAreas; i++) {
  666. if ( AreaIsClusterPortal( trace.areas[i] ) ) {
  667. break;
  668. }
  669. }
  670. if ( i >= trace.numAreas ) {
  671. // create a walk off ledge reachability from area1 to area2
  672. walkOffLedgeReach = new idReachability_WalkOffLedge();
  673. walkOffLedgeReach->travelType = TFL_WALKOFFLEDGE;
  674. walkOffLedgeReach->toAreaNum = area2num;
  675. walkOffLedgeReach->fromAreaNum = area1num;
  676. walkOffLedgeReach->start = floor_bestStart;
  677. walkOffLedgeReach->end = floor_bestEnd;
  678. walkOffLedgeReach->edgeNum = abs( floor_bestArea1FloorEdgeNum );
  679. walkOffLedgeReach->travelTime = file->settings.tt_startWalkOffLedge + idMath::Fabs(floor_bestDist) * 50 / file->settings.gravityValue;
  680. AddReachabilityToArea( walkOffLedgeReach, area1num );
  681. return true;
  682. }
  683. }
  684. }
  685. }
  686. }
  687. }
  688. return false;
  689. }
  690. /*
  691. ================
  692. idAASReach::Reachability_WalkOffLedge
  693. ================
  694. */
  695. void idAASReach::Reachability_WalkOffLedge( int areaNum ) {
  696. int i, j, faceNum, edgeNum, side, reachAreaNum, p, areas[10];
  697. aasArea_t *area;
  698. aasFace_t *face;
  699. aasEdge_t *edge;
  700. idPlane *plane;
  701. idVec3 v1, v2, mid, dir, testEnd;
  702. idReachability_WalkOffLedge *reach;
  703. aasTrace_t trace;
  704. if ( !AreaHasFloor( areaNum ) || CanSwimInArea( areaNum ) ) {
  705. return;
  706. }
  707. area = &file->areas[areaNum];
  708. for ( i = 0; i < area->numFaces; i++ ) {
  709. faceNum = file->faceIndex[area->firstFace + i];
  710. face = &file->faces[abs(faceNum)];
  711. // face must be a floor face
  712. if ( !(face->flags & FACE_FLOOR) ) {
  713. continue;
  714. }
  715. for ( j = 0; j < face->numEdges; j++ ) {
  716. edgeNum = file->edgeIndex[face->firstEdge + j];
  717. edge = &file->edges[abs(edgeNum)];
  718. //if ( !(edge->flags & EDGE_LEDGE) ) {
  719. // continue;
  720. //}
  721. side = edgeNum < 0;
  722. v1 = file->vertices[edge->vertexNum[side]];
  723. v2 = file->vertices[edge->vertexNum[!side]];
  724. plane = &file->planeList[face->planeNum ^ INTSIGNBITSET(faceNum) ];
  725. // get the direction into the other area
  726. dir = plane->Normal().Cross( v2 - v1 );
  727. dir.Normalize();
  728. mid = ( v1 + v2 ) * 0.5f;
  729. testEnd = mid + INSIDEUNITS_WALKEND * dir;
  730. testEnd[2] -= file->settings.maxFallHeight + 1.0f;
  731. trace.areas = areas;
  732. trace.maxAreas = sizeof(areas) / sizeof(int);
  733. file->Trace( trace, mid, testEnd );
  734. reachAreaNum = trace.lastAreaNum;
  735. if ( !reachAreaNum || reachAreaNum == areaNum ) {
  736. continue;
  737. }
  738. if ( idMath::Fabs( mid[2] - trace.endpos[2] ) > file->settings.maxFallHeight ) {
  739. continue;
  740. }
  741. if ( !AreaHasFloor( reachAreaNum ) && !CanSwimInArea( reachAreaNum ) ) {
  742. continue;
  743. }
  744. if ( ReachabilityExists( areaNum, reachAreaNum) ) {
  745. continue;
  746. }
  747. // if not going through a cluster portal
  748. for ( p = 0; p < trace.numAreas; p++ ) {
  749. if ( AreaIsClusterPortal( trace.areas[p] ) ) {
  750. break;
  751. }
  752. }
  753. if ( p < trace.numAreas ) {
  754. continue;
  755. }
  756. reach = new idReachability_WalkOffLedge();
  757. reach->travelType = TFL_WALKOFFLEDGE;
  758. reach->toAreaNum = reachAreaNum;
  759. reach->fromAreaNum = areaNum;
  760. reach->start = mid;
  761. reach->end = trace.endpos;
  762. reach->edgeNum = abs( edgeNum );
  763. reach->travelTime = file->settings.tt_startWalkOffLedge + idMath::Fabs(mid[2] - trace.endpos[2]) * 50 / file->settings.gravityValue;
  764. AddReachabilityToArea( reach, areaNum );
  765. }
  766. }
  767. }
  768. /*
  769. ================
  770. idAASReach::FlagReachableAreas
  771. ================
  772. */
  773. void idAASReach::FlagReachableAreas( idAASFileLocal *file ) {
  774. int i, numReachableAreas;
  775. numReachableAreas = 0;
  776. for ( i = 1; i < file->areas.Num(); i++ ) {
  777. if ( ( file->areas[i].flags & ( AREA_FLOOR | AREA_LADDER ) ) ||
  778. ( file->areas[i].contents & AREACONTENTS_WATER ) ) {
  779. file->areas[i].flags |= AREA_REACHABLE_WALK;
  780. }
  781. if ( file->GetSettings().allowFlyReachabilities ) {
  782. file->areas[i].flags |= AREA_REACHABLE_FLY;
  783. }
  784. numReachableAreas++;
  785. }
  786. common->Printf( "%6d reachable areas\n", numReachableAreas );
  787. }
  788. /*
  789. ================
  790. idAASReach::Build
  791. ================
  792. */
  793. bool idAASReach::Build( const idMapFile *mapFile, idAASFileLocal *file ) {
  794. int i, j, lastPercent, percent;
  795. this->mapFile = mapFile;
  796. this->file = file;
  797. numReachabilities = 0;
  798. common->Printf( "[Reachability]\n" );
  799. // delete all existing reachabilities
  800. file->DeleteReachabilities();
  801. FlagReachableAreas( file );
  802. for ( i = 1; i < file->areas.Num(); i++ ) {
  803. if ( !( file->areas[i].flags & AREA_REACHABLE_WALK ) ) {
  804. continue;
  805. }
  806. if ( file->GetSettings().allowSwimReachabilities ) {
  807. Reachability_Swim( i );
  808. }
  809. Reachability_EqualFloorHeight( i );
  810. }
  811. lastPercent = -1;
  812. for ( i = 1; i < file->areas.Num(); i++ ) {
  813. if ( !( file->areas[i].flags & AREA_REACHABLE_WALK ) ) {
  814. continue;
  815. }
  816. for ( j = 0; j < file->areas.Num(); j++ ) {
  817. if ( i == j ) {
  818. continue;
  819. }
  820. if ( !( file->areas[j].flags & AREA_REACHABLE_WALK ) ) {
  821. continue;
  822. }
  823. if ( ReachabilityExists( i, j ) ) {
  824. continue;
  825. }
  826. if ( Reachability_Step_Barrier_WaterJump_WalkOffLedge( i, j ) ) {
  827. continue;
  828. }
  829. }
  830. //Reachability_WalkOffLedge( i );
  831. percent = 100 * i / file->areas.Num();
  832. if ( percent > lastPercent ) {
  833. common->Printf( "\r%6d%%", percent );
  834. lastPercent = percent;
  835. }
  836. }
  837. if ( file->GetSettings().allowFlyReachabilities ) {
  838. for ( i = 1; i < file->areas.Num(); i++ ) {
  839. Reachability_Fly( i );
  840. }
  841. }
  842. file->LinkReversedReachability();
  843. common->Printf( "\r%6d reachabilities\n", numReachabilities );
  844. return true;
  845. }