etqw-sdk/source/game/botai/aas/AAS_pathing.cpp

747 lines
21 KiB
C++

// Copyright (C) 2007 Id Software, Inc.
//
#include "../../precompiled.h"
#pragma hdrstop
#include "AAS_local.h"
#include "../../../libs/AASLib/AASFile.h"
#include "../BotThread.h"
#include "../BotThreadData.h"
const float MAX_WALK_PATH_DISTANCE = 512.0f;
const float MAX_WALK_PATH_DEST_DISTANCE = 4096.0f;
const int MAX_WALK_PATH_ITERATIONS = 10;
const int MAX_WALK_PATH_SUB_SAMPLE_ITERATIONS = 8;
idCVar aas_optimizePaths( "aas_optimizePaths", "1", CVAR_BOOL | CVAR_CHEAT | CVAR_GAME, "set to 1 to enable path optimization" );
idCVar aas_subSampleWalkPaths( "aas_subSampleWalkPaths", "1", CVAR_BOOL | CVAR_CHEAT | CVAR_GAME, "set to 1 to enable walk path sub-sampling" );
idCVar aas_extendFlyPaths( "aas_extendFlyPaths", "1", CVAR_BOOL | CVAR_CHEAT | CVAR_GAME, "set to 1 to enable extending fly paths" );
/*
============
idAreaTrail
============
*/
class idAreaTrail {
public:
static const int MAX_AREA_TRAIL = 4;
idAreaTrail( int startAreaNum ) {
for ( int i = 0; i < MAX_AREA_TRAIL; i++ ) {
areaTrail[i] = startAreaNum;
}
trailIndex = 0;
}
void Add( int areaNum ) {
areaTrail[trailIndex] = areaNum;
trailIndex = ( trailIndex + 1 ) & ( MAX_AREA_TRAIL - 1 );
}
bool InList( int areaNum ) {
for ( int i = 0; i < MAX_AREA_TRAIL; i++ ) {
if ( areaNum == areaTrail[i] ) {
return true;
}
}
return false;
}
private:
int areaTrail[MAX_AREA_TRAIL];
int trailIndex;
};
const int idAreaTrail::MAX_AREA_TRAIL;
/*
============
idAASLocal::WalkPathIsValid
Returns true if one can walk in a straight line from start to goal.
============
*/
ID_INLINE bool idAASLocal::WalkPathIsValid( int startAreaNum, const idVec3 &startOrigin, int goalAreaNum,
const idVec3 &goalOrigin, int travelFlags, int &endAreaNum ) const {
aasTraceFloor_t trace;
file->TraceFloor( trace, startOrigin, startAreaNum, goalOrigin, goalAreaNum, travelFlags );
endAreaNum = trace.lastAreaNum;
return ( endAreaNum == goalAreaNum );
}
/*
============
idAASLocal::SubSampleWalkPath
============
*/
void idAASLocal::SubSampleWalkPath( int startAreaNum, const idVec3 &startOrigin, int pathAreaNum, const idVec3 &pathStart, const idVec3 &pathEnd, int travelFlags, idVec3 &endPos, int &endAreaNum ) const {
idVec3 bestEndPos = pathStart;
int bestEndAreaNum = pathAreaNum;
idVec3 dir = pathEnd - pathStart;
float length = dir.LengthSqr();
float step = 0.25f;
float fraction = 0.5f;
for ( int i = 0; i < MAX_WALK_PATH_SUB_SAMPLE_ITERATIONS && length * step > Square( 4.0f ); i++, step *= 0.5f ) {
idVec3 pathPoint = pathStart + dir * fraction;
int curEndAreaNum;
if ( WalkPathIsValid( startAreaNum, startOrigin, pathAreaNum, pathPoint, travelFlags, curEndAreaNum ) ) {
bestEndPos = pathPoint;
bestEndAreaNum = curEndAreaNum;
fraction += step;
} else {
fraction -= step;
}
}
endPos = bestEndPos;
endAreaNum = bestEndAreaNum;
}
/*
============
idAASLocal::WalkPathToGoal
============
*/
bool idAASLocal::WalkPathToGoal( idAASPath &path, int startAreaNum, const idVec3 &startOrigin,
int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, int walkTravelFlags ) const {
int travelTime, endAreaNum;
const aasReachability_t *reach = NULL;
path.type = PATHTYPE_WALK;
path.moveGoal = startOrigin;
path.moveAreaNum = startAreaNum;
path.reachability = NULL;
path.viewGoal = startOrigin;
path.travelTime = 1;
if ( file == NULL ) {
path.moveGoal = goalOrigin;
return true;
}
// if the start and goal are in the same area
if ( startAreaNum == goalAreaNum ) {
path.moveGoal = goalOrigin;
return true;
}
// test for a direct path to the destination
if ( ( goalOrigin - startOrigin ).LengthSqr() < Square( MAX_WALK_PATH_DEST_DISTANCE ) ) {
if ( WalkPathIsValid( startAreaNum, startOrigin, goalAreaNum, goalOrigin, walkTravelFlags, endAreaNum ) ) {
path.moveGoal = goalOrigin;
path.moveAreaNum = goalAreaNum;
return true;
}
}
idAreaTrail areaTrail( startAreaNum );
int badTravelFlags = ~walkTravelFlags;
int curAreaNum = startAreaNum;
for ( int i = 0; i < MAX_WALK_PATH_ITERATIONS; i++ ) {
if ( !RouteToGoalArea( curAreaNum, path.moveGoal, goalAreaNum, travelFlags, travelTime, &reach ) ) {
return false;
}
if ( reach == NULL ) {
return false;
}
if ( i == 0 ) {
path.travelTime = travelTime;
}
// no need to check through the first area
if ( curAreaNum != startAreaNum ) {
// only optimize a limited distance ahead
if ( ( reach->GetStart() - startOrigin ).LengthSqr() > Square( MAX_WALK_PATH_DISTANCE ) ) {
if ( aas_subSampleWalkPaths.GetBool() ) {
SubSampleWalkPath( startAreaNum, startOrigin, curAreaNum, path.moveGoal, reach->GetStart(), walkTravelFlags, path.moveGoal, path.moveAreaNum );
}
return true;
}
// if we can't walk directly to the start of the reachability, stop routing
// and get a sub-sampled path for as far as we can directly walk.
if ( !WalkPathIsValid( startAreaNum, startOrigin, curAreaNum, reach->GetStart(), walkTravelFlags, endAreaNum ) ) {
if ( aas_subSampleWalkPaths.GetBool() ) {
SubSampleWalkPath( startAreaNum, startOrigin, curAreaNum, path.moveGoal, reach->GetStart(), walkTravelFlags, path.moveGoal, path.moveAreaNum );
}
return true;
}
}
path.moveGoal = reach->GetStart();
path.moveAreaNum = curAreaNum;
if ( ( reach->travelFlags & badTravelFlags ) != 0 ) {
break;
}
path.moveGoal = reach->GetEnd();
path.moveAreaNum = reach->toAreaNum;
if ( !aas_optimizePaths.GetBool() ) {
return true;
}
if ( reach->toAreaNum == goalAreaNum ) {
if ( !WalkPathIsValid( startAreaNum, startOrigin, goalAreaNum, goalOrigin, walkTravelFlags, endAreaNum ) ) {
if ( aas_subSampleWalkPaths.GetBool() ) {
SubSampleWalkPath( startAreaNum, startOrigin, goalAreaNum, path.moveGoal, goalOrigin, walkTravelFlags, path.moveGoal, path.moveAreaNum );
}
return true;
}
path.moveGoal = goalOrigin;
path.moveAreaNum = goalAreaNum;
return true;
}
curAreaNum = reach->toAreaNum;
if ( areaTrail.InList( curAreaNum ) ) {
botThreadData.Warning( "idAASLocal::WalkPathToGoal: local routing minimum at area %d while going from area %d to area %d", curAreaNum, startAreaNum, goalAreaNum );
break;
}
areaTrail.Add( curAreaNum );
}
switch( reach->travelFlags ) {
case TFL_WALKOFFLEDGE:
path.type = PATHTYPE_WALKOFFLEDGE;
path.reachability = reach;
path.moveGoal = reach->GetEnd();
path.moveAreaNum = reach->toAreaNum;
break;
case TFL_WALKOFFBARRIER: {
path.type = PATHTYPE_WALKOFFBARRIER;
path.reachability = reach;
path.moveGoal = reach->GetEnd();
path.moveAreaNum = reach->toAreaNum;
break;
}
case TFL_BARRIERJUMP:
path.type = PATHTYPE_BARRIERJUMP;
path.reachability = reach;
break;
case TFL_JUMP:
path.type = PATHTYPE_JUMP;
path.reachability = reach;
break;
case TFL_LADDER:
path.type = PATHTYPE_LADDER;
path.reachability = reach;
path.moveGoal = reach->GetEnd();
path.moveAreaNum = reach->toAreaNum;
break;
case TFL_TELEPORT:
path.type = PATHTYPE_TELEPORT;
path.reachability = reach;
break;
case TFL_ELEVATOR:
path.type = PATHTYPE_ELEVATOR;
path.reachability = reach;
break;
default:
break;
}
return true;
}
/*
============
idAASLocal::HorizontalDistanceSquare
============
*/
ID_INLINE float idAASLocal::HorizontalDistanceSquare( const idVec3 &start, const idVec3 &end ) const {
idVec3 dir = end - start;
dir -= file->GetSettings().invGravityDir * dir * file->GetSettings().invGravityDir;
return dir.LengthSqr();
}
/*
============
idAASLocal::HopPathIsValid
============
*/
bool idAASLocal::HopPathIsValid( int startAreaNum, const idVec3 &startOrigin, int goalAreaNum, const idVec3 &goalOrigin, const idAASHopPathParms &parms ) const {
if ( ( file->GetArea( goalAreaNum ).flags & AAS_AREA_OUTSIDE ) == 0 ) {
return false;
}
if ( HorizontalDistanceSquare( startOrigin, goalOrigin ) > Square( parms.maxDistance ) ) {
return false;
}
const int MAX_POINTS = 512;
idVec3 points[MAX_POINTS];
aasTraceHeight_t trace;
trace.maxPoints = MAX_POINTS;
trace.points = points;
file->TraceHeight( trace, startOrigin, goalOrigin );
if ( trace.numPoints >= MAX_POINTS ) {
return false;
}
float baseHeight = startOrigin * file->GetSettings().invGravityDir;
// skip the first point which is in the start area
for ( int i = 1; i < trace.numPoints; i++ ) {
float height = trace.points[i] * file->GetSettings().invGravityDir - baseHeight;
float maxHeight = idMath::ClampFloat( parms.minHeight, parms.maxHeight, parms.maxSlope * idMath::Sqrt( HorizontalDistanceSquare( startOrigin, trace.points[i] ) ) );
if ( height > maxHeight ) {
return false;
}
}
return true;
}
/*
============
idAASLocal::ExtendHopPathToGoal
============
*/
bool idAASLocal::ExtendHopPathToGoal( idAASPath &path, int startAreaNum, const idVec3 &startOrigin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, int walkTravelFlags, const idAASHopPathParms &parms ) const {
int travelTime;
const aasReachability_t *reach = NULL;
if ( !aas_extendFlyPaths.GetBool() ) {
return false;
}
assert( path.type != MAX_PATHTYPE );
if ( ( file->GetArea( startAreaNum ).flags & AAS_AREA_OUTSIDE ) == 0 ) {
return false;
}
if ( path.moveGoal == goalOrigin ) {
return HopPathIsValid( startAreaNum, startOrigin, path.moveAreaNum, path.moveGoal, parms );
}
if ( ( file->GetArea( goalAreaNum ).flags & AAS_AREA_OUTSIDE ) != 0 ) {
if ( HopPathIsValid( startAreaNum, startOrigin, goalAreaNum, goalOrigin, parms ) ) {
path.moveGoal = goalOrigin;
path.moveAreaNum = goalAreaNum;
return true;
}
}
idAreaTrail areaTrail( path.moveAreaNum );
bool canHop = HopPathIsValid( startAreaNum, startOrigin, path.moveAreaNum, path.moveGoal, parms );
for ( int i = 0; i < MAX_WALK_PATH_ITERATIONS; i++ ) {
if ( !RouteToGoalArea( path.moveAreaNum, path.moveGoal, goalAreaNum, travelFlags, travelTime, &reach ) ) {
return false;
}
if ( reach == NULL ) {
return false;
}
if ( !HopPathIsValid( startAreaNum, startOrigin, reach->toAreaNum, reach->GetEnd(), parms ) ) {
break;
}
path.moveGoal = reach->GetEnd();
path.moveAreaNum = reach->toAreaNum;
canHop = true;
if ( reach->toAreaNum == goalAreaNum ) {
return canHop;
}
if ( areaTrail.InList( path.moveAreaNum ) ) {
botThreadData.Warning( "idAASLocal::HopPathToGoal: local routing minimum at area %d while going from area %d to area %d", path.moveAreaNum, startAreaNum, goalAreaNum );
break;
}
areaTrail.Add( path.moveAreaNum );
}
return canHop;
}
/*
============
idAASLocal::SetupObstaclePVS
============
*/
void idAASLocal::SetupObstaclePVS() {
numObstaclePVSBytes = ( file->GetNumAreas() + 7 ) / 8;
obstaclePVS = (byte *) Mem_AllocAligned( numObstaclePVSBytes, ALIGN_16 );
}
/*
============
idAASLocal::ShutdownObstaclePVS
============
*/
void idAASLocal::ShutdownObstaclePVS() {
Mem_FreeAligned( obstaclePVS );
numObstaclePVSBytes = 0;
obstaclePVS = NULL;
obstaclePVSAreaNum = 0;
}
/*
============
idAASLocal::GetObstaclePVS
============
*/
const byte * idAASLocal::GetObstaclePVS( int areaNum ) const {
if ( areaNum == obstaclePVSAreaNum ) {
return obstaclePVS;
}
memset( obstaclePVS, 0, numObstaclePVSBytes );
const aasArea_t &area = file->GetArea( areaNum );
for ( int offset = area.obstaclePVSOffset, index = 0; index < file->GetNumAreas(); ) {
int rleBits = file->GetObstaclePVS( offset++ );
if ( ( rleBits & AAS_PVS_RLE_RUN_BIT ) != 0 ) {
// short run-length code
int run = rleBits & ( ( 1 << AAS_PVS_RLE_1ST_COUNT_BITS ) - 1 );
// run-length mode
if ( ( rleBits & AAS_PVS_RLE_RUN_LONG_BIT ) != 0 ) {
// additional bits for long run-length code
run |= file->GetObstaclePVS( offset++ ) << AAS_PVS_RLE_1ST_COUNT_BITS;
}
index += ( run + 1 ) * AAS_PVS_RLE_RUN_GRANULARITY;
} else {
// immediate mode
for ( int i = 0; i < AAS_PVS_RLE_IMMEDIATE_BITS; i++ ) {
obstaclePVS[index >> 3] |= ( ( rleBits >> i ) & 1 ) << ( index & 7 );
index++;
}
}
}
obstaclePVSAreaNum = areaNum;
return obstaclePVS;
}
/*
============
idAASLocal::GetObstaclePVSWallEdges
============
*/
int idAASLocal::GetObstaclePVSWallEdges( int areaNum, int *edges, int maxEdges ) const {
int numEdges = 0;
if ( file == NULL ) {
return numEdges;
}
const aasArea_t &area = file->GetArea( areaNum );
for ( int offset = area.obstaclePVSOffset, index = 0; index < file->GetNumAreas(); ) {
int rleBits = file->GetObstaclePVS( offset++ );
if ( ( rleBits & AAS_PVS_RLE_RUN_BIT ) != 0 ) {
// short run-length code
int run = rleBits & ( ( 1 << AAS_PVS_RLE_1ST_COUNT_BITS ) - 1 );
// run-length mode
if ( ( rleBits & AAS_PVS_RLE_RUN_LONG_BIT ) != 0 ) {
// additional bits for long run-length code
run |= file->GetObstaclePVS( offset++ ) << AAS_PVS_RLE_1ST_COUNT_BITS;
}
index += ( run + 1 ) * AAS_PVS_RLE_RUN_GRANULARITY;
} else {
// immediate mode
for ( int i = 0; i < AAS_PVS_RLE_IMMEDIATE_BITS; i++ ) {
if ( ( rleBits & ( 1 << i ) ) == 0 ) {
index++;
continue;
}
const aasArea_t &pvsArea = file->GetArea( index++ );
for ( int j = 0; j < pvsArea.numEdges; j++ ) {
int edgeNum = file->GetEdgeIndex( pvsArea.firstEdge + j );
const aasEdge_t &edge = file->GetEdge( abs( edgeNum ) );
if ( ( edge.flags & ( AAS_EDGE_WALL | AAS_EDGE_LEDGE ) ) != 0 ) {
edges[numEdges++] = edgeNum;
if ( numEdges >= maxEdges ) {
return numEdges;
}
}
}
}
}
}
// FIXME: create short sequences per area above and inline sorting of sequences here
SortWallEdges( edges, numEdges );
return numEdges;
}
/*
============
idAASLocal::SortWallEdges
============
*/
struct wallEdge_t {
int edgeNum;
int verts[2];
wallEdge_t * next;
};
void idAASLocal::SortWallEdges( int *edges, int numEdges ) const {
int i, j, k, numSequences;
wallEdge_t **sequenceFirst, **sequenceLast, *wallEdges, *wallEdge;
wallEdges = (wallEdge_t *) _alloca16( numEdges * sizeof( wallEdge_t ) );
sequenceFirst = (wallEdge_t **)_alloca16( numEdges * sizeof( wallEdge_t * ) );
sequenceLast = (wallEdge_t **)_alloca16( numEdges * sizeof( wallEdge_t * ) );
for ( i = 0; i < numEdges; i++ ) {
int edgeNum = edges[i];
const int *v = file->GetEdge( abs( edgeNum ) ).vertexNum;
wallEdges[i].edgeNum = edgeNum;
wallEdges[i].verts[0] = v[INT32_SIGNBITSET( edgeNum )];
wallEdges[i].verts[1] = v[INT32_SIGNBITNOTSET( edgeNum )];
wallEdges[i].next = NULL;
sequenceFirst[i] = &wallEdges[i];
sequenceLast[i] = &wallEdges[i];
}
numSequences = numEdges;
for ( i = 0; i < numSequences; i++ ) {
for ( j = i+1; j < numSequences; j++ ) {
if ( sequenceFirst[i]->verts[0] == sequenceLast[j]->verts[1] ) {
sequenceLast[j]->next = sequenceFirst[i];
sequenceFirst[i] = sequenceFirst[j];
break;
}
if ( sequenceLast[i]->verts[1] == sequenceFirst[j]->verts[0] ) {
sequenceLast[i]->next = sequenceFirst[j];
sequenceLast[i] = sequenceLast[j];
break;
}
}
if ( j < numSequences ) {
numSequences--;
for ( k = j; k < numSequences; k++ ) {
sequenceFirst[k] = sequenceFirst[k+1];
sequenceLast[k] = sequenceLast[k+1];
}
i--;
}
}
k = 0;
for ( i = 0; i < numSequences; i++ ) {
for ( wallEdge = sequenceFirst[i]; wallEdge; wallEdge = wallEdge->next ) {
edges[k++] = wallEdge->edgeNum;
}
}
}
/*
============
idAASLocal::EdgesIntersect2D
============
*/
bool idAASLocal::EdgesIntersect2D( const int edge1, const int edge2, const idBounds &b1, const idBounds &b2, const float height ) const {
const float EDGE_INTERSECTION_EPSILON = 0.1f;
const int *e0 = file->GetEdge( abs(edge1) ).vertexNum;
const int *e1 = file->GetEdge( abs(edge2) ).vertexNum;
const aasVertex_t &v0 = file->GetVertex( e0[0] );
const aasVertex_t &v1 = file->GetVertex( e0[1] );
const aasVertex_t &v2 = file->GetVertex( e1[0] );
const aasVertex_t &v3 = file->GetVertex( e1[1] );
idVec3 plane1;
plane1.x = v0.y - v1.y;
plane1.y = v1.x - v0.x;
plane1.z = - ( v0.x * plane1.x + v0.y * plane1.y );
float d0 = plane1.x * v2.x + plane1.y * v2.y + plane1.z;
float d1 = plane1.x * v3.x + plane1.y * v3.y + plane1.z;
// if the edges are in the same plane
if ( fabs( d0 ) < EDGE_INTERSECTION_EPSILON && fabs( d1 ) < EDGE_INTERSECTION_EPSILON ) {
// edges must be at least 'height' units apart vertically
if ( b1[0].z - b2[1].z > height || b2[0].z - b1[1].z > height ) {
// the edges intersect because the 2D bounds overlap
return true;
}
return false;
}
// if both points of the second edge are at the same side of the vertical plane through the first edge
if ( !( IEEE_FLT_SIGNBITSET( d0 ) ^ IEEE_FLT_SIGNBITSET( d1 ) ) ) {
return false;
}
idVec3 plane2;
plane2.x = v2.y - v3.y;
plane2.y = v3.x - v2.x;
plane2.z = - ( v2.x * plane2.x + v2.y * plane2.y );
float d2 = plane2.x * v0.x + plane2.y * v0.y + plane2.z;
float d3 = plane2.x * v1.x + plane2.y * v1.y + plane2.z;
// if both points of the first edge are at the same side of the vertical plane through the second edge
if ( !( IEEE_FLT_SIGNBITSET( d2 ) ^ IEEE_FLT_SIGNBITSET( d3 ) ) ) {
return false;
}
d0 = d0 / ( d0 - d1 );
float z0 = v2.z + d0 * ( v3.z - v2.z );
d2 = d2 / ( d2 - d3 );
float z1 = v0.z + d2 * ( v1.z - v0.z );
// at the crossing point the height difference should be at least 'height' units
if ( fabs( z0 - z1 ) > height ) {
return true;
}
return false;
}
/*
============
idAASLocal::GetWallEdges
============
*/
int idAASLocal::GetWallEdges( int areaNum, const idBounds &bounds, int travelFlags, float height, int *edges, int maxEdges ) const {
int j, k, numEdges;
int *areaQueue, curArea, queueStart, queueEnd;
byte *areasVisited;
idBounds *edgeBounds;
if ( file == NULL ) {
return 0;
}
numEdges = 0;
areasVisited = (byte *) _alloca16( file->GetNumAreas() );
memset( areasVisited, 0, file->GetNumAreas() * sizeof( byte ) );
areaQueue = (int *) _alloca16( file->GetNumAreas() * sizeof( int ) );
edgeBounds = (idBounds *) _alloca16( maxEdges * sizeof( edgeBounds[0] ) );
queueStart = -1;
queueEnd = 0;
areaQueue[0] = areaNum;
areasVisited[areaNum] = true;
for ( curArea = areaNum; queueStart < queueEnd; curArea = areaQueue[++queueStart] ) {
const aasArea_t &area = file->GetArea( curArea );
bool edgeIntersection = false;
int withoutAreaNumEdges = numEdges;
idBounds areaBounds;
areaBounds.Clear();
for ( j = 0; j < area.numEdges && !edgeIntersection; j++ ) {
int edgeNum = file->GetEdgeIndex( area.firstEdge + j );
const aasEdge_t &edge = file->GetEdge( abs( edgeNum ) );
edgeBounds[numEdges][0] = edgeBounds[numEdges][1] = file->GetVertex( edge.vertexNum[0] );
edgeBounds[numEdges].AddPoint( file->GetVertex( edge.vertexNum[1] ) );
areaBounds.AddPoint( file->GetVertex( edge.vertexNum[0] ) );
if ( ( edge.flags & ( AAS_EDGE_WALL | AAS_EDGE_LEDGE ) ) == 0 ) {
continue;
}
// test if the edge is already in the list or intersects an existing edge
for ( k = 0; k < numEdges; k++ ) {
if ( edgeNum == edges[k] ) {
break;
}
// edges must have the same flags
const aasEdge_t &otherEdge = file->GetEdge( abs( edges[k] ) );
if ( ( edge.flags & ( AAS_EDGE_WALL | AAS_EDGE_LEDGE ) ) != ( otherEdge.flags & ( AAS_EDGE_WALL | AAS_EDGE_LEDGE ) ) ) {
continue;
}
// edges must overlap in 2D
if ( !edgeBounds[numEdges].IntersectsBounds2D( edgeBounds[k] ) ) {
continue;
}
if ( height > 0.0f && EdgesIntersect2D( edgeNum, edges[k], edgeBounds[numEdges], edgeBounds[k], height ) ) {
edgeIntersection = true;
break;
}
}
if ( k < numEdges ) {
continue;
}
// add the edge to the list
edges[numEdges++] = edgeNum;
if ( numEdges >= maxEdges ) {
return numEdges;
}
}
// if edges from this area intersected already collected edges
if ( edgeIntersection ) {
numEdges = withoutAreaNumEdges;
continue;
}
// if this area is already outside the search bounds
if ( !bounds.IntersectsBounds( areaBounds ) ) {
continue;
}
// add new areas to the queue
for ( aasReachability_t *reach = area.reach; reach; reach = reach->next ) {
if ( ( reach->travelFlags & travelFlags ) != 0 ) {
// if the area the reachability leads to hasn't been visited yet
if ( !areasVisited[reach->toAreaNum] ) {
areaQueue[queueEnd++] = reach->toAreaNum;
areasVisited[reach->toAreaNum] = true;
}
}
}
}
SortWallEdges( edges, numEdges );
return numEdges;
}