2 ===========================================================================
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
22 In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
26 ===========================================================================
29 #include "../../idlib/precompiled.h"
32 #include "AAS_local.h"
33 #include "../Game_local.h" // for print and error
35 #define CACHETYPE_AREA 1
36 #define CACHETYPE_PORTAL 2
38 #define MAX_ROUTING_CACHE_MEMORY (2*1024*1024)
40 #define LEDGE_TRAVELTIME_PANALTY 250
44 idRoutingCache::idRoutingCache
47 idRoutingCache::idRoutingCache( int size ) {
51 time_next = time_prev = NULL;
56 reachabilities = new byte[size];
57 memset( reachabilities, 0, size * sizeof( reachabilities[0] ) );
58 travelTimes = new unsigned short[size];
59 memset( travelTimes, 0, size * sizeof( travelTimes[0] ) );
64 idRoutingCache::~idRoutingCache
67 idRoutingCache::~idRoutingCache( void ) {
68 delete [] reachabilities;
69 delete [] travelTimes;
77 int idRoutingCache::Size( void ) const {
78 return sizeof( idRoutingCache ) + size * sizeof( reachabilities[0] ) + size * sizeof( travelTimes[0] );
83 idAASLocal::AreaTravelTime
86 unsigned short idAASLocal::AreaTravelTime( int areaNum, const idVec3 &start, const idVec3 &end ) const {
89 dist = ( end - start ).Length();
91 if ( file->GetArea( areaNum ).travelFlags & TFL_CROUCH ) {
92 dist *= 100.0f / 100.0f;
93 } else if ( file->GetArea( areaNum ).travelFlags & TFL_WATER ) {
94 dist *= 100.0f / 150.0f;
96 dist *= 100.0f / 300.0f;
101 return (unsigned short) idMath::FtoiFast( dist );
106 idAASLocal::CalculateAreaTravelTimes
109 void idAASLocal::CalculateAreaTravelTimes(void) {
110 int n, i, j, numReach, numRevReach, t, maxt;
112 idReachability *reach, *rev_reach;
114 // get total memory for all area travel times
115 numAreaTravelTimes = 0;
116 for ( n = 0; n < file->GetNumAreas(); n++ ) {
118 if ( !(file->GetArea( n ).flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) {
123 for ( reach = file->GetArea( n ).reach; reach; reach = reach->next ) {
128 for ( rev_reach = file->GetArea( n ).rev_reach; rev_reach; rev_reach = rev_reach->rev_next ) {
131 numAreaTravelTimes += numReach * numRevReach;
134 areaTravelTimes = (unsigned short *) Mem_Alloc( numAreaTravelTimes * sizeof( unsigned short ) );
135 bytePtr = (byte *) areaTravelTimes;
137 for ( n = 0; n < file->GetNumAreas(); n++ ) {
139 if ( !(file->GetArea( n ).flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) {
143 // for each reachability that starts in this area calculate the travel time
144 // towards all the reachabilities that lead towards this area
145 for ( maxt = i = 0, reach = file->GetArea( n ).reach; reach; reach = reach->next, i++ ) {
146 assert( i < MAX_REACH_PER_AREA );
147 if ( i >= MAX_REACH_PER_AREA ) {
148 gameLocal.Error( "i >= MAX_REACH_PER_AREA" );
151 reach->disableCount = 0;
152 reach->areaTravelTimes = (unsigned short *) bytePtr;
153 for ( j = 0, rev_reach = file->GetArea( n ).rev_reach; rev_reach; rev_reach = rev_reach->rev_next, j++ ) {
154 t = AreaTravelTime( n, reach->start, rev_reach->end );
155 reach->areaTravelTimes[j] = t;
160 bytePtr += j * sizeof( unsigned short );
163 // if this area is a portal
164 if ( file->GetArea( n ).cluster < 0 ) {
165 // set the maximum travel time through this portal
166 file->SetPortalMaxTravelTime( -file->GetArea( n ).cluster, maxt );
170 assert( ( (unsigned int) bytePtr - (unsigned int) areaTravelTimes ) <= numAreaTravelTimes * sizeof( unsigned short ) );
175 idAASLocal::DeleteAreaTravelTimes
178 void idAASLocal::DeleteAreaTravelTimes( void ) {
179 Mem_Free( areaTravelTimes );
180 areaTravelTimes = NULL;
181 numAreaTravelTimes = 0;
186 idAASLocal::SetupRoutingCache
189 void idAASLocal::SetupRoutingCache( void ) {
193 areaCacheIndexSize = 0;
194 for ( i = 0; i < file->GetNumClusters(); i++ ) {
195 areaCacheIndexSize += file->GetCluster( i ).numReachableAreas;
197 areaCacheIndex = (idRoutingCache ***) Mem_ClearedAlloc( file->GetNumClusters() * sizeof( idRoutingCache ** ) +
198 areaCacheIndexSize * sizeof( idRoutingCache *) );
199 bytePtr = ((byte *)areaCacheIndex) + file->GetNumClusters() * sizeof( idRoutingCache ** );
200 for ( i = 0; i < file->GetNumClusters(); i++ ) {
201 areaCacheIndex[i] = ( idRoutingCache ** ) bytePtr;
202 bytePtr += file->GetCluster( i ).numReachableAreas * sizeof( idRoutingCache * );
205 portalCacheIndexSize = file->GetNumAreas();
206 portalCacheIndex = (idRoutingCache **) Mem_ClearedAlloc( portalCacheIndexSize * sizeof( idRoutingCache * ) );
208 areaUpdate = (idRoutingUpdate *) Mem_ClearedAlloc( file->GetNumAreas() * sizeof( idRoutingUpdate ) );
209 portalUpdate = (idRoutingUpdate *) Mem_ClearedAlloc( (file->GetNumPortals()+1) * sizeof( idRoutingUpdate ) );
211 goalAreaTravelTimes = (unsigned short *) Mem_ClearedAlloc( file->GetNumAreas() * sizeof( unsigned short ) );
213 cacheListStart = cacheListEnd = NULL;
214 totalCacheMemory = 0;
219 idAASLocal::DeleteClusterCache
222 void idAASLocal::DeleteClusterCache( int clusterNum ) {
224 idRoutingCache *cache;
226 for ( i = 0; i < file->GetCluster( clusterNum ).numReachableAreas; i++ ) {
227 for ( cache = areaCacheIndex[clusterNum][i]; cache; cache = areaCacheIndex[clusterNum][i] ) {
228 areaCacheIndex[clusterNum][i] = cache->next;
229 UnlinkCache( cache );
237 idAASLocal::DeletePortalCache
240 void idAASLocal::DeletePortalCache( void ) {
242 idRoutingCache *cache;
244 for ( i = 0; i < file->GetNumAreas(); i++ ) {
245 for ( cache = portalCacheIndex[i]; cache; cache = portalCacheIndex[i] ) {
246 portalCacheIndex[i] = cache->next;
247 UnlinkCache( cache );
255 idAASLocal::ShutdownRoutingCache
258 void idAASLocal::ShutdownRoutingCache( void ) {
261 for ( i = 0; i < file->GetNumClusters(); i++ ) {
262 DeleteClusterCache( i );
267 Mem_Free( areaCacheIndex );
268 areaCacheIndex = NULL;
269 areaCacheIndexSize = 0;
270 Mem_Free( portalCacheIndex );
271 portalCacheIndex = NULL;
272 portalCacheIndexSize = 0;
273 Mem_Free( areaUpdate );
275 Mem_Free( portalUpdate );
277 Mem_Free( goalAreaTravelTimes );
278 goalAreaTravelTimes = NULL;
280 cacheListStart = cacheListEnd = NULL;
281 totalCacheMemory = 0;
286 idAASLocal::SetupRouting
289 bool idAASLocal::SetupRouting( void ) {
290 CalculateAreaTravelTimes();
297 idAASLocal::ShutdownRouting
300 void idAASLocal::ShutdownRouting( void ) {
301 DeleteAreaTravelTimes();
302 ShutdownRoutingCache();
307 idAASLocal::RoutingStats
310 void idAASLocal::RoutingStats( void ) const {
311 idRoutingCache *cache;
312 int numAreaCache, numPortalCache;
313 int totalAreaCacheMemory, totalPortalCacheMemory;
315 numAreaCache = numPortalCache = 0;
316 totalAreaCacheMemory = totalPortalCacheMemory = 0;
317 for ( cache = cacheListStart; cache; cache = cache->time_next ) {
318 if ( cache->type == CACHETYPE_AREA ) {
320 totalAreaCacheMemory += sizeof( idRoutingCache ) + cache->size * (sizeof( unsigned short ) + sizeof( byte ));
323 totalPortalCacheMemory += sizeof( idRoutingCache ) + cache->size * (sizeof( unsigned short ) + sizeof( byte ));
327 gameLocal.Printf( "%6d area cache (%d KB)\n", numAreaCache, totalAreaCacheMemory >> 10 );
328 gameLocal.Printf( "%6d portal cache (%d KB)\n", numPortalCache, totalPortalCacheMemory >> 10 );
329 gameLocal.Printf( "%6d total cache (%d KB)\n", numAreaCache + numPortalCache, totalCacheMemory >> 10 );
330 gameLocal.Printf( "%6d area travel times (%d KB)\n", numAreaTravelTimes, ( numAreaTravelTimes * sizeof( unsigned short ) ) >> 10 );
331 gameLocal.Printf( "%6d area cache entries (%d KB)\n", areaCacheIndexSize, ( areaCacheIndexSize * sizeof( idRoutingCache * ) ) >> 10 );
332 gameLocal.Printf( "%6d portal cache entries (%d KB)\n", portalCacheIndexSize, ( portalCacheIndexSize * sizeof( idRoutingCache * ) ) >> 10 );
337 idAASLocal::RemoveRoutingCacheUsingArea
340 void idAASLocal::RemoveRoutingCacheUsingArea( int areaNum ) {
343 clusterNum = file->GetArea( areaNum ).cluster;
344 if ( clusterNum > 0 ) {
345 // remove all the cache in the cluster the area is in
346 DeleteClusterCache( clusterNum );
349 // if this is a portal remove all cache in both the front and back cluster
350 DeleteClusterCache( file->GetPortal( -clusterNum ).clusters[0] );
351 DeleteClusterCache( file->GetPortal( -clusterNum ).clusters[1] );
358 idAASLocal::DisableArea
361 void idAASLocal::DisableArea( int areaNum ) {
362 assert( areaNum > 0 && areaNum < file->GetNumAreas() );
364 if ( file->GetArea( areaNum ).travelFlags & TFL_INVALID ) {
368 file->SetAreaTravelFlag( areaNum, TFL_INVALID );
370 RemoveRoutingCacheUsingArea( areaNum );
375 idAASLocal::EnableArea
378 void idAASLocal::EnableArea( int areaNum ) {
379 assert( areaNum > 0 && areaNum < file->GetNumAreas() );
381 if ( !( file->GetArea( areaNum ).travelFlags & TFL_INVALID ) ) {
385 file->RemoveAreaTravelFlag( areaNum, TFL_INVALID );
387 RemoveRoutingCacheUsingArea( areaNum );
392 idAASLocal::SetAreaState_r
395 bool idAASLocal::SetAreaState_r( int nodeNum, const idBounds &bounds, const int areaContents, bool disabled ) {
397 const aasNode_t *node;
398 bool foundClusterPortal = false;
400 while( nodeNum != 0 ) {
402 // if this area is a cluster portal
403 if ( file->GetArea( -nodeNum ).contents & areaContents ) {
405 DisableArea( -nodeNum );
407 EnableArea( -nodeNum );
409 foundClusterPortal |= true;
413 node = &file->GetNode( nodeNum );
414 res = bounds.PlaneSide( file->GetPlane( node->planeNum ) );
415 if ( res == PLANESIDE_BACK ) {
416 nodeNum = node->children[1];
418 else if ( res == PLANESIDE_FRONT ) {
419 nodeNum = node->children[0];
422 foundClusterPortal |= SetAreaState_r( node->children[1], bounds, areaContents, disabled );
423 nodeNum = node->children[0];
427 return foundClusterPortal;
432 idAASLocal::SetAreaState
435 bool idAASLocal::SetAreaState( const idBounds &bounds, const int areaContents, bool disabled ) {
442 expBounds[0] = bounds[0] - file->GetSettings().boundingBoxes[0][1];
443 expBounds[1] = bounds[1] - file->GetSettings().boundingBoxes[0][0];
445 // find all areas within or touching the bounds with the given contents and disable/enable them for routing
446 return SetAreaState_r( 1, expBounds, areaContents, disabled );
451 idAASLocal::GetBoundsAreas_r
454 void idAASLocal::GetBoundsAreas_r( int nodeNum, const idBounds &bounds, idList<int> &areas ) const {
456 const aasNode_t *node;
458 while( nodeNum != 0 ) {
460 areas.Append( -nodeNum );
463 node = &file->GetNode( nodeNum );
464 res = bounds.PlaneSide( file->GetPlane( node->planeNum ) );
465 if ( res == PLANESIDE_BACK ) {
466 nodeNum = node->children[1];
468 else if ( res == PLANESIDE_FRONT ) {
469 nodeNum = node->children[0];
472 GetBoundsAreas_r( node->children[1], bounds, areas );
473 nodeNum = node->children[0];
480 idAASLocal::SetObstacleState
483 void idAASLocal::SetObstacleState( const idRoutingObstacle *obstacle, bool enable ) {
485 const aasArea_t *area;
486 idReachability *reach, *rev_reach;
489 for ( i = 0; i < obstacle->areas.Num(); i++ ) {
491 RemoveRoutingCacheUsingArea( obstacle->areas[i] );
493 area = &file->GetArea( obstacle->areas[i] );
495 for ( rev_reach = area->rev_reach; rev_reach; rev_reach = rev_reach->rev_next ) {
497 if ( rev_reach->travelType & TFL_INVALID ) {
503 if ( obstacle->bounds.ContainsPoint( rev_reach->end ) ) {
507 for ( reach = area->reach; reach; reach = reach->next ) {
508 if ( obstacle->bounds.LineIntersection( rev_reach->end, reach->start ) ) {
517 rev_reach->disableCount--;
518 if ( rev_reach->disableCount <= 0 ) {
519 rev_reach->travelType &= ~TFL_INVALID;
520 rev_reach->disableCount = 0;
524 rev_reach->travelType |= TFL_INVALID;
525 rev_reach->disableCount++;
534 idAASLocal::AddObstacle
537 aasHandle_t idAASLocal::AddObstacle( const idBounds &bounds ) {
538 idRoutingObstacle *obstacle;
544 obstacle = new idRoutingObstacle;
545 obstacle->bounds[0] = bounds[0] - file->GetSettings().boundingBoxes[0][1];
546 obstacle->bounds[1] = bounds[1] - file->GetSettings().boundingBoxes[0][0];
547 GetBoundsAreas_r( 1, obstacle->bounds, obstacle->areas );
548 SetObstacleState( obstacle, true );
550 obstacleList.Append( obstacle );
551 return obstacleList.Num() - 1;
556 idAASLocal::RemoveObstacle
559 void idAASLocal::RemoveObstacle( const aasHandle_t handle ) {
563 if ( ( handle >= 0 ) && ( handle < obstacleList.Num() ) ) {
564 SetObstacleState( obstacleList[handle], false );
566 delete obstacleList[handle];
567 obstacleList.RemoveIndex( handle );
573 idAASLocal::RemoveAllObstacles
576 void idAASLocal::RemoveAllObstacles( void ) {
583 for ( i = 0; i < obstacleList.Num(); i++ ) {
584 SetObstacleState( obstacleList[i], false );
585 delete obstacleList[i];
587 obstacleList.Clear();
592 idAASLocal::LinkCache
594 link the cache in the cache list sorted from oldest to newest cache
597 void idAASLocal::LinkCache( idRoutingCache *cache ) const {
599 // if the cache is already linked
600 if ( cache->time_next || cache->time_prev || cacheListStart == cache ) {
601 UnlinkCache( cache );
604 totalCacheMemory += cache->Size();
606 // add cache to the end of the list
607 cache->time_next = NULL;
608 cache->time_prev = cacheListEnd;
609 if ( cacheListEnd ) {
610 cacheListEnd->time_next = cache;
612 cacheListEnd = cache;
613 if ( !cacheListStart ) {
614 cacheListStart = cache;
620 idAASLocal::UnlinkCache
623 void idAASLocal::UnlinkCache( idRoutingCache *cache ) const {
625 totalCacheMemory -= cache->Size();
628 if ( cache->time_next ) {
629 cache->time_next->time_prev = cache->time_prev;
631 cacheListEnd = cache->time_prev;
633 if ( cache->time_prev ) {
634 cache->time_prev->time_next = cache->time_next;
636 cacheListStart = cache->time_next;
638 cache->time_next = cache->time_prev = NULL;
643 idAASLocal::DeleteOldestCache
646 void idAASLocal::DeleteOldestCache( void ) const {
647 idRoutingCache *cache;
649 assert( cacheListStart );
651 // unlink the oldest cache
652 cache = cacheListStart;
653 UnlinkCache( cache );
655 // unlink the oldest cache from the area or portal cache index
657 cache->next->prev = cache->prev;
660 cache->prev->next = cache->next;
662 else if ( cache->type == CACHETYPE_AREA ) {
663 areaCacheIndex[cache->cluster][ClusterAreaNum( cache->cluster, cache->areaNum )] = cache->next;
665 else if ( cache->type == CACHETYPE_PORTAL ) {
666 portalCacheIndex[cache->areaNum] = cache->next;
674 idAASLocal::GetAreaReachability
677 idReachability *idAASLocal::GetAreaReachability( int areaNum, int reachabilityNum ) const {
678 idReachability *reach;
680 for ( reach = file->GetArea( areaNum ).reach; reach; reach = reach->next ) {
681 if ( --reachabilityNum < 0 ) {
690 idAASLocal::ClusterAreaNum
693 ID_INLINE int idAASLocal::ClusterAreaNum( int clusterNum, int areaNum ) const {
694 int side, areaCluster;
696 areaCluster = file->GetArea( areaNum ).cluster;
697 if ( areaCluster > 0 ) {
698 return file->GetArea( areaNum ).clusterAreaNum;
701 side = file->GetPortal( -areaCluster ).clusters[0] != clusterNum;
702 return file->GetPortal( -areaCluster ).clusterAreaNum[side];
708 idAASLocal::UpdateAreaRoutingCache
711 void idAASLocal::UpdateAreaRoutingCache( idRoutingCache *areaCache ) const {
712 int i, nextAreaNum, cluster, badTravelFlags, clusterAreaNum, numReachableAreas;
713 unsigned short t, startAreaTravelTimes[MAX_REACH_PER_AREA];
714 idRoutingUpdate *updateListStart, *updateListEnd, *curUpdate, *nextUpdate;
715 idReachability *reach;
716 const aasArea_t *nextArea;
718 // number of reachability areas within this cluster
719 numReachableAreas = file->GetCluster( areaCache->cluster ).numReachableAreas;
721 // number of the start area within the cluster
722 clusterAreaNum = ClusterAreaNum( areaCache->cluster, areaCache->areaNum );
723 if ( clusterAreaNum >= numReachableAreas ) {
727 areaCache->travelTimes[clusterAreaNum] = areaCache->startTravelTime;
728 badTravelFlags = ~areaCache->travelFlags;
729 memset( startAreaTravelTimes, 0, sizeof( startAreaTravelTimes ) );
731 // initialize first update
732 curUpdate = &areaUpdate[clusterAreaNum];
733 curUpdate->areaNum = areaCache->areaNum;
734 curUpdate->areaTravelTimes = startAreaTravelTimes;
735 curUpdate->tmpTravelTime = areaCache->startTravelTime;
736 curUpdate->next = NULL;
737 curUpdate->prev = NULL;
738 updateListStart = curUpdate;
739 updateListEnd = curUpdate;
741 // while there are updates in the list
742 while( updateListStart ) {
744 curUpdate = updateListStart;
745 if ( curUpdate->next ) {
746 curUpdate->next->prev = NULL;
749 updateListEnd = NULL;
751 updateListStart = curUpdate->next;
753 curUpdate->isInList = false;
755 for ( i = 0, reach = file->GetArea( curUpdate->areaNum ).rev_reach; reach; reach = reach->rev_next, i++ ) {
757 // if the reachability uses an undesired travel type
758 if ( reach->travelType & badTravelFlags ) {
762 // next area the reversed reachability leads to
763 nextAreaNum = reach->fromAreaNum;
764 nextArea = &file->GetArea( nextAreaNum );
766 // if traveling through the next area requires an undesired travel flag
767 if ( nextArea->travelFlags & badTravelFlags ) {
771 // get the cluster number of the area
772 cluster = nextArea->cluster;
773 // don't leave the cluster, however do flood into cluster portals
774 if ( cluster > 0 && cluster != areaCache->cluster ) {
778 // get the number of the area in the cluster
779 clusterAreaNum = ClusterAreaNum( areaCache->cluster, nextAreaNum );
780 if ( clusterAreaNum >= numReachableAreas ) {
781 continue; // should never happen
784 assert( clusterAreaNum < areaCache->size );
786 // time already travelled plus the traveltime through the current area
787 // plus the travel time of the reachability towards the next area
788 t = curUpdate->tmpTravelTime + curUpdate->areaTravelTimes[i] + reach->travelTime;
790 if ( !areaCache->travelTimes[clusterAreaNum] || t < areaCache->travelTimes[clusterAreaNum] ) {
792 areaCache->travelTimes[clusterAreaNum] = t;
793 areaCache->reachabilities[clusterAreaNum] = reach->number; // reversed reachability used to get into this area
794 nextUpdate = &areaUpdate[clusterAreaNum];
795 nextUpdate->areaNum = nextAreaNum;
796 nextUpdate->tmpTravelTime = t;
797 nextUpdate->areaTravelTimes = reach->areaTravelTimes;
799 // if we are not allowed to fly
800 if ( badTravelFlags & TFL_FLY ) {
801 // avoid areas near ledges
802 if ( file->GetArea( nextAreaNum ).flags & AREA_LEDGE ) {
803 nextUpdate->tmpTravelTime += LEDGE_TRAVELTIME_PANALTY;
807 if ( !nextUpdate->isInList ) {
808 nextUpdate->next = NULL;
809 nextUpdate->prev = updateListEnd;
810 if ( updateListEnd ) {
811 updateListEnd->next = nextUpdate;
814 updateListStart = nextUpdate;
816 updateListEnd = nextUpdate;
817 nextUpdate->isInList = true;
826 idAASLocal::GetAreaRoutingCache
829 idRoutingCache *idAASLocal::GetAreaRoutingCache( int clusterNum, int areaNum, int travelFlags ) const {
831 idRoutingCache *cache, *clusterCache;
833 // number of the area in the cluster
834 clusterAreaNum = ClusterAreaNum( clusterNum, areaNum );
835 // pointer to the cache for the area in the cluster
836 clusterCache = areaCacheIndex[clusterNum][clusterAreaNum];
837 // check if cache without undesired travel flags already exists
838 for ( cache = clusterCache; cache; cache = cache->next ) {
839 if ( cache->travelFlags == travelFlags ) {
845 cache = new idRoutingCache( file->GetCluster( clusterNum ).numReachableAreas );
846 cache->type = CACHETYPE_AREA;
847 cache->cluster = clusterNum;
848 cache->areaNum = areaNum;
849 cache->startTravelTime = 1;
850 cache->travelFlags = travelFlags;
852 cache->next = clusterCache;
853 if ( clusterCache ) {
854 clusterCache->prev = cache;
856 areaCacheIndex[clusterNum][clusterAreaNum] = cache;
857 UpdateAreaRoutingCache( cache );
865 idAASLocal::UpdatePortalRoutingCache
868 void idAASLocal::UpdatePortalRoutingCache( idRoutingCache *portalCache ) const {
869 int i, portalNum, clusterAreaNum;
871 const aasPortal_t *portal;
872 const aasCluster_t *cluster;
873 idRoutingCache *cache;
874 idRoutingUpdate *updateListStart, *updateListEnd, *curUpdate, *nextUpdate;
876 curUpdate = &portalUpdate[ file->GetNumPortals() ];
877 curUpdate->cluster = portalCache->cluster;
878 curUpdate->areaNum = portalCache->areaNum;
879 curUpdate->tmpTravelTime = portalCache->startTravelTime;
881 //put the area to start with in the current read list
882 curUpdate->next = NULL;
883 curUpdate->prev = NULL;
884 updateListStart = curUpdate;
885 updateListEnd = curUpdate;
887 // while there are updates in the current list
888 while( updateListStart ) {
890 curUpdate = updateListStart;
891 // remove the current update from the list
892 if ( curUpdate->next ) {
893 curUpdate->next->prev = NULL;
896 updateListEnd = NULL;
898 updateListStart = curUpdate->next;
899 // current update is removed from the list
900 curUpdate->isInList = false;
902 cluster = &file->GetCluster( curUpdate->cluster );
903 cache = GetAreaRoutingCache( curUpdate->cluster, curUpdate->areaNum, portalCache->travelFlags );
905 // take all portals of the cluster
906 for ( i = 0; i < cluster->numPortals; i++ ) {
907 portalNum = file->GetPortalIndex( cluster->firstPortal + i );
908 assert( portalNum < portalCache->size );
909 portal = &file->GetPortal( portalNum );
911 clusterAreaNum = ClusterAreaNum( curUpdate->cluster, portal->areaNum );
912 if ( clusterAreaNum >= cluster->numReachableAreas ) {
916 t = cache->travelTimes[clusterAreaNum];
920 t += curUpdate->tmpTravelTime;
922 if ( !portalCache->travelTimes[portalNum] || t < portalCache->travelTimes[portalNum] ) {
924 portalCache->travelTimes[portalNum] = t;
925 portalCache->reachabilities[portalNum] = cache->reachabilities[clusterAreaNum];
926 nextUpdate = &portalUpdate[portalNum];
927 if ( portal->clusters[0] == curUpdate->cluster ) {
928 nextUpdate->cluster = portal->clusters[1];
931 nextUpdate->cluster = portal->clusters[0];
933 nextUpdate->areaNum = portal->areaNum;
934 // add travel time through the actual portal area for the next update
935 nextUpdate->tmpTravelTime = t + portal->maxAreaTravelTime;
937 if ( !nextUpdate->isInList ) {
939 nextUpdate->next = NULL;
940 nextUpdate->prev = updateListEnd;
941 if ( updateListEnd ) {
942 updateListEnd->next = nextUpdate;
945 updateListStart = nextUpdate;
947 updateListEnd = nextUpdate;
948 nextUpdate->isInList = true;
957 idAASLocal::GetPortalRoutingCache
960 idRoutingCache *idAASLocal::GetPortalRoutingCache( int clusterNum, int areaNum, int travelFlags ) const {
961 idRoutingCache *cache;
963 // check if cache without undesired travel flags already exists
964 for ( cache = portalCacheIndex[areaNum]; cache; cache = cache->next ) {
965 if ( cache->travelFlags == travelFlags ) {
971 cache = new idRoutingCache( file->GetNumPortals() );
972 cache->type = CACHETYPE_PORTAL;
973 cache->cluster = clusterNum;
974 cache->areaNum = areaNum;
975 cache->startTravelTime = 1;
976 cache->travelFlags = travelFlags;
978 cache->next = portalCacheIndex[areaNum];
979 if ( portalCacheIndex[areaNum] ) {
980 portalCacheIndex[areaNum]->prev = cache;
982 portalCacheIndex[areaNum] = cache;
983 UpdatePortalRoutingCache( cache );
991 idAASLocal::RouteToGoalArea
994 bool idAASLocal::RouteToGoalArea( int areaNum, const idVec3 origin, int goalAreaNum, int travelFlags, int &travelTime, idReachability **reach ) const {
995 int clusterNum, goalClusterNum, portalNum, i, clusterAreaNum;
996 unsigned short int t, bestTime;
997 const aasPortal_t *portal;
998 const aasCluster_t *cluster;
999 idRoutingCache *areaCache, *portalCache, *clusterCache;
1000 idReachability *bestReach, *r, *nextr;
1009 if ( areaNum == goalAreaNum ) {
1013 if ( areaNum <= 0 || areaNum >= file->GetNumAreas() ) {
1014 gameLocal.Printf( "RouteToGoalArea: areaNum %d out of range\n", areaNum );
1017 if ( goalAreaNum <= 0 || goalAreaNum >= file->GetNumAreas() ) {
1018 gameLocal.Printf( "RouteToGoalArea: goalAreaNum %d out of range\n", goalAreaNum );
1022 while( totalCacheMemory > MAX_ROUTING_CACHE_MEMORY ) {
1023 DeleteOldestCache();
1026 clusterNum = file->GetArea( areaNum ).cluster;
1027 goalClusterNum = file->GetArea( goalAreaNum ).cluster;
1029 // if the source area is a cluster portal, read directly from the portal cache
1030 if ( clusterNum < 0 ) {
1031 // if the goal area is a portal
1032 if ( goalClusterNum < 0 ) {
1033 // just assume the goal area is part of the front cluster
1034 portal = &file->GetPortal( -goalClusterNum );
1035 goalClusterNum = portal->clusters[0];
1037 // get the portal routing cache
1038 portalCache = GetPortalRoutingCache( goalClusterNum, goalAreaNum, travelFlags );
1039 *reach = GetAreaReachability( areaNum, portalCache->reachabilities[-clusterNum] );
1040 travelTime = portalCache->travelTimes[-clusterNum] + AreaTravelTime( areaNum, origin, (*reach)->start );
1047 // check if the goal area is a portal of the source area cluster
1048 if ( goalClusterNum < 0 ) {
1049 portal = &file->GetPortal( -goalClusterNum );
1050 if ( portal->clusters[0] == clusterNum || portal->clusters[1] == clusterNum) {
1051 goalClusterNum = clusterNum;
1055 // if both areas are in the same cluster
1056 if ( clusterNum > 0 && goalClusterNum > 0 && clusterNum == goalClusterNum ) {
1057 clusterCache = GetAreaRoutingCache( clusterNum, goalAreaNum, travelFlags );
1058 clusterAreaNum = ClusterAreaNum( clusterNum, areaNum );
1059 if ( clusterCache->travelTimes[clusterAreaNum] ) {
1060 bestReach = GetAreaReachability( areaNum, clusterCache->reachabilities[clusterAreaNum] );
1061 bestTime = clusterCache->travelTimes[clusterAreaNum] + AreaTravelTime( areaNum, origin, bestReach->start );
1064 clusterCache = NULL;
1068 clusterCache = NULL;
1071 clusterNum = file->GetArea( areaNum ).cluster;
1072 goalClusterNum = file->GetArea( goalAreaNum ).cluster;
1074 // if the goal area is a portal
1075 if ( goalClusterNum < 0 ) {
1076 // just assume the goal area is part of the front cluster
1077 portal = &file->GetPortal( -goalClusterNum );
1078 goalClusterNum = portal->clusters[0];
1080 // get the portal routing cache
1081 portalCache = GetPortalRoutingCache( goalClusterNum, goalAreaNum, travelFlags );
1083 // the cluster the area is in
1084 cluster = &file->GetCluster( clusterNum );
1085 // current area inside the current cluster
1086 clusterAreaNum = ClusterAreaNum( clusterNum, areaNum );
1087 // if the area is not a reachable area
1088 if ( clusterAreaNum >= cluster->numReachableAreas) {
1092 // find the portal of the source area cluster leading towards the goal area
1093 for ( i = 0; i < cluster->numPortals; i++ ) {
1094 portalNum = file->GetPortalIndex( cluster->firstPortal + i );
1096 // if the goal area isn't reachable from the portal
1097 if ( !portalCache->travelTimes[portalNum] ) {
1101 portal = &file->GetPortal( portalNum );
1102 // get the cache of the portal area
1103 areaCache = GetAreaRoutingCache( clusterNum, portal->areaNum, travelFlags );
1104 // if the portal is not reachable from this area
1105 if ( !areaCache->travelTimes[clusterAreaNum] ) {
1109 r = GetAreaReachability( areaNum, areaCache->reachabilities[clusterAreaNum] );
1111 if ( clusterCache ) {
1112 // if the next reachability from the portal leads back into the cluster
1113 nextr = GetAreaReachability( portal->areaNum, portalCache->reachabilities[portalNum] );
1114 if ( file->GetArea( nextr->toAreaNum ).cluster < 0 || file->GetArea( nextr->toAreaNum ).cluster == clusterNum ) {
1119 // the total travel time is the travel time from the portal area to the goal area
1120 // plus the travel time from the source area towards the portal area
1121 t = portalCache->travelTimes[portalNum] + areaCache->travelTimes[clusterAreaNum];
1122 // NOTE: Should add the exact travel time through the portal area.
1123 // However we add the largest travel time through the portal area.
1124 // We cannot directly calculate the exact travel time through the portal area
1125 // because the reachability used to travel into the portal area is not known.
1126 t += portal->maxAreaTravelTime;
1128 // if the time is better than the one already found
1129 if ( !bestTime || t < bestTime ) {
1140 travelTime = bestTime;
1147 idAASLocal::TravelTimeToGoalArea
1150 int idAASLocal::TravelTimeToGoalArea( int areaNum, const idVec3 &origin, int goalAreaNum, int travelFlags ) const {
1152 idReachability *reach;
1158 if ( !RouteToGoalArea( areaNum, origin, goalAreaNum, travelFlags, travelTime, &reach ) ) {
1166 idAASLocal::FindNearestGoal
1169 bool idAASLocal::FindNearestGoal( aasGoal_t &goal, int areaNum, const idVec3 origin, const idVec3 &target, int travelFlags, aasObstacle_t *obstacles, int numObstacles, idAASCallback &callback ) const {
1170 int i, j, k, badTravelFlags, nextAreaNum, bestAreaNum;
1171 unsigned short t, bestTravelTime;
1172 idRoutingUpdate *updateListStart, *updateListEnd, *curUpdate, *nextUpdate;
1173 idReachability *reach;
1174 const aasArea_t *nextArea;
1176 float targetDist, dist;
1178 if ( file == NULL || areaNum <= 0 ) {
1179 goal.areaNum = areaNum;
1180 goal.origin = origin;
1184 // if the first area is valid goal, just return the origin
1185 if ( callback.TestArea( this, areaNum ) ) {
1186 goal.areaNum = areaNum;
1187 goal.origin = origin;
1192 for ( k = 0; k < numObstacles; k++ ) {
1193 obstacles[k].expAbsBounds[0] = obstacles[k].absBounds[0] - file->GetSettings().boundingBoxes[0][1];
1194 obstacles[k].expAbsBounds[1] = obstacles[k].absBounds[1] - file->GetSettings().boundingBoxes[0][0];
1197 badTravelFlags = ~travelFlags;
1198 SIMDProcessor->Memset( goalAreaTravelTimes, 0, file->GetNumAreas() * sizeof( unsigned short ) );
1200 targetDist = (target - origin).Length();
1202 // initialize first update
1203 curUpdate = &areaUpdate[areaNum];
1204 curUpdate->areaNum = areaNum;
1205 curUpdate->tmpTravelTime = 0;
1206 curUpdate->start = origin;
1207 curUpdate->next = NULL;
1208 curUpdate->prev = NULL;
1209 updateListStart = curUpdate;
1210 updateListEnd = curUpdate;
1215 // while there are updates in the list
1216 while ( updateListStart ) {
1218 curUpdate = updateListStart;
1219 if ( curUpdate->next ) {
1220 curUpdate->next->prev = NULL;
1223 updateListEnd = NULL;
1225 updateListStart = curUpdate->next;
1227 curUpdate->isInList = false;
1229 // if we already found a closer location
1230 if ( bestTravelTime && curUpdate->tmpTravelTime >= bestTravelTime ) {
1234 for ( i = 0, reach = file->GetArea( curUpdate->areaNum ).reach; reach; reach = reach->next, i++ ) {
1236 // if the reachability uses an undesired travel type
1237 if ( reach->travelType & badTravelFlags ) {
1241 // next area the reversed reachability leads to
1242 nextAreaNum = reach->toAreaNum;
1243 nextArea = &file->GetArea( nextAreaNum );
1245 // if traveling through the next area requires an undesired travel flag
1246 if ( nextArea->travelFlags & badTravelFlags ) {
1250 t = curUpdate->tmpTravelTime +
1251 AreaTravelTime( curUpdate->areaNum, curUpdate->start, reach->start ) +
1254 // project target origin onto movement vector through the area
1255 v1 = reach->end - curUpdate->start;
1257 v2 = target - curUpdate->start;
1258 p = curUpdate->start + (v2 * v1) * v1;
1260 // get the point on the path closest to the target
1261 for ( j = 0; j < 3; j++ ) {
1262 if ( (p[j] > curUpdate->start[j] + 0.1f && p[j] > reach->end[j] + 0.1f) ||
1263 (p[j] < curUpdate->start[j] - 0.1f && p[j] < reach->end[j] - 0.1f) ) {
1268 dist = (target - p).Length();
1270 dist = (target - reach->end).Length();
1273 // avoid moving closer to the target
1274 if ( dist < targetDist ) {
1275 t += ( targetDist - dist ) * 10;
1278 // if we already found a closer location
1279 if ( bestTravelTime && t >= bestTravelTime ) {
1283 // if this is not the best path towards the next area
1284 if ( goalAreaTravelTimes[nextAreaNum] && t >= goalAreaTravelTimes[nextAreaNum] ) {
1288 // path may not go through any obstacles
1289 for ( k = 0; k < numObstacles; k++ ) {
1290 // if the movement vector intersects the expanded obstacle bounds
1291 if ( obstacles[k].expAbsBounds.LineIntersection( curUpdate->start, reach->end ) ) {
1295 if ( k < numObstacles ) {
1299 goalAreaTravelTimes[nextAreaNum] = t;
1300 nextUpdate = &areaUpdate[nextAreaNum];
1301 nextUpdate->areaNum = nextAreaNum;
1302 nextUpdate->tmpTravelTime = t;
1303 nextUpdate->start = reach->end;
1305 // if we are not allowed to fly
1306 if ( badTravelFlags & TFL_FLY ) {
1307 // avoid areas near ledges
1308 if ( file->GetArea( nextAreaNum ).flags & AREA_LEDGE ) {
1309 nextUpdate->tmpTravelTime += LEDGE_TRAVELTIME_PANALTY;
1313 if ( !nextUpdate->isInList ) {
1314 nextUpdate->next = NULL;
1315 nextUpdate->prev = updateListEnd;
1316 if ( updateListEnd ) {
1317 updateListEnd->next = nextUpdate;
1319 updateListStart = nextUpdate;
1321 updateListEnd = nextUpdate;
1322 nextUpdate->isInList = true;
1325 // don't put goal near a ledge
1326 if ( !( nextArea->flags & AREA_LEDGE ) ) {
1328 // add travel time through the area
1329 t += AreaTravelTime( reach->toAreaNum, reach->end, nextArea->center );
1331 if ( !bestTravelTime || t < bestTravelTime ) {
1332 // if the area is not visible to the target
1333 if ( callback.TestArea( this, reach->toAreaNum ) ) {
1335 bestAreaNum = reach->toAreaNum;
1342 if ( bestAreaNum ) {
1343 goal.areaNum = bestAreaNum;
1344 goal.origin = AreaCenter( bestAreaNum );