DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

AAS_routing.cpp (37423B)


      1 /*
      2 ===========================================================================
      3 
      4 Doom 3 BFG Edition GPL Source Code
      5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 
      6 
      7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").  
      8 
      9 Doom 3 BFG Edition 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.
     13 
     14 Doom 3 BFG Edition 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.
     18 
     19 You should have received a copy of the GNU General Public License
     20 along with Doom 3 BFG Edition Source Code.  If not, see <http://www.gnu.org/licenses/>.
     21 
     22 In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code.  If not, please request a copy in writing from id Software at the address below.
     23 
     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.
     25 
     26 ===========================================================================
     27 */
     28 
     29 #pragma hdrstop
     30 #include "../../idlib/precompiled.h"
     31 
     32 
     33 #include "AAS_local.h"
     34 #include "../Game_local.h"		// for print and error
     35 
     36 #define CACHETYPE_AREA				1
     37 #define CACHETYPE_PORTAL			2
     38 
     39 #define MAX_ROUTING_CACHE_MEMORY	(2*1024*1024)
     40 
     41 #define LEDGE_TRAVELTIME_PANALTY	250
     42 
     43 /*
     44 ============
     45 idRoutingCache::idRoutingCache
     46 ============
     47 */
     48 idRoutingCache::idRoutingCache( int size ) {
     49 	areaNum = 0;
     50 	cluster = 0;
     51 	next = prev = NULL;
     52 	time_next = time_prev = NULL;
     53 	travelFlags = 0;
     54 	startTravelTime = 0;
     55 	type = 0;
     56 	this->size = size;
     57 	reachabilities = new (TAG_AAS) byte[size];
     58 	memset( reachabilities, 0, size * sizeof( reachabilities[0] ) );
     59 	travelTimes = new (TAG_AAS) unsigned short[size];
     60 	memset( travelTimes, 0, size * sizeof( travelTimes[0] ) );
     61 }
     62 
     63 /*
     64 ============
     65 idRoutingCache::~idRoutingCache
     66 ============
     67 */
     68 idRoutingCache::~idRoutingCache() {
     69 	delete [] reachabilities;
     70 	delete [] travelTimes;
     71 }
     72 
     73 /*
     74 ============
     75 idRoutingCache::Size
     76 ============
     77 */
     78 int idRoutingCache::Size() const {
     79 	return sizeof( idRoutingCache ) + size * sizeof( reachabilities[0] ) + size * sizeof( travelTimes[0] );
     80 }
     81 
     82 /*
     83 ============
     84 idAASLocal::AreaTravelTime
     85 ============
     86 */
     87 unsigned short idAASLocal::AreaTravelTime( int areaNum, const idVec3 &start, const idVec3 &end ) const {
     88 	float dist;
     89 
     90 	dist = ( end - start ).Length();
     91 
     92 	if ( file->GetArea( areaNum ).travelFlags & TFL_CROUCH ) {
     93 		dist *= 100.0f / 100.0f;
     94 	} else if ( file->GetArea( areaNum ).travelFlags & TFL_WATER ) {
     95 		dist *= 100.0f / 150.0f;
     96 	} else {
     97 		dist *= 100.0f / 300.0f;
     98 	}
     99 	if ( dist < 1.0f ) {
    100 		return 1;
    101 	}
    102 	return (unsigned short) idMath::Ftoi( dist );
    103 }
    104 
    105 /*
    106 ============
    107 idAASLocal::CalculateAreaTravelTimes
    108 ============
    109 */
    110 void idAASLocal::CalculateAreaTravelTimes() {
    111 	int n, i, j, numReach, numRevReach, t, maxt;
    112 	byte *bytePtr;
    113 	idReachability *reach, *rev_reach;
    114 
    115 	// get total memory for all area travel times
    116 	numAreaTravelTimes = 0;
    117 	for ( n = 0; n < file->GetNumAreas(); n++ ) {
    118 
    119 		if ( !(file->GetArea( n ).flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) {
    120 			continue;
    121 		}
    122 
    123 		numReach = 0;
    124 		for ( reach = file->GetArea( n ).reach; reach; reach = reach->next ) {
    125 			numReach++;
    126 		}
    127 
    128 		numRevReach = 0;
    129 		for ( rev_reach = file->GetArea( n ).rev_reach; rev_reach; rev_reach = rev_reach->rev_next ) {
    130 			numRevReach++;
    131 		}
    132 		numAreaTravelTimes += numReach * numRevReach;
    133 	}
    134 
    135 	areaTravelTimes = (unsigned short *) Mem_Alloc( numAreaTravelTimes * sizeof( unsigned short ), TAG_AAS );
    136 	bytePtr = (byte *) areaTravelTimes;
    137 
    138 	for ( n = 0; n < file->GetNumAreas(); n++ ) {
    139 
    140 		if ( !(file->GetArea( n ).flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) {
    141 			continue;
    142 		}
    143 
    144 		// for each reachability that starts in this area calculate the travel time
    145 		// towards all the reachabilities that lead towards this area
    146 		for ( maxt = i = 0, reach = file->GetArea( n ).reach; reach; reach = reach->next, i++ ) {
    147 			assert( i < MAX_REACH_PER_AREA );
    148 			if ( i >= MAX_REACH_PER_AREA ) {
    149 				gameLocal.Error( "i >= MAX_REACH_PER_AREA" );
    150 			}
    151 			reach->number = i;
    152 			reach->disableCount = 0;
    153 			reach->areaTravelTimes = (unsigned short *) bytePtr;
    154 			for ( j = 0, rev_reach = file->GetArea( n ).rev_reach; rev_reach; rev_reach = rev_reach->rev_next, j++ ) {
    155 				t = AreaTravelTime( n, reach->start, rev_reach->end );
    156 				reach->areaTravelTimes[j] = t;
    157 				if ( t > maxt ) {
    158 					maxt = t;
    159 				}
    160 			}
    161 			bytePtr += j * sizeof( unsigned short );
    162 		}
    163 
    164 		// if this area is a portal
    165 		if ( file->GetArea( n ).cluster < 0 ) {
    166 			// set the maximum travel time through this portal
    167 			file->SetPortalMaxTravelTime( -file->GetArea( n ).cluster, maxt );
    168 		}
    169 	}
    170 
    171 	assert( ( (unsigned int) bytePtr - (unsigned int) areaTravelTimes ) <= numAreaTravelTimes * sizeof( unsigned short ) );
    172 }
    173 
    174 /*
    175 ============
    176 idAASLocal::DeleteAreaTravelTimes
    177 ============
    178 */
    179 void idAASLocal::DeleteAreaTravelTimes() {
    180 	Mem_Free( areaTravelTimes );
    181 	areaTravelTimes = NULL;
    182 	numAreaTravelTimes = 0;
    183 }
    184 
    185 /*
    186 ============
    187 idAASLocal::SetupRoutingCache
    188 ============
    189 */
    190 void idAASLocal::SetupRoutingCache() {
    191 	int i;
    192 	byte *bytePtr;
    193 
    194 	areaCacheIndexSize = 0;
    195 	for ( i = 0; i < file->GetNumClusters(); i++ ) {
    196 		areaCacheIndexSize += file->GetCluster( i ).numReachableAreas;
    197 	}
    198 	areaCacheIndex = (idRoutingCache ***) Mem_ClearedAlloc( file->GetNumClusters() * sizeof( idRoutingCache ** ) +
    199 													areaCacheIndexSize * sizeof( idRoutingCache *), TAG_AAS );
    200 	bytePtr = ((byte *)areaCacheIndex) + file->GetNumClusters() * sizeof( idRoutingCache ** );
    201 	for ( i = 0; i < file->GetNumClusters(); i++ ) {
    202 		areaCacheIndex[i] = ( idRoutingCache ** ) bytePtr;
    203 		bytePtr += file->GetCluster( i ).numReachableAreas * sizeof( idRoutingCache * );
    204 	}
    205 
    206 	portalCacheIndexSize = file->GetNumAreas();
    207 	portalCacheIndex = (idRoutingCache **) Mem_ClearedAlloc( portalCacheIndexSize * sizeof( idRoutingCache * ), TAG_AAS );
    208 
    209 	areaUpdate = (idRoutingUpdate *) Mem_ClearedAlloc( file->GetNumAreas() * sizeof( idRoutingUpdate ), TAG_AAS );
    210 	portalUpdate = (idRoutingUpdate *) Mem_ClearedAlloc( (file->GetNumPortals()+1) * sizeof( idRoutingUpdate ), TAG_AAS );
    211 
    212 	goalAreaTravelTimes = (unsigned short *) Mem_ClearedAlloc( file->GetNumAreas() * sizeof( unsigned short ), TAG_AAS );
    213 
    214 	cacheListStart = cacheListEnd = NULL;
    215 	totalCacheMemory = 0;
    216 }
    217 
    218 /*
    219 ============
    220 idAASLocal::DeleteClusterCache
    221 ============
    222 */
    223 void idAASLocal::DeleteClusterCache( int clusterNum ) {
    224 	int i;
    225 	idRoutingCache *cache;
    226 
    227 	for ( i = 0; i < file->GetCluster( clusterNum ).numReachableAreas; i++ ) {
    228 		for ( cache = areaCacheIndex[clusterNum][i]; cache; cache = areaCacheIndex[clusterNum][i] ) {
    229 			areaCacheIndex[clusterNum][i] = cache->next;
    230 			UnlinkCache( cache );
    231 			delete cache;
    232 		}
    233 	}
    234 }
    235 
    236 /*
    237 ============
    238 idAASLocal::DeletePortalCache
    239 ============
    240 */
    241 void idAASLocal::DeletePortalCache() {
    242 	int i;
    243 	idRoutingCache *cache;
    244 
    245 	for ( i = 0; i < file->GetNumAreas(); i++ ) {
    246 		for ( cache = portalCacheIndex[i]; cache; cache = portalCacheIndex[i] ) {
    247 			portalCacheIndex[i] = cache->next;
    248 			UnlinkCache( cache );
    249 			delete cache;
    250 		}
    251 	}
    252 }
    253 
    254 /*
    255 ============
    256 idAASLocal::ShutdownRoutingCache
    257 ============
    258 */
    259 void idAASLocal::ShutdownRoutingCache() {
    260 	int i;
    261 
    262 	for ( i = 0; i < file->GetNumClusters(); i++ ) {
    263 		DeleteClusterCache( i );
    264 	}
    265 
    266 	DeletePortalCache();
    267 
    268 	Mem_Free( areaCacheIndex );
    269 	areaCacheIndex = NULL;
    270 	areaCacheIndexSize = 0;
    271 	Mem_Free( portalCacheIndex );
    272 	portalCacheIndex = NULL;
    273 	portalCacheIndexSize = 0;
    274 	Mem_Free( areaUpdate );
    275 	areaUpdate = NULL;
    276 	Mem_Free( portalUpdate );
    277 	portalUpdate = NULL;
    278 	Mem_Free( goalAreaTravelTimes );
    279 	goalAreaTravelTimes = NULL;
    280 
    281 	cacheListStart = cacheListEnd = NULL;
    282 	totalCacheMemory = 0;
    283 }
    284 
    285 /*
    286 ============
    287 idAASLocal::SetupRouting
    288 ============
    289 */
    290 bool idAASLocal::SetupRouting() {
    291 	CalculateAreaTravelTimes();
    292 	SetupRoutingCache();
    293 	return true;
    294 }
    295 
    296 /*
    297 ============
    298 idAASLocal::ShutdownRouting
    299 ============
    300 */
    301 void idAASLocal::ShutdownRouting() {
    302 	DeleteAreaTravelTimes();
    303 	ShutdownRoutingCache();
    304 }
    305 
    306 /*
    307 ============
    308 idAASLocal::RoutingStats
    309 ============
    310 */
    311 void idAASLocal::RoutingStats() const {
    312 	idRoutingCache *cache;
    313 	int numAreaCache, numPortalCache;
    314 	int totalAreaCacheMemory, totalPortalCacheMemory;
    315 
    316 	numAreaCache = numPortalCache = 0;
    317 	totalAreaCacheMemory = totalPortalCacheMemory = 0;
    318 	for ( cache = cacheListStart; cache; cache = cache->time_next ) {
    319 		if ( cache->type == CACHETYPE_AREA ) {
    320 			numAreaCache++;
    321 			totalAreaCacheMemory += sizeof( idRoutingCache ) + cache->size * (sizeof( unsigned short ) + sizeof( byte ));
    322 		} else {
    323 			numPortalCache++;
    324 			totalPortalCacheMemory += sizeof( idRoutingCache ) + cache->size * (sizeof( unsigned short ) + sizeof( byte ));
    325 		}
    326 	}
    327 
    328 	gameLocal.Printf( "%6d area cache (%d KB)\n", numAreaCache, totalAreaCacheMemory >> 10 );
    329 	gameLocal.Printf( "%6d portal cache (%d KB)\n", numPortalCache, totalPortalCacheMemory >> 10 );
    330 	gameLocal.Printf( "%6d total cache (%d KB)\n", numAreaCache + numPortalCache, totalCacheMemory >> 10 );
    331 	gameLocal.Printf( "%6d area travel times (%d KB)\n", numAreaTravelTimes, ( numAreaTravelTimes * sizeof( unsigned short ) ) >> 10 );
    332 	gameLocal.Printf( "%6d area cache entries (%d KB)\n", areaCacheIndexSize, ( areaCacheIndexSize * sizeof( idRoutingCache * ) ) >> 10 );
    333 	gameLocal.Printf( "%6d portal cache entries (%d KB)\n", portalCacheIndexSize, ( portalCacheIndexSize * sizeof( idRoutingCache * ) ) >> 10 );
    334 }
    335 
    336 /*
    337 ============
    338 idAASLocal::RemoveRoutingCacheUsingArea
    339 ============
    340 */
    341 void idAASLocal::RemoveRoutingCacheUsingArea( int areaNum ) {
    342 	int clusterNum;
    343 
    344 	clusterNum = file->GetArea( areaNum ).cluster;
    345 	if ( clusterNum > 0 ) {
    346 		// remove all the cache in the cluster the area is in
    347 		DeleteClusterCache( clusterNum );
    348 	}
    349 	else {
    350 		// if this is a portal remove all cache in both the front and back cluster
    351 		DeleteClusterCache( file->GetPortal( -clusterNum ).clusters[0] );
    352 		DeleteClusterCache( file->GetPortal( -clusterNum ).clusters[1] );
    353 	}
    354 	DeletePortalCache();
    355 }
    356 
    357 /*
    358 ============
    359 idAASLocal::DisableArea
    360 ============
    361 */
    362 void idAASLocal::DisableArea( int areaNum ) {
    363 	assert( areaNum > 0 && areaNum < file->GetNumAreas() );
    364 
    365 	if ( file->GetArea( areaNum ).travelFlags & TFL_INVALID ) {
    366 		return;
    367 	}
    368 
    369 	file->SetAreaTravelFlag( areaNum, TFL_INVALID );
    370 
    371 	RemoveRoutingCacheUsingArea( areaNum );
    372 }
    373 
    374 /*
    375 ============
    376 idAASLocal::EnableArea
    377 ============
    378 */
    379 void idAASLocal::EnableArea( int areaNum ) {
    380 	assert( areaNum > 0 && areaNum < file->GetNumAreas() );
    381 
    382 	if ( !( file->GetArea( areaNum ).travelFlags & TFL_INVALID ) ) {
    383 		return;
    384 	}
    385 
    386 	file->RemoveAreaTravelFlag( areaNum, TFL_INVALID );
    387 
    388 	RemoveRoutingCacheUsingArea( areaNum );
    389 }
    390 
    391 /*
    392 ============
    393 idAASLocal::SetAreaState_r
    394 ============
    395 */
    396 bool idAASLocal::SetAreaState_r( int nodeNum, const idBounds &bounds, const int areaContents, bool disabled ) {
    397 	int res;
    398 	const aasNode_t *node;
    399 	bool foundClusterPortal = false;
    400 
    401 	while( nodeNum != 0 ) {
    402 		if ( nodeNum < 0 ) {
    403 			// if this area is a cluster portal
    404 			if ( file->GetArea( -nodeNum ).contents & areaContents ) {
    405 				if ( disabled ) {
    406 					DisableArea( -nodeNum );
    407 				} else {
    408 					EnableArea( -nodeNum );
    409 				}
    410 				foundClusterPortal |= true;
    411 			}
    412 			break;
    413 		}
    414 		node = &file->GetNode( nodeNum );
    415 		res = bounds.PlaneSide( file->GetPlane( node->planeNum ) );
    416 		if ( res == PLANESIDE_BACK ) {
    417 			nodeNum = node->children[1];
    418 		}
    419 		else if ( res == PLANESIDE_FRONT ) {
    420 			nodeNum = node->children[0];
    421 		}
    422 		else {
    423 			foundClusterPortal |= SetAreaState_r( node->children[1], bounds, areaContents, disabled );
    424 			nodeNum = node->children[0];
    425 		}
    426 	}
    427 
    428 	return foundClusterPortal;
    429 }
    430 
    431 /*
    432 ============
    433 idAASLocal::SetAreaState
    434 ============
    435 */
    436 bool idAASLocal::SetAreaState( const idBounds &bounds, const int areaContents, bool disabled ) {
    437 	idBounds expBounds;
    438 
    439 	if ( !file ) {
    440 		return false;
    441 	}
    442 
    443 	expBounds[0] = bounds[0] - file->GetSettings().boundingBoxes[0][1];
    444 	expBounds[1] = bounds[1] - file->GetSettings().boundingBoxes[0][0];
    445 
    446 	// find all areas within or touching the bounds with the given contents and disable/enable them for routing
    447 	return SetAreaState_r( 1, expBounds, areaContents, disabled );
    448 }
    449 
    450 /*
    451 ============
    452 idAASLocal::GetBoundsAreas_r
    453 ============
    454 */
    455 void idAASLocal::GetBoundsAreas_r( int nodeNum, const idBounds &bounds, idList<int> &areas ) const {
    456 	int res;
    457 	const aasNode_t *node;
    458 
    459 	while( nodeNum != 0 ) {
    460 		if ( nodeNum < 0 ) {
    461 			areas.Append( -nodeNum );
    462 			break;
    463 		}
    464 		node = &file->GetNode( nodeNum );
    465 		res = bounds.PlaneSide( file->GetPlane( node->planeNum ) );
    466 		if ( res == PLANESIDE_BACK ) {
    467 			nodeNum = node->children[1];
    468 		}
    469 		else if ( res == PLANESIDE_FRONT ) {
    470 			nodeNum = node->children[0];
    471 		}
    472 		else {
    473 			GetBoundsAreas_r( node->children[1], bounds, areas );
    474 			nodeNum = node->children[0];
    475 		}
    476 	}
    477 }
    478 
    479 /*
    480 ============
    481 idAASLocal::SetObstacleState
    482 ============
    483 */
    484 void idAASLocal::SetObstacleState( const idRoutingObstacle *obstacle, bool enable ) {
    485 	int i;
    486 	const aasArea_t *area;
    487 	idReachability *reach, *rev_reach;
    488 	bool inside;
    489 
    490 	for ( i = 0; i < obstacle->areas.Num(); i++ ) {
    491 
    492 		RemoveRoutingCacheUsingArea( obstacle->areas[i] );
    493 
    494 		area = &file->GetArea( obstacle->areas[i] );
    495 
    496 		for ( rev_reach = area->rev_reach; rev_reach; rev_reach = rev_reach->rev_next ) {
    497 
    498 			if ( rev_reach->travelType & TFL_INVALID ) {
    499 				continue;
    500 			}
    501 
    502 			inside = false;
    503 
    504 			if ( obstacle->bounds.ContainsPoint( rev_reach->end ) ) {
    505 				inside = true;
    506 			}
    507 			else {
    508 				for ( reach = area->reach; reach; reach = reach->next ) {
    509 					if ( obstacle->bounds.LineIntersection( rev_reach->end, reach->start ) ) {
    510 						inside = true;
    511 						break;
    512 					}
    513 				}
    514 			}
    515 
    516 			if ( inside ) {
    517 				if ( enable ) {
    518 					rev_reach->disableCount--;
    519 					if ( rev_reach->disableCount <= 0 ) {
    520 						rev_reach->travelType &= ~TFL_INVALID;
    521 						rev_reach->disableCount = 0;
    522 					}
    523 				}
    524 				else {
    525 					rev_reach->travelType |= TFL_INVALID;
    526 					rev_reach->disableCount++;
    527 				}
    528 			}
    529 		}
    530 	}
    531 }
    532 
    533 /*
    534 ============
    535 idAASLocal::AddObstacle
    536 ============
    537 */
    538 aasHandle_t idAASLocal::AddObstacle( const idBounds &bounds ) {
    539 	idRoutingObstacle *obstacle;
    540 
    541 	if ( !file ) {
    542 		return -1;
    543 	}
    544 
    545 	obstacle = new (TAG_AAS) idRoutingObstacle;
    546 	obstacle->bounds[0] = bounds[0] - file->GetSettings().boundingBoxes[0][1];
    547 	obstacle->bounds[1] = bounds[1] - file->GetSettings().boundingBoxes[0][0];
    548 	GetBoundsAreas_r( 1, obstacle->bounds, obstacle->areas );
    549 	SetObstacleState( obstacle, true );
    550 
    551 	obstacleList.Append( obstacle );
    552 	return obstacleList.Num() - 1;
    553 }
    554 
    555 /*
    556 ============
    557 idAASLocal::RemoveObstacle
    558 ============
    559 */
    560 void idAASLocal::RemoveObstacle( const aasHandle_t handle ) {
    561 	if ( !file ) {
    562 		return;
    563 	}
    564 	if ( ( handle >= 0 ) && ( handle < obstacleList.Num() ) ) {
    565 		SetObstacleState( obstacleList[handle], false );
    566 
    567 		delete obstacleList[handle];
    568 		obstacleList.RemoveIndex( handle );
    569 	}
    570 }
    571 
    572 /*
    573 ============
    574 idAASLocal::RemoveAllObstacles
    575 ============
    576 */
    577 void idAASLocal::RemoveAllObstacles() {
    578 	int i;
    579 
    580 	if ( !file ) {
    581 		return;
    582 	}
    583 
    584 	for ( i = 0; i < obstacleList.Num(); i++ ) {
    585 		SetObstacleState( obstacleList[i], false );
    586 		delete obstacleList[i];
    587 	}
    588 	obstacleList.Clear();
    589 }
    590 
    591 /*
    592 ============
    593 idAASLocal::LinkCache
    594 
    595   link the cache in the cache list sorted from oldest to newest cache
    596 ============
    597 */
    598 void idAASLocal::LinkCache( idRoutingCache *cache ) const {
    599 
    600 	// if the cache is already linked
    601 	if ( cache->time_next || cache->time_prev || cacheListStart == cache ) {
    602 		UnlinkCache( cache );
    603 	}
    604 
    605 	totalCacheMemory += cache->Size();
    606 
    607 	// add cache to the end of the list
    608 	cache->time_next = NULL;
    609 	cache->time_prev = cacheListEnd;
    610 	if ( cacheListEnd ) {
    611 		cacheListEnd->time_next = cache;
    612 	}
    613 	cacheListEnd = cache;
    614 	if ( !cacheListStart ) {
    615 		cacheListStart = cache;
    616 	}
    617 }
    618 
    619 /*
    620 ============
    621 idAASLocal::UnlinkCache
    622 ============
    623 */
    624 void idAASLocal::UnlinkCache( idRoutingCache *cache ) const {
    625 
    626 	totalCacheMemory -= cache->Size();
    627 
    628 	// unlink the cache
    629 	if ( cache->time_next ) {
    630 		cache->time_next->time_prev = cache->time_prev;
    631 	} else {
    632 		cacheListEnd = cache->time_prev;
    633 	}
    634 	if ( cache->time_prev ) {
    635 		cache->time_prev->time_next = cache->time_next;
    636 	} else {
    637 		cacheListStart = cache->time_next;
    638 	}
    639 	cache->time_next = cache->time_prev = NULL;
    640 }
    641 
    642 /*
    643 ============
    644 idAASLocal::DeleteOldestCache
    645 ============
    646 */
    647 void idAASLocal::DeleteOldestCache() const {
    648 	idRoutingCache *cache;
    649 
    650 	assert( cacheListStart );
    651 
    652 	// unlink the oldest cache
    653 	cache = cacheListStart;
    654 	UnlinkCache( cache );
    655 
    656 	// unlink the oldest cache from the area or portal cache index
    657 	if ( cache->next ) {
    658 		cache->next->prev = cache->prev;
    659 	}
    660 	if ( cache->prev ) {
    661 		cache->prev->next = cache->next;
    662 	}
    663 	else if ( cache->type == CACHETYPE_AREA ) {
    664 		areaCacheIndex[cache->cluster][ClusterAreaNum( cache->cluster, cache->areaNum )] = cache->next;
    665 	}
    666 	else if ( cache->type == CACHETYPE_PORTAL ) {
    667 		portalCacheIndex[cache->areaNum] = cache->next;
    668 	}
    669 
    670 	delete cache;
    671 }
    672 
    673 /*
    674 ============
    675 idAASLocal::GetAreaReachability
    676 ============
    677 */
    678 idReachability *idAASLocal::GetAreaReachability( int areaNum, int reachabilityNum ) const {
    679 	idReachability *reach;
    680 
    681 	for ( reach = file->GetArea( areaNum ).reach; reach; reach = reach->next ) {
    682 		if ( --reachabilityNum < 0 ) {
    683 			return reach;
    684 		}
    685 	}
    686 	return NULL;
    687 }
    688 
    689 /*
    690 ============
    691 idAASLocal::ClusterAreaNum
    692 ============
    693 */
    694 ID_INLINE int idAASLocal::ClusterAreaNum( int clusterNum, int areaNum ) const {
    695 	int side, areaCluster;
    696 
    697 	areaCluster = file->GetArea( areaNum ).cluster;
    698 	if ( areaCluster > 0 ) {
    699 		return file->GetArea( areaNum ).clusterAreaNum;
    700 	}
    701 	else {
    702 		side = file->GetPortal( -areaCluster ).clusters[0] != clusterNum;
    703 		return file->GetPortal( -areaCluster ).clusterAreaNum[side];
    704 	}
    705 }
    706 
    707 /*
    708 ============
    709 idAASLocal::UpdateAreaRoutingCache
    710 ============
    711 */
    712 void idAASLocal::UpdateAreaRoutingCache( idRoutingCache *areaCache ) const {
    713 	int i, nextAreaNum, cluster, badTravelFlags, clusterAreaNum, numReachableAreas;
    714 	unsigned short t, startAreaTravelTimes[MAX_REACH_PER_AREA];
    715 	idRoutingUpdate *updateListStart, *updateListEnd, *curUpdate, *nextUpdate;
    716 	idReachability *reach;
    717 	const aasArea_t *nextArea;
    718 
    719 	// number of reachability areas within this cluster
    720 	numReachableAreas = file->GetCluster( areaCache->cluster ).numReachableAreas;
    721 
    722 	// number of the start area within the cluster
    723 	clusterAreaNum = ClusterAreaNum( areaCache->cluster, areaCache->areaNum );
    724 	if ( clusterAreaNum >= numReachableAreas ) {
    725 		return;
    726 	}
    727 
    728 	areaCache->travelTimes[clusterAreaNum] = areaCache->startTravelTime;
    729 	badTravelFlags = ~areaCache->travelFlags;
    730 	memset( startAreaTravelTimes, 0, sizeof( startAreaTravelTimes ) );
    731 
    732 	// initialize first update
    733 	curUpdate = &areaUpdate[clusterAreaNum];
    734 	curUpdate->areaNum = areaCache->areaNum;
    735 	curUpdate->areaTravelTimes = startAreaTravelTimes;
    736 	curUpdate->tmpTravelTime = areaCache->startTravelTime;
    737 	curUpdate->next = NULL;
    738 	curUpdate->prev = NULL;
    739 	updateListStart = curUpdate;
    740 	updateListEnd = curUpdate;
    741 
    742 	// while there are updates in the list
    743 	while( updateListStart ) {
    744 
    745 		curUpdate = updateListStart;
    746 		if ( curUpdate->next ) {
    747 			curUpdate->next->prev = NULL;
    748 		}
    749 		else {
    750 			updateListEnd = NULL;
    751 		}
    752 		updateListStart = curUpdate->next;
    753 
    754 		curUpdate->isInList = false;
    755 
    756 		for ( i = 0, reach = file->GetArea( curUpdate->areaNum ).rev_reach; reach; reach = reach->rev_next, i++ ) {
    757 
    758 			// if the reachability uses an undesired travel type
    759 			if ( reach->travelType & badTravelFlags ) {
    760 				continue;
    761 			}
    762 
    763 			// next area the reversed reachability leads to
    764 			nextAreaNum = reach->fromAreaNum;
    765 			nextArea = &file->GetArea( nextAreaNum );
    766 
    767 			// if traveling through the next area requires an undesired travel flag
    768 			if ( nextArea->travelFlags & badTravelFlags ) {
    769 				continue;
    770 			}
    771 
    772 			// get the cluster number of the area
    773 			cluster = nextArea->cluster;
    774 			// don't leave the cluster, however do flood into cluster portals
    775 			if ( cluster > 0 && cluster != areaCache->cluster ) {
    776 				continue;
    777 			}
    778 
    779 			// get the number of the area in the cluster
    780 			clusterAreaNum = ClusterAreaNum( areaCache->cluster, nextAreaNum );
    781 			if ( clusterAreaNum >= numReachableAreas ) {
    782 				continue;	// should never happen
    783 			}
    784 
    785 			assert( clusterAreaNum < areaCache->size );
    786 
    787 			// time already travelled plus the traveltime through the current area
    788 			// plus the travel time of the reachability towards the next area
    789 			t = curUpdate->tmpTravelTime + curUpdate->areaTravelTimes[i] + reach->travelTime;
    790 
    791 			if ( !areaCache->travelTimes[clusterAreaNum] || t < areaCache->travelTimes[clusterAreaNum] ) {
    792 
    793 				areaCache->travelTimes[clusterAreaNum] = t;
    794 				areaCache->reachabilities[clusterAreaNum] = reach->number; // reversed reachability used to get into this area
    795 				nextUpdate = &areaUpdate[clusterAreaNum];
    796 				nextUpdate->areaNum = nextAreaNum;
    797 				nextUpdate->tmpTravelTime = t;
    798 				nextUpdate->areaTravelTimes = reach->areaTravelTimes;
    799 
    800 				// if we are not allowed to fly
    801 				if ( badTravelFlags & TFL_FLY ) {
    802 					// avoid areas near ledges
    803 					if ( file->GetArea( nextAreaNum ).flags & AREA_LEDGE ) {
    804 						nextUpdate->tmpTravelTime += LEDGE_TRAVELTIME_PANALTY;
    805 					}
    806 				}
    807 
    808 				if ( !nextUpdate->isInList ) {
    809 					nextUpdate->next = NULL;
    810 					nextUpdate->prev = updateListEnd;
    811 					if ( updateListEnd ) {
    812 						updateListEnd->next = nextUpdate;
    813 					}
    814 					else {
    815 						updateListStart = nextUpdate;
    816 					}
    817 					updateListEnd = nextUpdate;
    818 					nextUpdate->isInList = true;
    819 				}
    820 			}
    821 		}
    822 	}
    823 }
    824 
    825 /*
    826 ============
    827 idAASLocal::GetAreaRoutingCache
    828 ============
    829 */
    830 idRoutingCache *idAASLocal::GetAreaRoutingCache( int clusterNum, int areaNum, int travelFlags ) const {
    831 	int clusterAreaNum;
    832 	idRoutingCache *cache, *clusterCache;
    833 
    834 	// number of the area in the cluster
    835 	clusterAreaNum = ClusterAreaNum( clusterNum, areaNum );
    836 	// pointer to the cache for the area in the cluster
    837 	clusterCache = areaCacheIndex[clusterNum][clusterAreaNum];
    838 	// check if cache without undesired travel flags already exists
    839 	for ( cache = clusterCache; cache; cache = cache->next ) {
    840 		if ( cache->travelFlags == travelFlags ) {
    841 			break;
    842 		}
    843 	}
    844 	// if no cache found
    845 	if ( !cache ) {
    846 		cache = new (TAG_AAS) idRoutingCache( file->GetCluster( clusterNum ).numReachableAreas );
    847 		cache->type = CACHETYPE_AREA;
    848 		cache->cluster = clusterNum;
    849 		cache->areaNum = areaNum;
    850 		cache->startTravelTime = 1;
    851 		cache->travelFlags = travelFlags;
    852 		cache->prev = NULL;
    853 		cache->next = clusterCache;
    854 		if ( clusterCache ) {
    855 			clusterCache->prev = cache;
    856 		}
    857 		areaCacheIndex[clusterNum][clusterAreaNum] = cache;
    858 		UpdateAreaRoutingCache( cache );
    859 	}
    860 	LinkCache( cache );
    861 	return cache;
    862 }
    863 
    864 /*
    865 ============
    866 idAASLocal::UpdatePortalRoutingCache
    867 ============
    868 */
    869 void idAASLocal::UpdatePortalRoutingCache( idRoutingCache *portalCache ) const {
    870 	int i, portalNum, clusterAreaNum;
    871 	unsigned short t;
    872 	const aasPortal_t *portal;
    873 	const aasCluster_t *cluster;
    874 	idRoutingCache *cache;
    875 	idRoutingUpdate *updateListStart, *updateListEnd, *curUpdate, *nextUpdate;
    876 
    877 	curUpdate = &portalUpdate[ file->GetNumPortals() ];
    878 	curUpdate->cluster = portalCache->cluster;
    879 	curUpdate->areaNum = portalCache->areaNum;
    880 	curUpdate->tmpTravelTime = portalCache->startTravelTime;
    881 
    882 	//put the area to start with in the current read list
    883 	curUpdate->next = NULL;
    884 	curUpdate->prev = NULL;
    885 	updateListStart = curUpdate;
    886 	updateListEnd = curUpdate;
    887 
    888 	// while there are updates in the current list
    889 	while( updateListStart ) {
    890 
    891 		curUpdate = updateListStart;
    892 		// remove the current update from the list
    893 		if ( curUpdate->next ) {
    894 			curUpdate->next->prev = NULL;
    895 		}
    896 		else {
    897 			updateListEnd = NULL;
    898 		}
    899 		updateListStart = curUpdate->next;
    900 		// current update is removed from the list
    901 		curUpdate->isInList = false;
    902 
    903 		cluster = &file->GetCluster( curUpdate->cluster );
    904 		cache = GetAreaRoutingCache( curUpdate->cluster, curUpdate->areaNum, portalCache->travelFlags );
    905 
    906 		// take all portals of the cluster
    907 		for ( i = 0; i < cluster->numPortals; i++ ) {
    908 			portalNum = file->GetPortalIndex( cluster->firstPortal + i );
    909 			assert( portalNum < portalCache->size );
    910 			portal = &file->GetPortal( portalNum );
    911 
    912 			clusterAreaNum = ClusterAreaNum( curUpdate->cluster, portal->areaNum );
    913 			if ( clusterAreaNum >= cluster->numReachableAreas ) {
    914 				continue;
    915 			}
    916 
    917 			t = cache->travelTimes[clusterAreaNum];
    918 			if ( t == 0 ) {
    919 				continue;
    920 			}
    921 			t += curUpdate->tmpTravelTime;
    922 
    923 			if ( !portalCache->travelTimes[portalNum] || t < portalCache->travelTimes[portalNum] ) {
    924 
    925 				portalCache->travelTimes[portalNum] = t;
    926 				portalCache->reachabilities[portalNum] = cache->reachabilities[clusterAreaNum];
    927 				nextUpdate = &portalUpdate[portalNum];
    928 				if ( portal->clusters[0] == curUpdate->cluster ) {
    929 					nextUpdate->cluster = portal->clusters[1];
    930 				}
    931 				else {
    932 					nextUpdate->cluster = portal->clusters[0];
    933 				}
    934 				nextUpdate->areaNum = portal->areaNum;
    935 				// add travel time through the actual portal area for the next update
    936 				nextUpdate->tmpTravelTime = t + portal->maxAreaTravelTime;
    937 
    938 				if ( !nextUpdate->isInList ) {
    939 
    940 					nextUpdate->next = NULL;
    941 					nextUpdate->prev = updateListEnd;
    942 					if ( updateListEnd ) {
    943 						updateListEnd->next = nextUpdate;
    944 					}
    945 					else {
    946 						updateListStart = nextUpdate;
    947 					}
    948 					updateListEnd = nextUpdate;
    949 					nextUpdate->isInList = true;
    950 				}
    951 			}
    952 		}
    953 	}
    954 }
    955 
    956 /*
    957 ============
    958 idAASLocal::GetPortalRoutingCache
    959 ============
    960 */
    961 idRoutingCache *idAASLocal::GetPortalRoutingCache( int clusterNum, int areaNum, int travelFlags ) const {
    962 	idRoutingCache *cache;
    963 
    964 	// check if cache without undesired travel flags already exists
    965 	for ( cache = portalCacheIndex[areaNum]; cache; cache = cache->next ) {
    966 		if ( cache->travelFlags == travelFlags ) {
    967 			break;
    968 		}
    969 	}
    970 	// if no cache found
    971 	if ( !cache ) {
    972 		cache = new (TAG_AAS) idRoutingCache( file->GetNumPortals() );
    973 		cache->type = CACHETYPE_PORTAL;
    974 		cache->cluster = clusterNum;
    975 		cache->areaNum = areaNum;
    976 		cache->startTravelTime = 1;
    977 		cache->travelFlags = travelFlags;
    978 		cache->prev = NULL;
    979 		cache->next = portalCacheIndex[areaNum];
    980 		if ( portalCacheIndex[areaNum] ) {
    981 			portalCacheIndex[areaNum]->prev = cache;
    982 		}
    983 		portalCacheIndex[areaNum] = cache;
    984 		UpdatePortalRoutingCache( cache );
    985 	}
    986 	LinkCache( cache );
    987 	return cache;
    988 }
    989 
    990 /*
    991 ============
    992 idAASLocal::RouteToGoalArea
    993 ============
    994 */
    995 bool idAASLocal::RouteToGoalArea( int areaNum, const idVec3 origin, int goalAreaNum, int travelFlags, int &travelTime, idReachability **reach ) const {
    996 	int clusterNum, goalClusterNum, portalNum, i, clusterAreaNum;
    997 	unsigned short int t, bestTime;
    998 	const aasPortal_t *portal;
    999 	const aasCluster_t *cluster;
   1000 	idRoutingCache *areaCache, *portalCache, *clusterCache;
   1001 	idReachability *bestReach, *r, *nextr;
   1002 
   1003 	travelTime = 0;
   1004 	*reach = NULL;
   1005 
   1006 	if ( !file ) {
   1007 		return false;
   1008 	}
   1009 
   1010 	if ( areaNum == goalAreaNum ) {
   1011 		return true;
   1012 	}
   1013 
   1014 	if ( areaNum <= 0 || areaNum >= file->GetNumAreas() ) {
   1015 		gameLocal.Printf( "RouteToGoalArea: areaNum %d out of range\n", areaNum );
   1016 		return false;
   1017 	}
   1018 	if ( goalAreaNum <= 0 || goalAreaNum >= file->GetNumAreas() ) {
   1019 		gameLocal.Printf( "RouteToGoalArea: goalAreaNum %d out of range\n", goalAreaNum );
   1020 		return false;
   1021 	}
   1022 
   1023 	while( totalCacheMemory > MAX_ROUTING_CACHE_MEMORY ) {
   1024 		DeleteOldestCache();
   1025 	}
   1026 
   1027 	clusterNum = file->GetArea( areaNum ).cluster;
   1028 	goalClusterNum = file->GetArea( goalAreaNum ).cluster;
   1029 
   1030 	// if the source area is a cluster portal, read directly from the portal cache
   1031 	if ( clusterNum < 0 ) {
   1032 		// if the goal area is a portal
   1033 		if ( goalClusterNum < 0 ) {
   1034 			// just assume the goal area is part of the front cluster
   1035 			portal = &file->GetPortal( -goalClusterNum );
   1036 			goalClusterNum = portal->clusters[0];
   1037 		}
   1038 		// get the portal routing cache
   1039 		portalCache = GetPortalRoutingCache( goalClusterNum, goalAreaNum, travelFlags );
   1040 		*reach = GetAreaReachability( areaNum, portalCache->reachabilities[-clusterNum] );
   1041 		travelTime = portalCache->travelTimes[-clusterNum] + AreaTravelTime( areaNum, origin, (*reach)->start );
   1042 		return true;
   1043 	}
   1044 
   1045 	bestTime = 0;
   1046 	bestReach = NULL;
   1047 
   1048 	// check if the goal area is a portal of the source area cluster
   1049 	if ( goalClusterNum < 0 ) {
   1050 		portal = &file->GetPortal( -goalClusterNum );
   1051 		if ( portal->clusters[0] == clusterNum || portal->clusters[1] == clusterNum) {
   1052 			goalClusterNum = clusterNum;
   1053 		}
   1054 	}
   1055 
   1056 	// if both areas are in the same cluster
   1057 	if ( clusterNum > 0 && goalClusterNum > 0 && clusterNum == goalClusterNum ) {
   1058 		clusterCache = GetAreaRoutingCache( clusterNum, goalAreaNum, travelFlags );
   1059 		clusterAreaNum = ClusterAreaNum( clusterNum, areaNum );
   1060 		if ( clusterCache->travelTimes[clusterAreaNum] ) {
   1061 			bestReach = GetAreaReachability( areaNum, clusterCache->reachabilities[clusterAreaNum] );
   1062 			bestTime = clusterCache->travelTimes[clusterAreaNum] + AreaTravelTime( areaNum, origin, bestReach->start );
   1063 		}
   1064 		else {
   1065 			clusterCache = NULL;
   1066 		}
   1067 	}
   1068 	else {
   1069 		clusterCache = NULL;
   1070 	}
   1071 
   1072 	clusterNum = file->GetArea( areaNum ).cluster;
   1073 	goalClusterNum = file->GetArea( goalAreaNum ).cluster;
   1074 
   1075 	// if the goal area is a portal
   1076 	if ( goalClusterNum < 0 ) {
   1077 		// just assume the goal area is part of the front cluster
   1078 		portal = &file->GetPortal( -goalClusterNum );
   1079 		goalClusterNum = portal->clusters[0];
   1080 	}
   1081 	// get the portal routing cache
   1082 	portalCache = GetPortalRoutingCache( goalClusterNum, goalAreaNum, travelFlags );
   1083 
   1084 	// the cluster the area is in
   1085 	cluster = &file->GetCluster( clusterNum );
   1086 	// current area inside the current cluster
   1087 	clusterAreaNum = ClusterAreaNum( clusterNum, areaNum );
   1088 	// if the area is not a reachable area
   1089 	if ( clusterAreaNum >= cluster->numReachableAreas) {
   1090 		return false;
   1091 	}
   1092 
   1093 	// find the portal of the source area cluster leading towards the goal area
   1094 	for ( i = 0; i < cluster->numPortals; i++ ) {
   1095 		portalNum = file->GetPortalIndex( cluster->firstPortal + i );
   1096 
   1097 		// if the goal area isn't reachable from the portal
   1098 		if ( !portalCache->travelTimes[portalNum] ) {
   1099 			continue;
   1100 		}
   1101 
   1102 		portal = &file->GetPortal( portalNum );
   1103 		// get the cache of the portal area
   1104 		areaCache = GetAreaRoutingCache( clusterNum, portal->areaNum, travelFlags );
   1105 		// if the portal is not reachable from this area
   1106 		if ( !areaCache->travelTimes[clusterAreaNum] ) {
   1107 			continue;
   1108 		}
   1109 
   1110 		r = GetAreaReachability( areaNum, areaCache->reachabilities[clusterAreaNum] );
   1111 
   1112 		if ( clusterCache ) {
   1113 			// if the next reachability from the portal leads back into the cluster
   1114 			nextr = GetAreaReachability( portal->areaNum, portalCache->reachabilities[portalNum] );
   1115 			if ( file->GetArea( nextr->toAreaNum ).cluster < 0 || file->GetArea( nextr->toAreaNum ).cluster == clusterNum ) {
   1116 				continue;
   1117 			}
   1118 		}
   1119 
   1120 		// the total travel time is the travel time from the portal area to the goal area
   1121 		// plus the travel time from the source area towards the portal area
   1122 		t = portalCache->travelTimes[portalNum] + areaCache->travelTimes[clusterAreaNum];
   1123 		// NOTE:	Should add the exact travel time through the portal area.
   1124 		//			However we add the largest travel time through the portal area.
   1125 		//			We cannot directly calculate the exact travel time through the portal area
   1126 		//			because the reachability used to travel into the portal area is not known.
   1127 		t += portal->maxAreaTravelTime;
   1128 
   1129 		// if the time is better than the one already found
   1130 		if ( !bestTime || t < bestTime ) {
   1131 			bestReach = r;
   1132 			bestTime = t;
   1133 		}
   1134 	}
   1135 
   1136 	if ( !bestReach ) {
   1137 		return false;
   1138 	}
   1139 
   1140 	*reach = bestReach;
   1141 	travelTime = bestTime;
   1142 
   1143 	return true;
   1144 }
   1145 
   1146 /*
   1147 ============
   1148 idAASLocal::TravelTimeToGoalArea
   1149 ============
   1150 */
   1151 int idAASLocal::TravelTimeToGoalArea( int areaNum, const idVec3 &origin, int goalAreaNum, int travelFlags ) const {
   1152 	int travelTime;
   1153 	idReachability *reach;
   1154 
   1155 	if ( !file ) {
   1156 		return 0;
   1157 	}
   1158 
   1159 	if ( !RouteToGoalArea( areaNum, origin, goalAreaNum, travelFlags, travelTime, &reach ) ) {
   1160 		return 0;
   1161 	}
   1162 	return travelTime;
   1163 }
   1164 
   1165 /*
   1166 ============
   1167 idAASLocal::FindNearestGoal
   1168 ============
   1169 */
   1170 bool idAASLocal::FindNearestGoal( aasGoal_t &goal, int areaNum, const idVec3 origin, const idVec3 &target, int travelFlags, aasObstacle_t *obstacles, int numObstacles, idAASCallback &callback ) const {
   1171 	int i, j, k, badTravelFlags, nextAreaNum, bestAreaNum;
   1172 	unsigned short t, bestTravelTime;
   1173 	idRoutingUpdate *updateListStart, *updateListEnd, *curUpdate, *nextUpdate;
   1174 	idReachability *reach;
   1175 	const aasArea_t *nextArea;
   1176 	idVec3 v1, v2, p;
   1177 	float targetDist, dist;
   1178 
   1179 	if ( file == NULL || areaNum <= 0 ) {
   1180 		goal.areaNum = areaNum;
   1181 		goal.origin = origin;
   1182 		return false;
   1183 	}
   1184 
   1185 	// if the first area is valid goal, just return the origin
   1186 	if ( callback.TestArea( this, areaNum ) ) {
   1187 		goal.areaNum = areaNum;
   1188 		goal.origin = origin;
   1189 		return true;
   1190 	}
   1191 
   1192 	// setup obstacles
   1193 	for ( k = 0; k < numObstacles; k++ ) {
   1194 		obstacles[k].expAbsBounds[0] = obstacles[k].absBounds[0] - file->GetSettings().boundingBoxes[0][1];
   1195 		obstacles[k].expAbsBounds[1] = obstacles[k].absBounds[1] - file->GetSettings().boundingBoxes[0][0];
   1196 	}
   1197 	
   1198 	badTravelFlags = ~travelFlags;
   1199 	SIMDProcessor->Memset( goalAreaTravelTimes, 0, file->GetNumAreas() * sizeof( unsigned short ) );
   1200 
   1201 	targetDist = (target - origin).Length();
   1202 
   1203 	// initialize first update
   1204 	curUpdate = &areaUpdate[areaNum];
   1205 	curUpdate->areaNum = areaNum;
   1206 	curUpdate->tmpTravelTime = 0;
   1207 	curUpdate->start = origin;
   1208 	curUpdate->next = NULL;
   1209 	curUpdate->prev = NULL;
   1210 	updateListStart = curUpdate;
   1211 	updateListEnd = curUpdate;
   1212 
   1213 	bestTravelTime = 0;
   1214 	bestAreaNum = 0;
   1215 
   1216 	// while there are updates in the list
   1217 	while ( updateListStart ) {
   1218 
   1219 		curUpdate = updateListStart;
   1220 		if ( curUpdate->next ) {
   1221 			curUpdate->next->prev = NULL;
   1222 		}
   1223 		else {
   1224 			updateListEnd = NULL;
   1225 		}
   1226 		updateListStart = curUpdate->next;
   1227 
   1228 		curUpdate->isInList = false;
   1229 
   1230 		// if we already found a closer location
   1231 		if ( bestTravelTime && curUpdate->tmpTravelTime >= bestTravelTime ) {
   1232 			continue;
   1233 		}
   1234 
   1235 		for ( i = 0, reach = file->GetArea( curUpdate->areaNum ).reach; reach; reach = reach->next, i++ ) {
   1236 
   1237 			// if the reachability uses an undesired travel type
   1238 			if ( reach->travelType & badTravelFlags ) {
   1239 				continue;
   1240 			}
   1241 
   1242 			// next area the reversed reachability leads to
   1243 			nextAreaNum = reach->toAreaNum;
   1244 			nextArea = &file->GetArea( nextAreaNum );
   1245 
   1246 			// if traveling through the next area requires an undesired travel flag
   1247 			if ( nextArea->travelFlags & badTravelFlags ) {
   1248 				continue;
   1249 			}
   1250 
   1251 			t = curUpdate->tmpTravelTime +
   1252 					AreaTravelTime( curUpdate->areaNum, curUpdate->start, reach->start ) +
   1253 						reach->travelTime;
   1254 
   1255 			// project target origin onto movement vector through the area
   1256 			v1 = reach->end - curUpdate->start;
   1257 			v1.Normalize();
   1258 			v2 = target - curUpdate->start;
   1259 			p = curUpdate->start + (v2 * v1) * v1;
   1260 
   1261 			// get the point on the path closest to the target
   1262 			for ( j = 0; j < 3; j++ ) {
   1263 				if ( (p[j] > curUpdate->start[j] + 0.1f && p[j] > reach->end[j] + 0.1f) ||
   1264 					(p[j] < curUpdate->start[j] - 0.1f && p[j] < reach->end[j] - 0.1f) ) {
   1265 					break;
   1266 				}
   1267 			}
   1268 			if ( j >= 3 ) {
   1269 				dist = (target - p).Length();
   1270 			} else {
   1271 				dist = (target - reach->end).Length();
   1272 			}
   1273 
   1274 			// avoid moving closer to the target
   1275 			if ( dist < targetDist ) {
   1276 				t += ( targetDist - dist ) * 10;
   1277 			}
   1278 
   1279 			// if we already found a closer location
   1280 			if ( bestTravelTime && t >= bestTravelTime ) {
   1281 				continue;
   1282 			}
   1283 
   1284 			// if this is not the best path towards the next area
   1285 			if ( goalAreaTravelTimes[nextAreaNum] && t >= goalAreaTravelTimes[nextAreaNum] ) {
   1286 				continue;
   1287 			}
   1288 
   1289 			// path may not go through any obstacles
   1290 			for ( k = 0; k < numObstacles; k++ ) {
   1291 				// if the movement vector intersects the expanded obstacle bounds
   1292 				if ( obstacles[k].expAbsBounds.LineIntersection( curUpdate->start, reach->end ) ) {
   1293 					break;
   1294 				}
   1295 			}
   1296 			if ( k < numObstacles ) {
   1297 				continue;
   1298 			}
   1299 
   1300 			goalAreaTravelTimes[nextAreaNum] = t;
   1301 			nextUpdate = &areaUpdate[nextAreaNum];
   1302 			nextUpdate->areaNum = nextAreaNum;
   1303 			nextUpdate->tmpTravelTime = t;
   1304 			nextUpdate->start = reach->end;
   1305 
   1306 			// if we are not allowed to fly
   1307 			if ( badTravelFlags & TFL_FLY ) {
   1308 				// avoid areas near ledges
   1309 				if ( file->GetArea( nextAreaNum ).flags & AREA_LEDGE ) {
   1310 					nextUpdate->tmpTravelTime += LEDGE_TRAVELTIME_PANALTY;
   1311 				}
   1312 			}
   1313 
   1314 			if ( !nextUpdate->isInList ) {
   1315 				nextUpdate->next = NULL;
   1316 				nextUpdate->prev = updateListEnd;
   1317 				if ( updateListEnd ) {
   1318 					updateListEnd->next = nextUpdate;
   1319 				} else {
   1320 					updateListStart = nextUpdate;
   1321 				}
   1322 				updateListEnd = nextUpdate;
   1323 				nextUpdate->isInList = true;
   1324 			}
   1325 
   1326 			// don't put goal near a ledge
   1327 			if ( !( nextArea->flags & AREA_LEDGE ) ) {
   1328 
   1329 				// add travel time through the area
   1330 				t += AreaTravelTime( reach->toAreaNum, reach->end, nextArea->center );
   1331 	
   1332 				if ( !bestTravelTime || t < bestTravelTime ) {
   1333 					// if the area is not visible to the target
   1334 					if ( callback.TestArea( this, reach->toAreaNum ) ) {
   1335 						bestTravelTime = t;
   1336 						bestAreaNum = reach->toAreaNum;
   1337 					}
   1338 				}
   1339 			}
   1340 		}
   1341 	}
   1342 
   1343 	if ( bestAreaNum ) {
   1344 		goal.areaNum = bestAreaNum;
   1345 		goal.origin = AreaCenter( bestAreaNum );
   1346 		return true;
   1347 	}
   1348 
   1349 	return false;
   1350 }