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 }