2 ===========================================================================
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
22 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.
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
26 ===========================================================================
29 #include "../../../idlib/precompiled.h"
33 #include "AASFile_local.h"
36 #define INSIDEUNITS 2.0f
37 #define INSIDEUNITS_WALKEND 0.5f
38 #define INSIDEUNITS_WALKSTART 0.1f
39 #define INSIDEUNITS_SWIMEND 0.5f
40 #define INSIDEUNITS_FLYEND 0.5f
41 #define INSIDEUNITS_WATERJUMP 15.0f
46 idAASReach::ReachabilityExists
49 bool idAASReach::ReachabilityExists( int fromAreaNum, int toAreaNum ) {
51 idReachability *reach;
53 area = &file->areas[fromAreaNum];
54 for ( reach = area->reach; reach; reach = reach->next ) {
55 if ( reach->toAreaNum == toAreaNum ) {
64 idAASReach::CanSwimInArea
67 ID_INLINE bool idAASReach::CanSwimInArea( int areaNum ) {
68 return ( file->areas[areaNum].contents & AREACONTENTS_WATER ) != 0;
73 idAASReach::AreaHasFloor
76 ID_INLINE bool idAASReach::AreaHasFloor( int areaNum ) {
77 return ( file->areas[areaNum].flags & AREA_FLOOR ) != 0;
82 idAASReach::AreaIsClusterPortal
85 ID_INLINE bool idAASReach::AreaIsClusterPortal( int areaNum ) {
86 return ( file->areas[areaNum].contents & AREACONTENTS_CLUSTERPORTAL ) != 0;
91 idAASReach::AddReachabilityToArea
94 void idAASReach::AddReachabilityToArea( idReachability *reach, int areaNum ) {
97 area = &file->areas[areaNum];
98 reach->next = area->reach;
105 idAASReach::Reachability_Fly
108 void idAASReach::Reachability_Fly( int areaNum ) {
109 int i, faceNum, otherAreaNum;
112 idReachability_Fly *reach;
114 area = &file->areas[areaNum];
116 for ( i = 0; i < area->numFaces; i++ ) {
117 faceNum = file->faceIndex[area->firstFace + i];
118 face = &file->faces[abs(faceNum)];
120 otherAreaNum = face->areas[INTSIGNBITNOTSET(faceNum)];
122 if ( otherAreaNum == 0 ) {
126 if ( ReachabilityExists( areaNum, otherAreaNum ) ) {
130 // create reachability going through this face
131 reach = new idReachability_Fly();
132 reach->travelType = TFL_FLY;
133 reach->toAreaNum = otherAreaNum;
134 reach->fromAreaNum = areaNum;
136 reach->travelTime = 1;
137 reach->start = file->FaceCenter( abs(faceNum) );
139 reach->end = reach->start + file->planeList[face->planeNum].Normal() * INSIDEUNITS_FLYEND;
141 reach->end = reach->start - file->planeList[face->planeNum].Normal() * INSIDEUNITS_FLYEND;
143 AddReachabilityToArea( reach, areaNum );
149 idAASReach::Reachability_Swim
152 void idAASReach::Reachability_Swim( int areaNum ) {
153 int i, faceNum, otherAreaNum;
156 idReachability_Swim *reach;
158 if ( !CanSwimInArea( areaNum ) ) {
162 area = &file->areas[areaNum];
164 for ( i = 0; i < area->numFaces; i++ ) {
165 faceNum = file->faceIndex[area->firstFace + i];
166 face = &file->faces[abs(faceNum)];
168 otherAreaNum = face->areas[INTSIGNBITNOTSET(faceNum)];
170 if ( otherAreaNum == 0 ) {
174 if ( !CanSwimInArea( otherAreaNum ) ) {
178 if ( ReachabilityExists( areaNum, otherAreaNum ) ) {
182 // create reachability going through this face
183 reach = new idReachability_Swim();
184 reach->travelType = TFL_SWIM;
185 reach->toAreaNum = otherAreaNum;
186 reach->fromAreaNum = areaNum;
188 reach->travelTime = 1;
189 reach->start = file->FaceCenter( abs(faceNum) );
191 reach->end = reach->start + file->planeList[face->planeNum].Normal() * INSIDEUNITS_SWIMEND;
193 reach->end = reach->start - file->planeList[face->planeNum].Normal() * INSIDEUNITS_SWIMEND;
195 AddReachabilityToArea( reach, areaNum );
201 idAASReach::Reachability_EqualFloorHeight
204 void idAASReach::Reachability_EqualFloorHeight( int areaNum ) {
205 int i, k, l, m, n, faceNum, face1Num, face2Num, otherAreaNum, edge1Num, edge2Num;
206 aasArea_t *area, *otherArea;
207 aasFace_t *face, *face1, *face2;
208 idReachability_Walk *reach;
210 if ( !AreaHasFloor( areaNum ) ) {
214 area = &file->areas[areaNum];
216 for ( i = 0; i < area->numFaces; i++ ) {
217 faceNum = file->faceIndex[area->firstFace + i];
218 face = &file->faces[abs(faceNum)];
220 otherAreaNum = face->areas[INTSIGNBITNOTSET(faceNum)];
221 if ( !AreaHasFloor( otherAreaNum ) ) {
225 otherArea = &file->areas[otherAreaNum];
227 for ( k = 0; k < area->numFaces; k++ ) {
228 face1Num = file->faceIndex[area->firstFace + k];
229 face1 = &file->faces[abs(face1Num)];
231 if ( !( face1->flags & FACE_FLOOR ) ) {
234 for ( l = 0; l < otherArea->numFaces; l++ ) {
235 face2Num = file->faceIndex[otherArea->firstFace + l];
236 face2 = &file->faces[abs(face2Num)];
238 if ( !( face2->flags & FACE_FLOOR ) ) {
242 for ( m = 0; m < face1->numEdges; m++ ) {
243 edge1Num = abs(file->edgeIndex[face1->firstEdge + m]);
244 for ( n = 0; n < face2->numEdges; n++ ) {
245 edge2Num = abs(file->edgeIndex[face2->firstEdge + n]);
246 if ( edge1Num == edge2Num ) {
250 if ( n < face2->numEdges ) {
254 if ( m < face1->numEdges ) {
258 if ( l < otherArea->numFaces ) {
262 if ( k < area->numFaces ) {
263 // create reachability
264 reach = new idReachability_Walk();
265 reach->travelType = TFL_WALK;
266 reach->toAreaNum = otherAreaNum;
267 reach->fromAreaNum = areaNum;
268 reach->edgeNum = abs( edge1Num );
269 reach->travelTime = 1;
270 reach->start = file->EdgeCenter( edge1Num );
272 reach->end = reach->start + file->planeList[face->planeNum].Normal() * INSIDEUNITS_WALKEND;
275 reach->end = reach->start - file->planeList[face->planeNum].Normal() * INSIDEUNITS_WALKEND;
277 AddReachabilityToArea( reach, areaNum );
284 idAASReach::Reachability_Step_Barrier_WaterJump_WalkOffLedge
287 bool idAASReach::Reachability_Step_Barrier_WaterJump_WalkOffLedge( int area1num, int area2num ) {
288 int i, j, k, l, edge1Num, edge2Num, areas[10];
289 int floor_bestArea1FloorEdgeNum, floor_bestArea2FloorEdgeNum, floor_foundReach;
290 int water_bestArea1FloorEdgeNum, water_bestArea2FloorEdgeNum, water_foundReach;
291 int side1, faceSide1, floorFace1Num;
292 float dist, dist1, dist2, diff, invGravityDot, orthogonalDot;
293 float x1, x2, x3, x4, y1, y2, y3, y4, tmp, y;
294 float length, floor_bestLength, water_bestLength, floor_bestDist, water_bestDist;
295 idVec3 v1, v2, v3, v4, tmpv, p1area1, p1area2, p2area1, p2area2;
296 idVec3 normal, orthogonal, edgeVec, start, end;
297 idVec3 floor_bestStart, floor_bestEnd, floor_bestNormal;
298 idVec3 water_bestStart, water_bestEnd, water_bestNormal;
301 aasArea_t *area1, *area2;
302 aasFace_t *floorFace1, *floorFace2, *floor_bestFace1, *water_bestFace1;
303 aasEdge_t *edge1, *edge2;
304 idReachability_Walk *walkReach;
305 idReachability_BarrierJump *barrierJumpReach;
306 idReachability_WaterJump *waterJumpReach;
307 idReachability_WalkOffLedge *walkOffLedgeReach;
310 // must be able to walk or swim in the first area
311 if ( !AreaHasFloor( area1num ) && !CanSwimInArea( area1num ) ) {
315 if ( !AreaHasFloor( area2num ) && !CanSwimInArea( area2num ) ) {
319 area1 = &file->areas[area1num];
320 area2 = &file->areas[area2num];
322 // if the areas are not near anough in the x-y direction
323 for ( i = 0; i < 2; i++ ) {
324 if ( area1->bounds[0][i] > area2->bounds[1][i] + 2.0f ) {
327 if ( area1->bounds[1][i] < area2->bounds[0][i] - 2.0f ) {
332 floor_foundReach = false;
333 floor_bestDist = 99999;
334 floor_bestLength = 0;
335 floor_bestArea2FloorEdgeNum = 0;
337 water_foundReach = false;
338 water_bestDist = 99999;
339 water_bestLength = 0;
340 water_bestArea2FloorEdgeNum = 0;
342 for ( i = 0; i < area1->numFaces; i++ ) {
343 floorFace1Num = file->faceIndex[area1->firstFace + i];
344 faceSide1 = floorFace1Num < 0;
345 floorFace1 = &file->faces[abs(floorFace1Num)];
347 // if this isn't a floor face
348 if ( !(floorFace1->flags & FACE_FLOOR) ) {
350 // if we can swim in the first area
351 if ( CanSwimInArea( area1num ) ) {
353 // face plane must be more or less horizontal
354 plane = &file->planeList[ floorFace1->planeNum ^ (!faceSide1) ];
355 if ( plane->Normal() * file->settings.invGravityDir < file->settings.minFloorCos ) {
360 // if we can't swim in the area it must be a ground face
365 for ( k = 0; k < floorFace1->numEdges; k++ ) {
366 edge1Num = file->edgeIndex[floorFace1->firstEdge + k];
367 side1 = (edge1Num < 0);
368 // NOTE: for water faces we must take the side area 1 is on into
369 // account because the face is shared and doesn't have to be oriented correctly
370 if ( !(floorFace1->flags & FACE_FLOOR) ) {
371 side1 = (side1 == faceSide1);
373 edge1Num = abs(edge1Num);
374 edge1 = &file->edges[edge1Num];
375 // vertices of the edge
376 v1 = file->vertices[edge1->vertexNum[!side1]];
377 v2 = file->vertices[edge1->vertexNum[side1]];
378 // get a vertical plane through the edge
379 // NOTE: normal is pointing into area 2 because the face edges are stored counter clockwise
381 normal = edgeVec.Cross( file->settings.invGravityDir );
385 // check the faces from the second area
386 for ( j = 0; j < area2->numFaces; j++ ) {
387 floorFace2 = &file->faces[abs(file->faceIndex[area2->firstFace + j])];
388 // must be a ground face
389 if ( !(floorFace2->flags & FACE_FLOOR) ) {
392 // check the edges of this ground face
393 for ( l = 0; l < floorFace2->numEdges; l++ ) {
394 edge2Num = abs(file->edgeIndex[floorFace2->firstEdge + l]);
395 edge2 = &file->edges[edge2Num];
396 // vertices of the edge
397 v3 = file->vertices[edge2->vertexNum[0]];
398 v4 = file->vertices[edge2->vertexNum[1]];
399 // check the distance between the two points and the vertical plane through the edge of area1
400 diff = normal * v3 - dist;
401 if ( diff < -0.2f || diff > 0.2f ) {
404 diff = normal * v4 - dist;
405 if ( diff < -0.2f || diff > 0.2f ) {
409 // project the two ground edges into the step side plane
410 // and calculate the shortest distance between the two
411 // edges if they overlap in the direction orthogonal to
412 // the gravity direction
413 orthogonal = file->settings.invGravityDir.Cross( normal );
414 invGravityDot = file->settings.invGravityDir * file->settings.invGravityDir;
415 orthogonalDot = orthogonal * orthogonal;
416 // projection into the step plane
417 // NOTE: since gravity is vertical this is just the z coordinate
418 y1 = v1[2];//(v1 * file->settings.invGravity) / invGravityDot;
419 y2 = v2[2];//(v2 * file->settings.invGravity) / invGravityDot;
420 y3 = v3[2];//(v3 * file->settings.invGravity) / invGravityDot;
421 y4 = v4[2];//(v4 * file->settings.invGravity) / invGravityDot;
423 x1 = (v1 * orthogonal) / orthogonalDot;
424 x2 = (v2 * orthogonal) / orthogonalDot;
425 x3 = (v3 * orthogonal) / orthogonalDot;
426 x4 = (v4 * orthogonal) / orthogonalDot;
429 tmp = x1; x1 = x2; x2 = tmp;
430 tmp = y1; y1 = y2; y2 = tmp;
431 tmpv = v1; v1 = v2; v2 = tmpv;
434 tmp = x3; x3 = x4; x4 = tmp;
435 tmp = y3; y3 = y4; y4 = tmp;
436 tmpv = v3; v3 = v4; v4 = tmpv;
438 // if the two projected edge lines have no overlap
439 if ( x2 <= x3 || x4 <= x1 ) {
442 // if the two lines fully overlap
443 if ( (x1 - 0.5f < x3 && x4 < x2 + 0.5f) && (x3 - 0.5f < x1 && x2 < x4 + 0.5f) ) {
452 // if the points are equal
453 if ( x1 > x3 - 0.1f && x1 < x3 + 0.1f ) {
458 else if ( x1 < x3 ) {
459 y = y1 + (x3 - x1) * (y2 - y1) / (x2 - x1);
466 y = y3 + (x1 - x3) * (y4 - y3) / (x4 - x3);
472 // if the points are equal
473 if ( x2 > x4 - 0.1f && x2 < x4 + 0.1f ) {
478 else if ( x2 < x4 ) {
479 y = y3 + (x2 - x3) * (y4 - y3) / (x4 - x3);
486 y = y1 + (x4 - x1) * (y2 - y1) / (x2 - x1);
494 // if both distances are pretty much equal then we take the middle of the points
495 if ( dist1 > dist2 - 1.0f && dist1 < dist2 + 1.0f ) {
497 start = ( p1area1 + p2area1 ) * 0.5f;
498 end = ( p1area2 + p2area2 ) * 0.5f;
500 else if (dist1 < dist2) {
511 // get the length of the overlapping part of the edges of the two areas
512 length = (p2area2 - p1area2).Length();
514 if ( floorFace1->flags & FACE_FLOOR ) {
515 // if the vertical distance is smaller
516 if ( dist < floor_bestDist ||
517 // or the vertical distance is pretty much the same
518 // but the overlapping part of the edges is longer
519 (dist < floor_bestDist + 1.0f && length > floor_bestLength) ) {
520 floor_bestDist = dist;
521 floor_bestLength = length;
522 floor_foundReach = true;
523 floor_bestArea1FloorEdgeNum = edge1Num;
524 floor_bestArea2FloorEdgeNum = edge2Num;
525 floor_bestFace1 = floorFace1;
526 floor_bestStart = start;
527 floor_bestNormal = normal;
532 // if the vertical distance is smaller
533 if ( dist < water_bestDist ||
534 //or the vertical distance is pretty much the same
535 //but the overlapping part of the edges is longer
536 (dist < water_bestDist + 1.0f && length > water_bestLength) ) {
537 water_bestDist = dist;
538 water_bestLength = length;
539 water_foundReach = true;
540 water_bestArea1FloorEdgeNum = edge1Num;
541 water_bestArea2FloorEdgeNum = edge2Num;
542 water_bestFace1 = floorFace1;
543 water_bestStart = start; // best start point in area1
544 water_bestNormal = normal; // normal is pointing into area2
545 water_bestEnd = end; // best point towards area2
553 // NOTE: swim reachabilities should already be filtered out
558 // | step height -> TFL_WALK
562 // ~~~~~~~~| step height and low water -> TFL_WALK
565 // ~~~~~~~~~~~~~~~~~~
567 // | step height and low water up to the step -> TFL_WALK
570 // check for a step reachability
571 if ( floor_foundReach ) {
572 // if area2 is higher but lower than the maximum step height
573 // NOTE: floor_bestDist >= 0 also catches equal floor reachabilities
574 if ( floor_bestDist >= 0 && floor_bestDist < file->settings.maxStepHeight ) {
575 // create walk reachability from area1 to area2
576 walkReach = new idReachability_Walk();
577 walkReach->travelType = TFL_WALK;
578 walkReach->toAreaNum = area2num;
579 walkReach->fromAreaNum = area1num;
580 walkReach->start = floor_bestStart + INSIDEUNITS_WALKSTART * floor_bestNormal;
581 walkReach->end = floor_bestEnd + INSIDEUNITS_WALKEND * floor_bestNormal;
582 walkReach->edgeNum = abs( floor_bestArea1FloorEdgeNum );
583 walkReach->travelTime = 0;
584 if ( area2->flags & AREA_CROUCH ) {
585 walkReach->travelTime += file->settings.tt_startCrouching;
587 AddReachabilityToArea( walkReach, area1num );
598 // | higher than step height and water up to waterjump height -> TFL_WATERJUMP
601 // ~~~~~~~~~~~~~~~~~~
606 // | higher than step height and low water up to the step -> TFL_WATERJUMP
609 // check for a waterjump reachability
610 if ( water_foundReach ) {
611 // get a test point a little bit towards area1
612 testPoint = water_bestEnd - INSIDEUNITS * water_bestNormal;
613 // go down the maximum waterjump height
614 testPoint[2] -= file->settings.maxWaterJumpHeight;
615 // if there IS water the sv_maxwaterjump height below the bestend point
616 if ( area1->flags & AREA_LIQUID ) {
617 // don't create rediculous water jump reachabilities from areas very far below the water surface
618 if ( water_bestDist < file->settings.maxWaterJumpHeight + 24 ) {
619 // water jumping from or towards a crouch only areas is not possible
620 if ( !(area1->flags & AREA_CROUCH) && !(area2->flags & AREA_CROUCH) ) {
621 // create water jump reachability from area1 to area2
622 waterJumpReach = new idReachability_WaterJump();
623 waterJumpReach->travelType = TFL_WATERJUMP;
624 waterJumpReach->toAreaNum = area2num;
625 waterJumpReach->fromAreaNum = area1num;
626 waterJumpReach->start = water_bestStart;
627 waterJumpReach->end = water_bestEnd + INSIDEUNITS_WATERJUMP * water_bestNormal;
628 waterJumpReach->edgeNum = abs( floor_bestArea1FloorEdgeNum );
629 waterJumpReach->travelTime = file->settings.tt_waterJump;
630 AddReachabilityToArea( waterJumpReach, area1num );
643 // | higher than max step height lower than max barrier height -> TFL_BARRIERJUMP
650 // ~~~~~~~~| higher than max step height lower than max barrier height
651 // --------| and a thin layer of water in the area to jump from -> TFL_BARRIERJUMP
653 // check for a barrier jump reachability
654 if ( floor_foundReach ) {
655 //if area2 is higher but lower than the maximum barrier jump height
656 if ( floor_bestDist > 0 && floor_bestDist < file->settings.maxBarrierHeight ) {
657 //if no water in area1 or a very thin layer of water on the ground
658 if ( !water_foundReach || (floor_bestDist - water_bestDist < 16) ) {
659 // cannot perform a barrier jump towards or from a crouch area
660 if ( !(area1->flags & AREA_CROUCH) && !(area2->flags & AREA_CROUCH) ) {
661 // create barrier jump reachability from area1 to area2
662 barrierJumpReach = new idReachability_BarrierJump();
663 barrierJumpReach->travelType = TFL_BARRIERJUMP;
664 barrierJumpReach->toAreaNum = area2num;
665 barrierJumpReach->fromAreaNum = area1num;
666 barrierJumpReach->start = floor_bestStart + INSIDEUNITS_WALKSTART * floor_bestNormal;
667 barrierJumpReach->end = floor_bestEnd + INSIDEUNITS_WALKEND * floor_bestNormal;
668 barrierJumpReach->edgeNum = abs( floor_bestArea1FloorEdgeNum );
669 barrierJumpReach->travelTime = file->settings.tt_barrierJump;
670 AddReachabilityToArea( barrierJumpReach, area1num );
677 // Walk and Walk Off Ledge
680 // | can walk or step back -> TFL_WALK
687 // | cannot walk/step back -> TFL_WALKOFFLEDGE
694 // | cannot step back but can waterjump back -> TFL_WALKOFFLEDGE
695 // --------- FIXME: create TFL_WALK reach??
697 // check for a walk or walk off ledge reachability
698 if ( floor_foundReach ) {
699 if ( floor_bestDist < 0 ) {
700 if ( floor_bestDist > -file->settings.maxStepHeight ) {
701 // create walk reachability from area1 to area2
702 walkReach = new idReachability_Walk();
703 walkReach->travelType = TFL_WALK;
704 walkReach->toAreaNum = area2num;
705 walkReach->fromAreaNum = area1num;
706 walkReach->start = floor_bestStart + INSIDEUNITS_WALKSTART * floor_bestNormal;
707 walkReach->end = floor_bestEnd + INSIDEUNITS_WALKEND * floor_bestNormal;
708 walkReach->edgeNum = abs( floor_bestArea1FloorEdgeNum );
709 walkReach->travelTime = 1;
710 AddReachabilityToArea( walkReach, area1num );
713 // if no maximum fall height set or less than the max
714 if ( !file->settings.maxFallHeight || idMath::Fabs(floor_bestDist) < file->settings.maxFallHeight ) {
715 // trace a bounding box vertically to check for solids
716 floor_bestEnd += INSIDEUNITS * floor_bestNormal;
717 start = floor_bestEnd;
718 start[2] = floor_bestStart[2];
722 trace.maxAreas = sizeof(areas) / sizeof(int);
723 file->Trace( trace, start, end );
724 // if the trace didn't start in solid and nothing was hit
725 if ( trace.lastAreaNum && trace.fraction >= 1.0f ) {
726 // the trace end point must be in the goal area
727 if ( trace.lastAreaNum == area2num ) {
728 // don't create reachability if going through a cluster portal
729 for (i = 0; i < trace.numAreas; i++) {
730 if ( AreaIsClusterPortal( trace.areas[i] ) ) {
734 if ( i >= trace.numAreas ) {
735 // create a walk off ledge reachability from area1 to area2
736 walkOffLedgeReach = new idReachability_WalkOffLedge();
737 walkOffLedgeReach->travelType = TFL_WALKOFFLEDGE;
738 walkOffLedgeReach->toAreaNum = area2num;
739 walkOffLedgeReach->fromAreaNum = area1num;
740 walkOffLedgeReach->start = floor_bestStart;
741 walkOffLedgeReach->end = floor_bestEnd;
742 walkOffLedgeReach->edgeNum = abs( floor_bestArea1FloorEdgeNum );
743 walkOffLedgeReach->travelTime = file->settings.tt_startWalkOffLedge + idMath::Fabs(floor_bestDist) * 50 / file->settings.gravityValue;
744 AddReachabilityToArea( walkOffLedgeReach, area1num );
757 idAASReach::Reachability_WalkOffLedge
760 void idAASReach::Reachability_WalkOffLedge( int areaNum ) {
761 int i, j, faceNum, edgeNum, side, reachAreaNum, p, areas[10];
766 idVec3 v1, v2, mid, dir, testEnd;
767 idReachability_WalkOffLedge *reach;
770 if ( !AreaHasFloor( areaNum ) || CanSwimInArea( areaNum ) ) {
774 area = &file->areas[areaNum];
776 for ( i = 0; i < area->numFaces; i++ ) {
777 faceNum = file->faceIndex[area->firstFace + i];
778 face = &file->faces[abs(faceNum)];
780 // face must be a floor face
781 if ( !(face->flags & FACE_FLOOR) ) {
785 for ( j = 0; j < face->numEdges; j++ ) {
787 edgeNum = file->edgeIndex[face->firstEdge + j];
788 edge = &file->edges[abs(edgeNum)];
790 //if ( !(edge->flags & EDGE_LEDGE) ) {
796 v1 = file->vertices[edge->vertexNum[side]];
797 v2 = file->vertices[edge->vertexNum[!side]];
799 plane = &file->planeList[face->planeNum ^ INTSIGNBITSET(faceNum) ];
801 // get the direction into the other area
802 dir = plane->Normal().Cross( v2 - v1 );
805 mid = ( v1 + v2 ) * 0.5f;
806 testEnd = mid + INSIDEUNITS_WALKEND * dir;
807 testEnd[2] -= file->settings.maxFallHeight + 1.0f;
809 trace.maxAreas = sizeof(areas) / sizeof(int);
810 file->Trace( trace, mid, testEnd );
812 reachAreaNum = trace.lastAreaNum;
813 if ( !reachAreaNum || reachAreaNum == areaNum ) {
816 if ( idMath::Fabs( mid[2] - trace.endpos[2] ) > file->settings.maxFallHeight ) {
819 if ( !AreaHasFloor( reachAreaNum ) && !CanSwimInArea( reachAreaNum ) ) {
822 if ( ReachabilityExists( areaNum, reachAreaNum) ) {
825 // if not going through a cluster portal
826 for ( p = 0; p < trace.numAreas; p++ ) {
827 if ( AreaIsClusterPortal( trace.areas[p] ) ) {
831 if ( p < trace.numAreas ) {
835 reach = new idReachability_WalkOffLedge();
836 reach->travelType = TFL_WALKOFFLEDGE;
837 reach->toAreaNum = reachAreaNum;
838 reach->fromAreaNum = areaNum;
840 reach->end = trace.endpos;
841 reach->edgeNum = abs( edgeNum );
842 reach->travelTime = file->settings.tt_startWalkOffLedge + idMath::Fabs(mid[2] - trace.endpos[2]) * 50 / file->settings.gravityValue;
843 AddReachabilityToArea( reach, areaNum );
850 idAASReach::FlagReachableAreas
853 void idAASReach::FlagReachableAreas( idAASFileLocal *file ) {
854 int i, numReachableAreas;
856 numReachableAreas = 0;
857 for ( i = 1; i < file->areas.Num(); i++ ) {
859 if ( ( file->areas[i].flags & ( AREA_FLOOR | AREA_LADDER ) ) ||
860 ( file->areas[i].contents & AREACONTENTS_WATER ) ) {
861 file->areas[i].flags |= AREA_REACHABLE_WALK;
863 if ( file->GetSettings().allowFlyReachabilities ) {
864 file->areas[i].flags |= AREA_REACHABLE_FLY;
869 common->Printf( "%6d reachable areas\n", numReachableAreas );
877 bool idAASReach::Build( const idMapFile *mapFile, idAASFileLocal *file ) {
878 int i, j, lastPercent, percent;
880 this->mapFile = mapFile;
882 numReachabilities = 0;
884 common->Printf( "[Reachability]\n" );
886 // delete all existing reachabilities
887 file->DeleteReachabilities();
889 FlagReachableAreas( file );
891 for ( i = 1; i < file->areas.Num(); i++ ) {
892 if ( !( file->areas[i].flags & AREA_REACHABLE_WALK ) ) {
895 if ( file->GetSettings().allowSwimReachabilities ) {
896 Reachability_Swim( i );
898 Reachability_EqualFloorHeight( i );
902 for ( i = 1; i < file->areas.Num(); i++ ) {
904 if ( !( file->areas[i].flags & AREA_REACHABLE_WALK ) ) {
908 for ( j = 0; j < file->areas.Num(); j++ ) {
913 if ( !( file->areas[j].flags & AREA_REACHABLE_WALK ) ) {
917 if ( ReachabilityExists( i, j ) ) {
920 if ( Reachability_Step_Barrier_WaterJump_WalkOffLedge( i, j ) ) {
925 //Reachability_WalkOffLedge( i );
927 percent = 100 * i / file->areas.Num();
928 if ( percent > lastPercent ) {
929 common->Printf( "\r%6d%%", percent );
930 lastPercent = percent;
934 if ( file->GetSettings().allowFlyReachabilities ) {
935 for ( i = 1; i < file->areas.Num(); i++ ) {
936 Reachability_Fly( i );
940 file->LinkReversedReachability();
942 common->Printf( "\r%6d reachabilities\n", numReachabilities );