//Rage headers #include "atl/binheap.h" #include "profile/telemetry.h" #include "profile/timebars.h" #include "profile/cputrace.h" #include "System/ipc.h" #include "System/Xtl.h" #include "Vector/Colors.h" #include "Vector/Geometry.h" #include "system/memory.h" // Framework headers #include "ai/navmesh/priqueue.h" #include "fwmaths/Vector.h" #include "PathServer/PathServer.h" #include "PathServer/PathServerThread.h" #include "ai/navmesh/pathserverthread.h" //Game headers #ifdef GTA_ENGINE #include "game/config.h" // Used for some thread priority stuff now, will need to be dealt with when moving more code to RAGE. #include "system/threadpriorities.h" #include "task/Movement/TaskNavMesh.h" // Used for tracking cases where an AI task has failed to retrieve a requested path #include "task/Movement/TaskMoveWander.h" #include "peds/pedpopulation.h" #include "system/InfoState.h" #endif #include "security/ragesecgameinterface.h" #include "Network/Sessions/NetworkSession.h" #if __XENON && !__FINAL #define __TRACE_PATH_REQUESTS_ON_XENON 1 #else #define __TRACE_PATH_REQUESTS_ON_XENON 0 #endif #define TRACK_LEAKED_PATHS 0 //NAVMESH_OPTIMISATIONS_OFF #if __TRACE_PATH_REQUESTS_ON_XENON #include "xtl.h" #include "tracerecording.h" #endif // __TRACE_PATH_REQUESTS_ON_XENON NAVMESH_OPTIMISATIONS() float CPathServerThread::ms_fMaxMassOfPushableObject = 500.0f; float CPathServerThread::ms_fMinMassOfSmallSignificant = 125.f; float CPathServerThread::ms_fMinVolumeOfSignificant = 0.33f; float CPathServerThread::ms_fBigVolumeOfSignificant = 1.5f; float CPathServerThread::ms_fHeightAboveNavMeshForDynObjLOS = 0.4f; //0.1f; // was 0.5f; float CPathServerThread::ms_fHeightAboveFirstTestFor2ndDynObjLOS = 0.4f; // This is the height above ms_fHeightAboveNavMeshForDynObjLOS value (and not above the navmesh itself) bank_float CPathServerThread::ms_fSmallPolySizeWhenTessellating = 2.0f; const float CPathServerThread::ms_fNormalDistBelowToLookForPoly = 4.0f; const float CPathServerThread::ms_fNormalDistAboveToLookForPoly = 0.0f; bank_float CPathServerThread::ms_fPreferDownhillScaleFactor = 50.0f; bank_float CPathServerThread::ms_fWanderTurnWeight = 200.0f; bank_float CPathServerThread::ms_fWanderTurnPlaneWeight = 15.0f; bank_float CPathServerThread::ms_fPullOutFromEdgesDistance = 0.25f; bank_float CPathServerThread::ms_fPullOutFromEdgesDistanceExtra = 0.4f; Vector3 CPathServerThread::ms_vDynamicObjectIntersectionPos(0.0f,0.0f,0.0f); float CPathServerThread::ms_fDynObjPlaneEpsilon[NUM_DYNAMIC_OBJECT_PLANES] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; float CPathServerThread::ms_fDynObjPlaneEpsilonForceReduced[NUM_DYNAMIC_OBJECT_PLANES] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; float CPathServerThread::ms_fDynObjPlaneEpsilonNotReduced[NUM_DYNAMIC_OBJECT_PLANES] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; // The list of all objects intersecting the "bothPolysMinMax" min/max region atArray CPathServerThread::ms_dynamicObjectsIntersectingPolygons(0,256); float CPathServerThread::ms_fNonPavementPenalty_ShortestPath = 50.0f; float CPathServerThread::ms_fNonPavementPenalty_Flee = 55.0f; float CPathServerThread::ms_fNonPavementPenalty_FleeSofter = 75.0f; float CPathServerThread::ms_fNonPavementPenalty_Wander = 1000.0f; //250.0f; //500.0f; // 200.0f // 16000.0f float CPathServerThread::ms_fClimbMultiplier_ShortestPath = 0.65f; float CPathServerThread::ms_fClimbMultiplier_Flee = 3.5f; float CPathServerThread::ms_fClimbMultiplier_Wander = 1.f; float CPathServerThread::ms_fWaterMultiplier_Flee = 10.f; #if __DEV s32 CPathServerThread::m_eVisitedPolyMarkingMode = CPathServerThread::EUseAStarTimeStamp; #endif #if !__FINAL bool CPathServerThread::ms_bMarkVisitedPolys = false; bank_bool CPathServerThread::ms_bUsePrefetching = false; #endif s32 CPathServerThread::ms_iNumVisitedPolysInThisBatchOfPathRequests = 0; s32 CPathServerThread::ms_iNumTessellationsInThisBatchOfPathRequests = 0; s32 CPathServerThread::ms_iMaxNumVisitedPolysInEachBatchOfPathRequests = 256; s32 CPathServerThread::ms_iMaxNumTessellationsInEachBatchOfPathRequests = 128; TPathFindVars CPathServerThread::m_Vars; TVisitPolyStruct CPathServerThread::m_VisitPolyVars; CTestNavMeshLosVars CPathServerThread::m_LosVars; CBlockedLadders CPathServerThread::m_BlockedLadders; bool CPathServerThread::m_bHandleTimestampOverflow = false; bool CPathServerThread::m_bHandleAStarTimestampOverflow = false; bank_u32 CPathServerThread::ms_iPathRequestNumMillisecsBeforeSleepingThread = 8; sysPerformanceTimer * CPathServerThread::m_PathRequestSleepTimer = NULL; bool CPathServerThread::m_bSleepingDuringPathSearch = false; #if !__FINAL && !__PPU && SANITY_CHECK_TESSELLATION void CPathServerThread::SanityCheckPolyConnections(CNavMesh * pNavMesh, TNavMeshPoly * pPoly) { Assert(pNavMesh->m_iIndexOfMesh == pPoly->GetNavMeshIndex()); u32 iPolyIndex = pNavMesh->GetPolyIndex(pPoly); u32 v,v2; for(v=0; vGetNumVertices(); v++) { TAdjPoly adjPoly; pNavMesh->GetAdjacentPoly(pPoly, v, adjPoly); if(adjPoly.GetNavMeshIndex() == NAVMESH_NAVMESH_INDEX_NONE) continue; // non-standard adjacency types don't necessarily link back to this poly if(adjPoly.GetAdjacencyType() != ADJACENCY_TYPE_NORMAL) continue; CNavMesh * pAdjNavMesh = CPathServer::GetNavMeshFromIndex(adjPoly.GetNavMeshIndex()); if(pAdjNavMesh) { TNavMeshPoly * pAdjPoly = pAdjNavMesh->GetPoly(adjPoly.GetPolyIndex()); Assert(pAdjPoly->GetNavMeshIndex() == pAdjNavMesh->m_iIndexOfMesh); for(v2=0; v2GetNumVertices(); v2++) { TAdjPoly adjAdjPoly; pAdjNavMesh->GetAdjacentPoly(pAdjPoly, v2, adjAdjPoly); if(adjAdjPoly.GetNavMeshIndex(pAdjNavMesh->GetAdjacentMeshes()) == pNavMesh->m_iIndexOfMesh && adjAdjPoly.GetPolyIndex() == iPolyIndex) break; } if(v2 == pAdjPoly->GetNumVertices()) { Assert(0 && "adjacent poly doesn't link back"); Displayf("ORIGINAL POLY\n"); CPathServer::DebugPolyText(pNavMesh, pNavMesh->GetPolyIndex(pPoly)); Displayf("ADJACENT POLY\n"); CPathServer::DebugPolyText(pAdjNavMesh, pAdjNavMesh->GetPolyIndex(pAdjPoly)); } } } } #endif #if __XENON // See the XDK "SetThreadName" for info on this. typedef struct tagTHREADNAME_INFO { DWORD dwType; // Must be 0x1000 LPCSTR szName; // Pointer to name (in user address space) DWORD dwThreadID; // Thread ID (-1 for caller thread) DWORD dwFlags; // Reserved for future use; must be zero } THREADNAME_INFO; void SetThreadName( DWORD dwThreadID, LPCSTR szThreadName ) { THREADNAME_INFO info; info.dwType = 0x1000; info.szName = szThreadName; info.dwThreadID = dwThreadID; info.dwFlags = 0; __try { RaiseException( 0x406D1388, 0, sizeof(info)/sizeof(DWORD), (DWORD *)&info ); } __except( EXCEPTION_CONTINUE_EXECUTION ) { } } #endif //----------------------------------------------------------------------------------------------- #ifdef GTA_ENGINE dev_s32 CBlockedLadders::ms_iDisableInterval = 5000; dev_s32 CBlockedLadders::ms_iMaxNumClimbers = 3; void CBlockedLadders::Reset() { for(s32 i=0; i= ms_iDisableInterval) { m_Ladders[i].m_iNumClimbers = 1; } else { m_Ladders[i].m_iNumClimbers++; } if(m_Ladders[i].m_iNumClimbers >= ms_iMaxNumClimbers) { m_Ladders[i].m_iDisableTimer = fwTimer::GetTimeInMilliseconds(); } m_Ladders[i].m_iLastPathfindTime = fwTimer::GetTimeInMilliseconds(); return; } if(iFirstFree < 0 && fwTimer::GetTimeInMilliseconds() - m_Ladders[i].m_iDisableTimer >= ms_iDisableInterval) { iFirstFree = i; } } if(iFirstFree >= 0) { m_Ladders[iFirstFree].m_vBasePosition = vBasePosition; m_Ladders[iFirstFree].m_iDisableTimer = fwTimer::GetTimeInMilliseconds(); m_Ladders[iFirstFree].m_iLastPathfindTime = fwTimer::GetTimeInMilliseconds(); m_Ladders[iFirstFree].m_iNumClimbers = 1; } } #endif //----------------------------------------------------------------------------------------------- //******************************************************* // CPathServerThread // This class encapsulates the path-finding thread, and // provides the methods which it uses to find paths. //******************************************************* const u32 CPathServerThread::ms_iNumFindPathIterationsBeforeGivingTime = 256; // was 1024 const u32 CPathServerThread::ms_iNumRefinePathIterationsBeforeGivingTime = 256; // was 1024 const u32 CPathServerThread::ms_iNumMinimisePathLengthIterationsBeforeGivingTime = 256; // was 1024 CPathServerThread::CPathServerThread() { sysMemUseMemoryBucket b(MEMBUCKET_GAMEPLAY); m_iTotalMemoryUsed = sizeof(CPathServerThread); #ifndef GTA_ENGINE m_hThreadHandle = 0; #endif m_iThreadID = 0; m_DynamicObjectsStore = rage_new TDynamicObject[PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS]; CDynamicObjectsContainer::Init(); //m_PolyObjectsCache = new fwPolyObjectsCache(this); Reset(); Printf("sizeof(CPathServer) : %" SIZETFMT "ik\n", sizeof(CPathServer) / 1024); Printf("sizeof(CPathServerThread) : %" SIZETFMT "ik\n", sizeof(CPathServerThread) / 1024); Printf("CPathServerThread allocated %" SIZETFMT "ik for m_DynamicObjectsStore[%i]\n", (sizeof(TDynamicObject) * PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS)/1024, PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS); #if !__FINAL m_PerfTimer = rage_new sysPerformanceTimer("PathServerThread"); m_RequestProcessingOverallTimer = rage_new sysPerformanceTimer("Request Overall Timer"); m_PolyTessellationTimer = rage_new sysPerformanceTimer("Poly Tessellation Timer"); #endif Reset(); } CPathServerThread::~CPathServerThread(void) { Close(); } bool CPathServerThread::Init(u32 iProcessorIndex) { #ifndef GTA_ENGINE if(m_hThreadHandle) { return false; } #endif if(m_iThreadID) { return false; } m_PathServerThreadParams.m_pPathServerThread = this; m_PathRequestSleepTimer = rage_new sysPerformanceTimer("PathServerThreadSleepTimer"); #if RSG_DURANGO iProcessorIndex = 5; #endif m_iProcessorIndex = iProcessorIndex; //****************************************************************************************************** #ifdef GTA_ENGINE // Create the event which is used to signal that a path has been requested fwPathServer::m_PathRequestSema = sysIpcCreateSema(false); sysIpcPriority iPathfindThreadPriority = static_cast(PRIO_PATHSERVER_THREAD); //************************************************************************************************* // Create the worker thread for the pathserver. We'll create this with the base (normal) priority // since the RAGE thread-priorities system isn't very usable - for example it supports -8 to +8 // priority values, but these don't map properly across platforms. m_iThreadID = sysIpcCreateThread( PathServerThreadFunc, &m_PathServerThreadParams, PATH_SERVER_THREAD_STACKSIZE, iPathfindThreadPriority, "PathServerThread", m_iProcessorIndex, "PathServer" ); #else /////////////////////////////////////////////// NON GTA_ENGINE ////////////////////////////////////////// // Initialize the critical section objects for synchronizing access to dynamic objects & navmesh data InitializeCriticalSection(&m_DynamicObjectsCriticalSection); InitializeCriticalSection(&m_NavMeshDataCriticalSection); InitializeCriticalSection(&m_NavMeshImmediateAccessCriticalSection); // this should be done in the main thread? m_hThreadHandle = CreateThread( NULL, PATH_SERVER_THREAD_STACKSIZE, PathServerThreadFunc, &m_PathServerThreadParams, CREATE_SUSPENDED, &m_iThreadID ); if(!m_hThreadHandle) { return false; } #endif // !GTA_ENGINE Printf("sizeof(CPathServerBinHeap::Node) = %" SIZETFMT "i\n", sizeof(CPathServerBinHeap::Node)); m_PathSearchPriorityQueue = rage_new CPathSearchPriorityQueue(MAX_PATH_STACK_SIZE); m_iTotalMemoryUsed += PATH_SERVER_THREAD_STACKSIZE; m_iTotalMemoryUsed += m_PathSearchPriorityQueue->GetMemoryUsed(); if(!m_iThreadID) { return false; } #if __WIN32 iProcessorIndex; // Shut the compiler up #endif // On the Xenon, we may assign this thread to a specified processor #if __XENON // Index out of range ? Then assign to CPU zero if(iProcessorIndex >= MAXIMUM_PROCESSORS) { iProcessorIndex = 0; } #endif // __XENON // On the PS3 this will probably be more complicated #ifdef GTA_ENGINE #else // Start it running ResumeThread(m_hThreadHandle); #endif return true; } void CPathServerThread::Reset() { m_pCurrentActiveRequest = NULL; m_pCurrentActivePathRequest = NULL; m_pCurrentActiveGridRequest = NULL; m_pCurrentActiveLosRequest = NULL; m_pCurrentActiveAudioRequest = NULL; m_pCurrentActiveFloodFillRequest = NULL; m_pCurrentActiveClearAreaRequest = NULL; m_pCurrentActiveClosestPositionRequest = NULL; m_bQuitNow = false; m_bHasQuit = false; memset(&m_Vars, 0, sizeof(m_Vars)); m_Vars.Init(); m_Vars.m_iNumVisitedPolys = 0; m_iLastNumVisitedPolys = 0; m_iLastRequestType = ENoRequest; m_iNavMeshPolyTimeStamp = 0; m_iNumDeferredClimbDeactivations = 0; m_pFirstDynamicObject = NULL; memset(m_DynamicObjectsStore, 0, sizeof(TDynamicObject) * PATHSERVER_MAX_NUM_DYNAMIC_OBJECTS); CDynamicObjectsContainer::Clear(); #ifdef GTA_ENGINE m_BlockedLadders.Reset(); #endif m_LosVars.m_iTestLosStackNumEntries = 0; } void CPathServerThread::Close(void) { // We should do something here to test that the // thread is still alive. // If the thread is still active, ask it to stop // Keep this thread spinning until "m_bHasQuit" is set // NB : We should have some maximum timeout (2secs?) before // which we kill the thread & quit anyway #ifdef GTA_ENGINE if(m_iThreadID) { if(!m_bHasQuit) { SignalQuit(); if(CPathServer::ms_bUseEventsForRequests) { // Be sure to signal the path-request event, so that the thread wakes up long enough to realise it must quit! sysIpcSignalSema(CPathServer::m_PathRequestSema); } sysIpcWaitThreadExit(m_iThreadID); } m_iThreadID = 0; } if(fwPathServer::m_PathRequestSema) { sysIpcDeleteSema(fwPathServer::m_PathRequestSema); fwPathServer::m_PathRequestSema = 0; } #else if(m_hThreadHandle) { if(!m_bHasQuit) { SignalQuit(); while(!m_bHasQuit) { Sleep(0); } } DeleteCriticalSection(&m_DynamicObjectsCriticalSection); DeleteCriticalSection(&m_NavMeshDataCriticalSection); DeleteCriticalSection(&m_NavMeshImmediateAccessCriticalSection); } #endif if(m_PathSearchPriorityQueue) { delete m_PathSearchPriorityQueue; m_PathSearchPriorityQueue = NULL; } if(m_DynamicObjectsStore) { delete[] m_DynamicObjectsStore; m_DynamicObjectsStore = NULL; } #if !__FINAL if(m_PerfTimer) { delete m_PerfTimer; m_PerfTimer = NULL; } if(m_RequestProcessingOverallTimer) { delete m_RequestProcessingOverallTimer; m_RequestProcessingOverallTimer = NULL; } if(m_PolyTessellationTimer) { delete m_PolyTessellationTimer; m_PolyTessellationTimer = NULL; } #endif } //******************************************************************************** // WaitForNextPathRequest // Waits for the next path request to appear. Depending on the implementation // and platform (eg. PS3 has no thread-event functions) we may either block // this thread until an event is signalled, or just sleep for a while. // Every so often this function will return a NULL, which lets the worker thread // perform various housekeeping duties even if it has no pending path requests //******************************************************************************** CPathServerRequestBase * CPathServerThread::WaitForNextRequest() { // Reset our counters which total up how much work we've done since we last waited. // If we're processing as many requests as are available every time this thread // wakes up, then we can use these counters to throttle off the amount of work done. ms_iNumVisitedPolysInThisBatchOfPathRequests = 0; ms_iNumTessellationsInThisBatchOfPathRequests = 0; // Awake 20 times a second // If there are not requests pending, and it's not time to perform housekeeping - then the thread // will go back to sleep. static dev_s32 iThreadWakeTimeMS = 50; //CPathServer::m_iThreadHousekeepingFrequencyInMillisecs; while(1) { #ifdef GTA_ENGINE // Don't service any requests when the game is paused if(fwTimer::IsGamePaused()) { do { ResetRequestTimers(); // Sleep for 2 secs. sysIpcSleep(2000); } while (fwTimer::IsGamePaused() && !m_bQuitNow); ResetRequestTimers(); } if(m_bQuitNow) return NULL; #endif #ifdef GTA_ENGINE if(CPathServer::ms_bUseEventsForRequests) { sysIpcWaitSemaTimed(CPathServer::m_PathRequestSema, iThreadWakeTimeMS); CPathServerRequestBase * pRequest = GetNextRequest(); // Always return the request, even if NULL. This is because if we recieve a NULL event, we will always // be ready to do a housekeeping task since the sysIpcCheckEvent func will quit out every multiple // of 'm_iThreadHousekeepingFrequencyInMillisecs'. return pRequest; } #else // GTA_ENGINE { Sleep(CPathServer::m_iTimeToSleepWaitingForRequestsMs); CPathServerRequestBase * pRequest = GetNextRequest(); if(pRequest) return pRequest; return NULL; } #endif } // Keep on looping until we get a path request, or it is time to do some thread housekeeping stuff } void CPathServerThread::DoHousekeeping() { #ifdef GTA_ENGINE if(!CPathServer::m_bGameRunning) return; if(m_bForcePerformThreadHousekeeping || fwTimer::GetTimeInMilliseconds() - CPathServer::m_iLastTimeThreadDidHousekeeping >= CPathServer::m_iThreadHousekeepingFrequencyInMillisecs) { CPathServer::m_iLastTimeThreadDidHousekeeping = fwTimer::GetTimeInMilliseconds(); #if !__FINAL m_PerfTimer->Reset(); m_PerfTimer->Start(); #endif // PF_START_TIMEBAR_DETAIL("Remove & Add objects"); // Remove objects which are flagged for deletion, and activate those which are newly added RemoveAndAddObjects(); // Climbs must be removed for climbable objects which have been uprooted; we cannot do this // at the time of the uproot for it may cause a long stall on the main thread whilst we wait // for a safe time to access the loaded navmeshes ProcessDeferredDeactivationOfClimbs(); UpdateAllDynamicObjectsPositions(); #if !__FINAL m_PerfTimer->Stop(); CPathServer::m_fTimeTakenToAddRemoveUpdateObjects = (float) m_PerfTimer->GetTimeMS(); #endif } // PF_START_TIMEBAR_DETAIL("Extract coverpoints"); // We will periodically run a timesliced algorithm which extracts coverpoints from the loaded navmeshes, // and places them into a buffer (m_CoverPointsBuffer). The CCover class can then query this buffer // to create CCoverPoint's without incurring a stall on the main game thread. CPathServer::MaybeExtractCoverPointsFromNavMeshes(); // PF_START_TIMEBAR_DETAIL("Find ped-generation coords"); // We periodically search through navmeshes to find potential positions to create new peds. This can be // quite an expensive process & difficult to timeslice, so we opt to do this as a background task in this // pathfinding thread. const bool bPedGenTimerExpired = m_bForcePerformThreadHousekeeping || (fwTimer::GetTimeInMilliseconds() - CPathServer::m_iLastTimeProcessedPedGeneration) >= CPathServer::m_iProcessPedGenerationFreq; if(bPedGenTimerExpired) { CPathServerAmbientPedGen & ambientPedGen = CPathServerGta::GetAmbientPedGen(); CPathServerOpponentPedGen & opponentPedGen = CPathServerGta::GetOpponentPedGen(); static CPathServer::EPedGenProcessing iLastPedGenMode = CPathServer::EPedGen_OpponentSpawning; // Choose the next pedgen processing; initially this will be the one which we didn't process last time CPathServer::EPedGenProcessing iNextPedGenMode = (iLastPedGenMode==CPathServer::EPedGen_AmbientPeds) ? CPathServer::EPedGen_OpponentSpawning : CPathServer::EPedGen_AmbientPeds; // Now adjust this if we have nothing to do in the current mode? // Or if it is more important that we be processing ambient ped generation (eg. when attempting to instantly fill the ambient population) if(iNextPedGenMode == CPathServer::EPedGen_OpponentSpawning && ( /*!NetworkInterface::IsGameInProgress() ||*/ CPedPopulation::GetInstantFillPopulation() || (opponentPedGen.GetState()!=CPathServerOpponentPedGen::STATE_ACTIVE && ambientPedGen.m_bPedGenNeedsProcessing))) { // Nothing to do for multiplayer ped spawning, but work still to do on ambient peds? Fine, choose to process ambient peds this time iNextPedGenMode = CPathServer::EPedGen_AmbientPeds; } else if(iNextPedGenMode == CPathServer::EPedGen_AmbientPeds && ambientPedGen.m_bPedGenNeedsProcessing == false && opponentPedGen.GetState()==CPathServerOpponentPedGen::STATE_ACTIVE) { // Nothing to do for ambient ped creation, but we have multiplayer spawning in need of attention? iNextPedGenMode = CPathServer::EPedGen_OpponentSpawning; } if(iNextPedGenMode == CPathServer::EPedGen_AmbientPeds && ambientPedGen.m_bPedGenNeedsProcessing) { CPathServer::m_iLastTimeProcessedPedGeneration = fwTimer::GetTimeInMilliseconds(); #if !__FINAL m_PerfTimer->Reset(); m_PerfTimer->Start(); #endif ambientPedGen.FindPedGenCoordsBackgroundTask(); #if !__FINAL m_PerfTimer->Stop(); CPathServer::m_fTimeTakenToDoPedGenInMSecs = (float) m_PerfTimer->GetTimeMS(); #endif } else if(iNextPedGenMode == CPathServer::EPedGen_OpponentSpawning && opponentPedGen.GetState()==CPathServerOpponentPedGen::STATE_ACTIVE) { CPathServer::m_iLastTimeProcessedPedGeneration = fwTimer::GetTimeInMilliseconds(); #if !__FINAL m_PerfTimer->Reset(); m_PerfTimer->Start(); #endif opponentPedGen.FindSpawnCoordsBackgroundTask(); #if !__FINAL m_PerfTimer->Stop(); CPathServer::m_fTimeTakenToDoPedGenInMSecs = (float) m_PerfTimer->GetTimeMS(); #endif } iLastPedGenMode = iNextPedGenMode; } // PF_POP_TIMEBAR_DETAIL(); // For some reason the Timerbars code does not properly end PIX events when popping a timer, // so do it manually - I don't want my main path-requests section to be nested within the // 'housekeeping' section when I do a profile! #if __XENON && !__FINAL // PIXEnd(); #endif #endif // GTA_ENGINE m_bForcePerformThreadHousekeeping = false; } void CPathServerThread::AddDeferredDisableClimbAtPosition(const Vector3 & vPos, const bool bNavmeshClimb, const bool bClimbableObject) { s32 iType = 0; if(bNavmeshClimb && bClimbableObject) iType = TDeferredClimbDeactivation::EBoth; else if(bNavmeshClimb) iType = TDeferredClimbDeactivation::ENavMeshClimb; else if(bClimbableObject) iType = TDeferredClimbDeactivation::EClimbableObject; else { Assertf(false, "At least one type of climb must be specified"); return; } Assertf(m_iNumDeferredClimbDeactivations < MAX_DEFERRED_CLIMB_DEACTIVATIONS, "Too many climbs marked for deactivation"); if(m_iNumDeferredClimbDeactivations < MAX_DEFERRED_CLIMB_DEACTIVATIONS) { m_DeferredClimbDeactivations[m_iNumDeferredClimbDeactivations].type = iType; m_DeferredClimbDeactivations[m_iNumDeferredClimbDeactivations].xyz[0] = vPos.x; m_DeferredClimbDeactivations[m_iNumDeferredClimbDeactivations].xyz[1] = vPos.y; m_DeferredClimbDeactivations[m_iNumDeferredClimbDeactivations].xyz[2] = vPos.z; m_iNumDeferredClimbDeactivations++; } } void CPathServerThread::ProcessDeferredDeactivationOfClimbs() { #ifdef GTA_ENGINE for(s32 c=0; cm_bSlotEmpty; const u32 iDeltaTime = fwTimer::GetTimeInMilliseconds() - pReq->m_iTimeRequestIssued; if(!bSlotEmpty && pReq->m_bComplete && iDeltaTime > CPathServerRequestBase::ms_iRequestTimeoutValInMillisecs) { #if AI_OPTIMISATIONS_OFF Assertf(false, "NOTE - path request 0x%p (context 0x%p) has been waiting for %i ms to be retrieved - it will now be cancelled.\n", pReq, pReq->m_pContext, CPathServerRequestBase::ms_iRequestTimeoutValInMillisecs); Assert(pReq->m_hHandle != PATH_HANDLE_NULL); #if TRACK_LEAKED_PATHS for(int t=0; tGetSize(); t++) { CTask * pTask = CTask::_ms_pPool->GetSlot(t); if(pTask) { if(pTask->GetTaskType()==CTaskTypes::TASK_MOVE_FOLLOW_NAVMESH) { CTaskMoveFollowNavMesh * pNavTask = (CTaskMoveFollowNavMesh*)pTask; if(pReq->m_hHandle == pNavTask->GetPathHandle()) { AssertMsg(false, "Found leaked path task (follow navmesh)."); } } if(pTask->GetTaskType()==CTaskTypes::TASK_MOVE_WANDER) { CTaskMoveWander * pWanderTask = (CTaskMoveWander*)pTask; if(pReq->m_hHandle == pWanderTask->GetPathHandle()) { AssertMsg(false, "Found leaked path task (wander)."); } } } } #endif // TRACK_LEAKED_PATHS #endif // AI_OPTIMISATIONS_OFF if(pReq->m_hHandle != PATH_HANDLE_NULL) CPathServer::CancelRequest(pReq->m_hHandle); pReq->m_bSlotEmpty = true; } } for(i=0; im_bSlotEmpty; const u32 iDeltaTime = fwTimer::GetTimeInMilliseconds() - pReq->m_iTimeRequestIssued; if(!bSlotEmpty && pReq->m_bComplete && iDeltaTime > CPathServerRequestBase::ms_iRequestTimeoutValInMillisecs) { #if AI_OPTIMISATIONS_OFF Assertf(false, "NOTE - grid request 0x%p (context 0x%p) has been waiting for %i ms to be retrieved - it will now be cancelled.\n", pReq, pReq->m_pContext, CPathServerRequestBase::ms_iRequestTimeoutValInMillisecs); #endif if(pReq->m_hHandle != PATH_HANDLE_NULL) CPathServer::CancelRequest(pReq->m_hHandle); pReq->m_bSlotEmpty = true; } } for(i=0; im_bSlotEmpty; const u32 iDeltaTime = fwTimer::GetTimeInMilliseconds() - pReq->m_iTimeRequestIssued; if(!bSlotEmpty && pReq->m_bComplete && iDeltaTime > CPathServerRequestBase::ms_iRequestTimeoutValInMillisecs) { #if AI_OPTIMISATIONS_OFF Assertf(false, "NOTE - LOS request 0x%p (context 0x%p) has been waiting for %i ms to be retrieved - it will now be cancelled.\n", pReq, pReq->m_pContext, CPathServerRequestBase::ms_iRequestTimeoutValInMillisecs); #endif if(pReq->m_hHandle != PATH_HANDLE_NULL) CPathServer::CancelRequest(pReq->m_hHandle); pReq->m_bSlotEmpty = true; } } for(i=0; im_bSlotEmpty; const u32 iDeltaTime = fwTimer::GetTimeInMilliseconds() - pReq->m_iTimeRequestIssued; if(!bSlotEmpty && pReq->m_bComplete && iDeltaTime > CPathServerRequestBase::ms_iRequestTimeoutValInMillisecs) { #if NAVMESH_OPTIMISATIONS_OFF Assertf(false, "NOTE - audio request 0x%p (context 0x%p) has been waiting for %i ms to be retrieved - it will now be cancelled.\n", pReq, pReq->m_pContext, CPathServerRequestBase::ms_iRequestTimeoutValInMillisecs); #endif if(pReq->m_hHandle != PATH_HANDLE_NULL) CPathServer::CancelRequest(pReq->m_hHandle); pReq->m_bSlotEmpty = true; } } for(i=0; im_bSlotEmpty; const u32 iDeltaTime = fwTimer::GetTimeInMilliseconds() - pReq->m_iTimeRequestIssued; if(!bSlotEmpty && pReq->m_bComplete && iDeltaTime > CPathServerRequestBase::ms_iRequestTimeoutValInMillisecs) { #if AI_OPTIMISATIONS_OFF Assertf(false, "NOTE - floodfill request 0x%p (context 0x%p) has been waiting for %i ms to be retrieved - it will now be cancelled.\n", pReq, pReq->m_pContext, CPathServerRequestBase::ms_iRequestTimeoutValInMillisecs); #endif if(pReq->m_hHandle != PATH_HANDLE_NULL) CPathServer::CancelRequest(pReq->m_hHandle); pReq->m_bSlotEmpty = true; } } for(i=0; im_bSlotEmpty; const u32 iDeltaTime = fwTimer::GetTimeInMilliseconds() - pReq->m_iTimeRequestIssued; if(!bSlotEmpty && pReq->m_bComplete && iDeltaTime > CPathServerRequestBase::ms_iRequestTimeoutValInMillisecs) { #if AI_OPTIMISATIONS_OFF Assertf(false, "NOTE - ClearArea request 0x%p (context 0x%p) has been waiting for %i ms to be retrieved - it will now be cancelled.\n", pReq, pReq->m_pContext, CPathServerRequestBase::ms_iRequestTimeoutValInMillisecs); #endif if(pReq->m_hHandle != PATH_HANDLE_NULL) CPathServer::CancelRequest(pReq->m_hHandle); pReq->m_bSlotEmpty = true; } } for(i=0; im_bSlotEmpty; const u32 iDeltaTime = fwTimer::GetTimeInMilliseconds() - pReq->m_iTimeRequestIssued; if(!bSlotEmpty && pReq->m_bComplete && iDeltaTime > CPathServerRequestBase::ms_iRequestTimeoutValInMillisecs) { #if AI_OPTIMISATIONS_OFF Assertf(false, "NOTE - ClosestPosition request 0x%p (context 0x%p) has been waiting for %i ms to be retrieved - it will now be cancelled.\n", pReq, pReq->m_pContext, CPathServerRequestBase::ms_iRequestTimeoutValInMillisecs); #endif if(pReq->m_hHandle != PATH_HANDLE_NULL) CPathServer::CancelRequest(pReq->m_hHandle); pReq->m_bSlotEmpty = true; } } UNLOCK_REQUESTS #endif // __CHECK_FOR_STALE_PATH_REQUESTS #endif } void CPathServerThread::Run() { USE_MEMBUCKET(MEMBUCKET_GAMEPLAY); while(!m_bQuitNow) { TELEMETRY_SET_THREAD_NAME(m_iThreadID, "PathServer"); #ifdef GTA_ENGINE // PF_FRAMEINIT_TIMEBARS(); // PF_START_TIMEBAR_IDLE("Idle"); #endif #ifdef GTA_ENGINE // If the game is loading/restarting then don't run this thread while( (!CPathServer::m_bGameRunning || !CPathServer::ms_bGameInSession) && !m_bQuitNow) { sysIpcSleep(1000); } #endif CPathServerRequestBase * pRequest = NULL; if(!m_bForcePerformThreadHousekeeping) { if(CPathServer::ms_bProcessAllPendingPathsAtOnce) { pRequest = GetNextRequest(); if(!pRequest) pRequest = WaitForNextRequest(); } else { pRequest = WaitForNextRequest(); } } #ifdef GTA_ENGINE // Disable defragging whilst pathserver thread is active if(CPathServer::GetNavMeshStore(kNavDomainRegular)) CPathServer::GetNavMeshStore(kNavDomainRegular)->SetDefragmentCopyBlocked(true); if(CPathServer::GetNavMeshStore(kNavDomainHeightMeshes)) CPathServer::GetNavMeshStore(kNavDomainHeightMeshes)->SetDefragmentCopyBlocked(true); //------------------------------------------------------------------------------------------- // Under certain conditions we will wish to stall the pathserver thread in a safe condition: // 1) If the pathserver is waiting to perform RequestAndEvict from the main thread // 2) If we have scripted blocking objects waiting to be added while( sysInterlockedAnd(&CPathServer::ms_iBlockRequestsFlags, 0xffffffff) !=0 ) { sysIpcSleep(1); } #endif { // Enter scope, so that navmeshes critical section will be automatically left later #ifdef GTA_ENGINE LOCK_NAVMESH_DATA; #else EnterCriticalSection(&m_NavMeshDataCriticalSection); #endif fwPathServer::m_bPathServerThreadIsActive = true; //---------------------------------------------------------------------------------- // Periodically do housekeeping tasks - switching cpu, extracting coverpoints, etc DoHousekeeping(); //---------------------- // Service requests if(pRequest && CPathServer::m_bGameRunning) { m_pCurrentActiveRequest = NULL; m_pCurrentActivePathRequest = NULL; m_pCurrentActiveGridRequest = NULL; m_pCurrentActiveLosRequest = NULL; m_pCurrentActiveAudioRequest = NULL; m_pCurrentActiveFloodFillRequest = NULL; m_pCurrentActiveClearAreaRequest = NULL; m_pCurrentActiveClosestPositionRequest = NULL; #if !__FINAL m_RequestProcessingOverallTimer->Reset(); m_RequestProcessingOverallTimer->Start(); pRequest->m_iNumTimesSlept = 0; #endif //******************************************************************* // See what type of path request this is. Each request should be // handled in it's own Process...() function.. //******************************************************************* pRequest->m_iCompletionCode = PATH_NOT_FOUND; // Grid request switch(pRequest->m_iType) { //--------------------------------------------------------- // Process 'CGridRequest' type requests // UNUSED case EGrid: { Assertf(false, "request type not in use"); CGridRequest * pGridRequest = (CGridRequest*) pRequest; m_pCurrentActiveRequest = pGridRequest; m_pCurrentActiveGridRequest = pGridRequest; SampleWalkableGrid(pGridRequest); break; } //--------------------------------------------------------- // Process 'CPathRequest' type requests case EPath: { CPathRequest * pPathRequest = (CPathRequest*) pRequest; #if !__FINAL #ifdef GTA_ENGINE pPathRequest->m_iFrameRequestStarted = fwTimer::GetFrameCount(); #endif #endif Assert(fwPathServer::GetCurrentNumTessellationPolys() == 0); PIXBeginCN(0, Color_purple.GetColor(), "ProcessPathRequest 0x%x", pRequest->m_hHandle); #if __TRACE_PATH_REQUESTS_ON_XENON if(CPathServer::ms_bUseXTraceOnTheNextPathRequest) { XTraceStartRecording( "e:\\PathRequestTrace.bin" ); } #endif m_pCurrentActiveRequest = pPathRequest; m_pCurrentActivePathRequest = pPathRequest; bool bOk = true; if(pPathRequest->m_iCompletionCode == PATH_CANCELLED) { bOk = false; } else { bOk = ProcessPathRequest(pPathRequest); } m_pCurrentActivePathRequest = NULL; // If not ok, then the error code will have been set within ProcessPathRequest() if(!bOk) { } else { if(!pPathRequest->m_iNumPoints) { // If no points, then set a special completion code pPathRequest->m_iCompletionCode = PATH_NO_POINTS; } else { // Path was found, so fill in the completion data // Note that the "RefinePathAndCreateNodes()" function will have // already created the waypoints, etc.. pPathRequest->m_iCompletionCode = PATH_FOUND; } } { sysCriticalSection critSec(pPathRequest->m_CriticalSectionToken); pPathRequest->m_bRequestActive = false; // If the request was interrupted then don't mark it as complete. if(pPathRequest->m_iCompletionCode == PATH_ABORTED_DUE_TO_ANOTHER_THREAD) { pPathRequest->m_bRequestPending = true; } else { pPathRequest->m_bComplete = true; #if !__FINAL #ifdef GTA_ENGINE pPathRequest->m_iFrameRequestCompleted = fwTimer::GetFrameCount(); #endif #endif } pPathRequest->m_bWaitingToAbort = false; } // Detessellate polys if(CPathServer::ms_bDoPolyUnTessellation) { #if !__FINAL m_PerfTimer->Reset(); m_PerfTimer->Start(); #endif //-------------------------------------------------------------------------- // Remove all polygon tessellations, and return navmeshes to original state fwPathServer::DetessellateAllPolys(); #if !__FINAL m_PerfTimer->Stop(); pPathRequest->m_fMillisecsSpentInDetessellation = (float) m_PerfTimer->GetTimeMS(); #endif } if(pPathRequest->m_bHasAdjustedDynamicObjectsMinMaxForWidth) { Assert(pPathRequest->m_bUseVariableEntityRadius); const float fRadiusDelta = PATHSERVER_PED_RADIUS - pPathRequest->m_fEntityRadius; Assert(fRadiusDelta < 0.0f); CDynamicObjectsContainer::AdjustAllBoundsByAmount(m_Vars.m_PathSearchDistanceMinMax, fRadiusDelta, false); pPathRequest->m_bHasAdjustedDynamicObjectsMinMaxForWidth = false; } #if __TRACE_PATH_REQUESTS_ON_XENON if(CPathServer::ms_bUseXTraceOnTheNextPathRequest) { XTraceStopRecording(); CPathServer::ms_bUseXTraceOnTheNextPathRequest = false; } #endif PIXEnd(); break; } //--------------------------------------------------------- // Process 'CLineOfSightRequest' type requests case ELineOfSight: { CLineOfSightRequest * pLosRequest = (CLineOfSightRequest*) pRequest; PIXBeginCN(0, Color_purple.GetColor(), "ProcessLosRequest 0x%x", pRequest->m_hHandle); m_pCurrentActiveRequest = pLosRequest; m_pCurrentActiveLosRequest = pLosRequest; if(ProcessLineOfSightRequest(pLosRequest)) { // Set completion status to PATH_FOUND. If the request failed, then the status // will have already been set in the Process...() function pLosRequest->m_iCompletionCode = PATH_FOUND; } { sysCriticalSection critSec(pLosRequest->m_CriticalSectionToken); pLosRequest->m_bRequestActive = false; pLosRequest->m_bComplete = true; pLosRequest->m_bWaitingToAbort = false; } #if !__FINAL #ifdef GTA_ENGINE pLosRequest->m_iFrameRequestCompleted = fwTimer::GetFrameCount(); #endif #endif PIXEnd(); break; } //--------------------------------------------------------- // Process 'CAudioRequest' type requests case EAudioProperties: { CAudioRequest * pAudioRequest = (CAudioRequest*) pRequest; PIXBeginCN(0, Color_purple.GetColor(), "ProcessAudioRequest 0x%x", pRequest->m_hHandle); m_pCurrentActiveRequest = pAudioRequest; m_pCurrentActiveAudioRequest = pAudioRequest; if(ProcessAudioPropertiesRequest(pAudioRequest)) { // Set completion status to PATH_FOUND. If the request failed, then the status // will have already been set in the Process...() function pAudioRequest->m_iCompletionCode = PATH_FOUND; } { sysCriticalSection critSec(pAudioRequest->m_CriticalSectionToken); pAudioRequest->m_bRequestActive = false; pAudioRequest->m_bComplete = true; pAudioRequest->m_bWaitingToAbort = false; } #if !__FINAL #ifdef GTA_ENGINE pAudioRequest->m_iFrameRequestCompleted = fwTimer::GetFrameCount(); #endif #endif PIXEnd(); break; } //--------------------------------------------------------- // Process 'CFloodFillRequest' type requests case EFloodFill: { CFloodFillRequest * pFloodFillRequest = (CFloodFillRequest*) pRequest; PIXBeginCN(0, Color_purple.GetColor(), "ProcessFloodFillRequest 0x%x", pRequest->m_hHandle); m_pCurrentActiveRequest = pFloodFillRequest; m_pCurrentActiveFloodFillRequest = pFloodFillRequest; if(ProcessFloodFillRequest(pFloodFillRequest)) { // Set completion status to PATH_FOUND. If the request failed, then the status // will have already been set in the Process...() function pFloodFillRequest->m_iCompletionCode = PATH_FOUND; } { sysCriticalSection critSec(pFloodFillRequest->m_CriticalSectionToken); pFloodFillRequest->m_bRequestActive = false; // If the request was interrupted then don't mark it as complete. if(pFloodFillRequest->m_iCompletionCode == PATH_ABORTED_DUE_TO_ANOTHER_THREAD) { pFloodFillRequest->m_bRequestPending = true; } else { pFloodFillRequest->m_bComplete = true; } pFloodFillRequest->m_bWaitingToAbort = false; } #if !__FINAL #ifdef GTA_ENGINE pFloodFillRequest->m_iFrameRequestCompleted = fwTimer::GetFrameCount(); #endif #endif PIXEnd(); break; } //--------------------------------------------------------- // Process 'CClearAreaRequest' type requests case EClearArea: { CClearAreaRequest * pClearAreaRequest = (CClearAreaRequest*) pRequest; PIXBeginCN(0, Color_purple.GetColor(), "ProcessClearAreaRequest 0x%x", pRequest->m_hHandle); m_pCurrentActiveRequest = pClearAreaRequest; m_pCurrentActiveClearAreaRequest = pClearAreaRequest; if(ProcessClearAreaRequest(pClearAreaRequest)) { // Set completion status to PATH_FOUND. If the request failed, then the status // will have already been set in the Process...() function pClearAreaRequest->m_iCompletionCode = PATH_FOUND; } { sysCriticalSection critSec(pClearAreaRequest->m_CriticalSectionToken); pClearAreaRequest->m_bRequestActive = false; // If the request was interrupted then don't mark it as complete. if(pClearAreaRequest->m_iCompletionCode == PATH_ABORTED_DUE_TO_ANOTHER_THREAD) { pClearAreaRequest->m_bRequestPending = true; } else { pClearAreaRequest->m_bComplete = true; } pClearAreaRequest->m_bWaitingToAbort = false; } #if !__FINAL #ifdef GTA_ENGINE pClearAreaRequest->m_iFrameRequestCompleted = fwTimer::GetFrameCount(); #endif #endif PIXEnd(); break; } //--------------------------------------------------------- // Process 'CClosestPositionRequest' type requests case EClosestPosition: { CClosestPositionRequest * pClosestPosRequest = (CClosestPositionRequest*) pRequest; PIXBeginCN(0, Color_purple.GetColor(), "CClosestPositionRequest 0x%x", pRequest->m_hHandle); m_pCurrentActiveRequest = pClosestPosRequest; m_pCurrentActiveClosestPositionRequest = pClosestPosRequest; if(ProcessClosestPositionRequest(pClosestPosRequest)) { // Set completion status to PATH_FOUND. If the request failed, then the status // will have already been set in the Process...() function pClosestPosRequest->m_iCompletionCode = PATH_FOUND; } { sysCriticalSection critSec(pClosestPosRequest->m_CriticalSectionToken); pClosestPosRequest->m_bRequestActive = false; // If the request was interrupted then don't mark it as complete. if(pClosestPosRequest->m_iCompletionCode == PATH_ABORTED_DUE_TO_ANOTHER_THREAD) { pClosestPosRequest->m_bRequestPending = true; } else { pClosestPosRequest->m_bComplete = true; } pClosestPosRequest->m_bWaitingToAbort = false; } #if !__FINAL #ifdef GTA_ENGINE pClosestPosRequest->m_iFrameRequestCompleted = fwTimer::GetFrameCount(); #endif #endif PIXEnd(); break; } default: { break; } } #ifdef GTA_ENGINE // PF_START_TIMEBAR_DETAIL("End Process Requests"); #endif #if !__FINAL m_RequestProcessingOverallTimer->Stop(); pRequest->m_fTotalProcessingTimeInMillisecs = (float) m_RequestProcessingOverallTimer->GetTimeMS(); CPathServer::m_fRunningTotalOfTimeOnPathRequests += pRequest->m_fTotalProcessingTimeInMillisecs; #endif m_pCurrentActiveRequest = NULL; m_pCurrentActivePathRequest = NULL; m_pCurrentActiveGridRequest = NULL; m_pCurrentActiveLosRequest = NULL; m_pCurrentActiveAudioRequest = NULL; m_pCurrentActiveFloodFillRequest = NULL; m_pCurrentActiveClearAreaRequest = NULL; m_pCurrentActiveClosestPositionRequest = NULL; // Leave the navmeshes critical section #ifndef GTA_ENGINE LeaveCriticalSection(&m_NavMeshDataCriticalSection); #endif } //------------------------------------------------------------------------------------- // If the processing of a request has been forced to abort by the streaming system, // then we must restore this thread's priority to its normal level, as it might have // been boosted so that it took immediate control. // We then need to yield, since now that we have released the critical section on the // navmeshes - the main thread can continue to process the streaming. if(CPathServer::m_bForceAbortCurrentRequest) { if(pRequest) pRequest->m_bWasAborted = true; CPathServer::m_bForceAbortCurrentRequest = false; fwPathServer::m_bPathServerThreadIsActive = false; sysIpcYield(0); } //------------------------------------------------------------------------------------- // If the previous request caused either of the the internal timestamps to overflow // then ensure all timestamps in all navmesh polygons are reset. // This process was moved out of IncTimeStamp() to avoid thread locks when this this // thread and another are trying to lock the navmesh stores and the navmesh data in // reverse order. This couldn't be solved without locking the navmesh data from the // main thread during RequestAndEvict, which would have caused a stall. // I've safeguarded against an overflow causing rare pathfinding errors when touching // polygons with an old timestamp, by lowering the max timestamp value to if(m_bHandleTimestampOverflow) { HandleTimeStampOverFlow(); m_bHandleTimestampOverflow = false; } if(m_bHandleAStarTimestampOverflow) { HandleAStarTimeStampOverFlow(); m_bHandleAStarTimestampOverflow = false; } } fwPathServer::m_bPathServerThreadIsActive = false; #ifdef GTA_ENGINE // Enable defragging again if(CPathServer::GetNavMeshStore(kNavDomainRegular)) CPathServer::GetNavMeshStore(kNavDomainRegular)->SetDefragmentCopyBlocked(false); if(CPathServer::GetNavMeshStore(kNavDomainHeightMeshes)) CPathServer::GetNavMeshStore(kNavDomainHeightMeshes)->SetDefragmentCopyBlocked(false); #endif } #ifndef GTA_ENGINE LeaveCriticalSection(&m_NavMeshDataCriticalSection); m_hThreadHandle = 0; #endif m_iThreadID = 0; m_bHasQuit = true; } s32 CPathServerThread::GetNumPendingRequests() const { s32 iNum=0; int r; for(r=0; rm_bRequestPending && pPathRequest->m_hHandle != 0) { iHighestHandle = Max(iHighestHandle, pPathRequest->m_hHandle); } } float fHighestPriority = -1.0f; for(i=0; im_bRequestPending && pPathRequest->m_hHandle != 0) { float fPriority = (float)(iHighestHandle - pPathRequest->m_hHandle); if(pPathRequest->m_bHighPrioRoute) fPriority *= 2.0f; if(pPathRequest->m_bMissionPed || pPathRequest->m_bScriptedRoute) fPriority *= 2.0f; if(fPriority > fHighestPriority) { iIndex = i; fHighestPriority = fPriority; } } } if(iIndex == -1) { GTA_ENGINE_ONLY(UNLOCK_REQUESTS) return NULL; } CPathServer::m_PathRequests[iIndex].m_bRequestPending = false; CPathServer::m_PathRequests[iIndex].m_bRequestActive = true; GTA_ENGINE_ONLY(UNLOCK_REQUESTS) return &CPathServer::m_PathRequests[iIndex]; } //**************************************************** // Get the next grid request to process. // Choose the one with the lowest non-zero handle //**************************************************** CGridRequest * CPathServerThread::GetNextGridRequest(void) { u32 iLowestHandle = 0xFFFFFFFF; s32 iIndex = -1; GTA_ENGINE_ONLY(LOCK_REQUESTS) int i; for(i=0; im_bRequestPending && pGridRequest->m_hHandle != 0 && pGridRequest->m_hHandle < iLowestHandle) { iLowestHandle = pGridRequest->m_hHandle; iIndex = i; } } if(iIndex == -1) { GTA_ENGINE_ONLY(UNLOCK_REQUESTS) return NULL; } CPathServer::m_GridRequests[iIndex].m_bRequestPending = false; CPathServer::m_GridRequests[iIndex].m_bRequestActive = true; GTA_ENGINE_ONLY(UNLOCK_REQUESTS) return &CPathServer::m_GridRequests[iIndex]; } //**************************************************** // Get the next line-of-sight request to process. // Choose the one with the lowest non-zero handle //**************************************************** CLineOfSightRequest * CPathServerThread::GetNextLosRequest(void) { u32 iLowestHandle = 0xFFFFFFFF; s32 iIndex = -1; GTA_ENGINE_ONLY(LOCK_REQUESTS) int i; for(i=0; im_bRequestPending && pLosRequest->m_hHandle != 0 && pLosRequest->m_hHandle < iLowestHandle) { iLowestHandle = pLosRequest->m_hHandle; iIndex = i; } } if(iIndex == -1) { GTA_ENGINE_ONLY(UNLOCK_REQUESTS) return NULL; } CPathServer::m_LineOfSightRequests[iIndex].m_bRequestPending = false; CPathServer::m_LineOfSightRequests[iIndex].m_bRequestActive = true; GTA_ENGINE_ONLY(UNLOCK_REQUESTS) return &CPathServer::m_LineOfSightRequests[iIndex]; } //**************************************************** // Get the next audio-properties request to process. // Choose the one with the lowest non-zero handle //**************************************************** CAudioRequest * CPathServerThread::GetNextAudioPropertiesRequest() { u32 iLowestHandle = 0xFFFFFFFF; u32 iLowestHandlePriorityRequest = 0xFFFFFFFF; s32 iIndex = -1; s32 iIndexPriorityRequest = -1; GTA_ENGINE_ONLY(LOCK_REQUESTS) int i; for(i=0; im_bRequestPending && pAudioRequest->m_hHandle != 0) { // Find the lowest request if(pAudioRequest->m_hHandle < iLowestHandle) { iLowestHandle = pAudioRequest->m_hHandle; iIndex = i; } // Find the lowest priority request if(pAudioRequest->m_bPriorityRequest && pAudioRequest->m_hHandle < iLowestHandlePriorityRequest) { iLowestHandlePriorityRequest = pAudioRequest->m_hHandle; iIndexPriorityRequest = i; } } } // No pending requests found? if(iIndex == -1 && iIndexPriorityRequest == -1) { GTA_ENGINE_ONLY(UNLOCK_REQUESTS) return NULL; } // Deal with any priority request we may have found if(iIndexPriorityRequest != -1) { CPathServer::m_AudioRequests[iIndexPriorityRequest].m_bRequestPending = false; CPathServer::m_AudioRequests[iIndexPriorityRequest].m_bRequestActive = true; GTA_ENGINE_ONLY(UNLOCK_REQUESTS) return &CPathServer::m_AudioRequests[iIndexPriorityRequest]; } // Otherwise it was a regular request else { CPathServer::m_AudioRequests[iIndex].m_bRequestPending = false; CPathServer::m_AudioRequests[iIndex].m_bRequestActive = true; GTA_ENGINE_ONLY(UNLOCK_REQUESTS) return &CPathServer::m_AudioRequests[iIndex]; } } //**************************************************** // Get the next flood-fill request to process. // Choose the one with the lowest non-zero handle //**************************************************** CFloodFillRequest * CPathServerThread::GetNextFloodFillRequest(void) { u32 iLowestHandle = 0xFFFFFFFF; s32 iIndex = -1; GTA_ENGINE_ONLY(LOCK_REQUESTS) int i; for(i=0; im_bRequestPending && pFloodFillRequest->m_hHandle != 0 && pFloodFillRequest->m_hHandle < iLowestHandle) { iLowestHandle = pFloodFillRequest->m_hHandle; iIndex = i; } } if(iIndex == -1) { GTA_ENGINE_ONLY(UNLOCK_REQUESTS) return NULL; } CPathServer::m_FloodFillRequests[iIndex].m_bRequestPending = false; CPathServer::m_FloodFillRequests[iIndex].m_bRequestActive = true; GTA_ENGINE_ONLY(UNLOCK_REQUESTS) return &CPathServer::m_FloodFillRequests[iIndex]; } //**************************************************** // Get the next clear-area request to process. // Choose the one with the lowest non-zero handle //**************************************************** CClearAreaRequest * CPathServerThread::GetNextClearAreaRequest(void) { u32 iLowestHandle = 0xFFFFFFFF; s32 iIndex = -1; GTA_ENGINE_ONLY(LOCK_REQUESTS) int i; for(i=0; im_bRequestPending && pClearAreaRequest->m_hHandle != 0 && pClearAreaRequest->m_hHandle < iLowestHandle) { iLowestHandle = pClearAreaRequest->m_hHandle; iIndex = i; } } if(iIndex == -1) { GTA_ENGINE_ONLY(UNLOCK_REQUESTS) return NULL; } CPathServer::m_ClearAreaRequests[iIndex].m_bRequestPending = false; CPathServer::m_ClearAreaRequests[iIndex].m_bRequestActive = true; GTA_ENGINE_ONLY(UNLOCK_REQUESTS) return &CPathServer::m_ClearAreaRequests[iIndex]; } //**************************************************** // Get the next closest-position request to process. // Choose the one with the lowest non-zero handle //**************************************************** CClosestPositionRequest * CPathServerThread::GetNextClosestPositionRequest(void) { u32 iLowestHandle = 0xFFFFFFFF; s32 iIndex = -1; GTA_ENGINE_ONLY(LOCK_REQUESTS) int i; for(i=0; im_bRequestPending && pClosestPositionRequest->m_hHandle != 0 && pClosestPositionRequest->m_hHandle < iLowestHandle) { iLowestHandle = pClosestPositionRequest->m_hHandle; iIndex = i; } } if(iIndex == -1) { GTA_ENGINE_ONLY(UNLOCK_REQUESTS) return NULL; } CPathServer::m_ClosestPositionRequests[iIndex].m_bRequestPending = false; CPathServer::m_ClosestPositionRequests[iIndex].m_bRequestActive = true; GTA_ENGINE_ONLY(UNLOCK_REQUESTS) return &CPathServer::m_ClosestPositionRequests[iIndex]; } TNavMeshAndPoly CPathServerThread::GetClosestPolyBelowPoint(aiNavDomain domain, const Vector3 & vPos, float fMaxRadius, float fHeightAbove, float fHeightBelow, bool bOnlyOnPavement, bool bClosestToCentre) { TNavMeshAndPoly meshAndPoly; meshAndPoly.Reset(); // adjPoly.SetNavMeshIndex(NAVMESH_NAVMESH_INDEX_NONE); // adjPoly.SetPolyIndex(NAVMESH_POLY_INDEX_NONE); Vector3 vMin = vPos - Vector3(fMaxRadius, fMaxRadius, fHeightAbove); // below (this needs to account for when standing on walls, etc) Vector3 vMax = vPos + Vector3(fMaxRadius, fMaxRadius, fHeightBelow); // above // There may be up to 4 CNavMeshes which we need to examine, in the case // that we are straddling the corner of four meshes. // For now however, we'll just use the mesh directly below vPos. // NB : Improve this to query adjacent nav-meshes if needed. u32 iMeshIndex = CPathServer::GetNavMeshIndexFromPosition(vPos, domain); if(iMeshIndex == NAVMESH_NAVMESH_INDEX_NONE) { return meshAndPoly; } CNavMesh * pNavMesh = CPathServer::GetNavMeshFromIndex(iMeshIndex, domain); if(!pNavMesh) { return meshAndPoly; } u32 iPolyIndex = pNavMesh->GetPolyMostlyWithinVolume(vMin, vMax, bOnlyOnPavement, bClosestToCentre); if(iPolyIndex == NAVMESH_POLY_INDEX_NONE) { return meshAndPoly; } meshAndPoly.m_iNavMeshIndex = iMeshIndex; meshAndPoly.m_iPolyIndex = iPolyIndex; return meshAndPoly; } TNavMeshAndPoly CPathServerThread::GetClosestPolyBelowPointInMultipleNavMeshes(aiNavDomain domain, const Vector3 & vPos, float fMaxRadius, float fHeightAbove, float fHeightBelow, bool bOnlyOnPavement, bool bClosestToCentre) { Vector3 vMin = vPos - Vector3(fMaxRadius, fMaxRadius, fHeightAbove); // below (this needs to account for when standing on walls, etc) Vector3 vMax = vPos + Vector3(fMaxRadius, fMaxRadius, fHeightBelow); // above float fNavMeshSize = CPathServer::m_pNavMeshStores[domain]->GetMeshSize(); u32 iNavMeshIndices[5]; iNavMeshIndices[0] = CPathServer::GetNavMeshIndexFromPosition(vPos, domain); iNavMeshIndices[1] = CPathServer::GetNavMeshIndexFromPosition(vPos - Vector3(fNavMeshSize, 0.0f, 0.0f), domain); iNavMeshIndices[2] = CPathServer::GetNavMeshIndexFromPosition(vPos + Vector3(fNavMeshSize, 0.0f, 0.0f), domain); iNavMeshIndices[3] = CPathServer::GetNavMeshIndexFromPosition(vPos - Vector3(0.0f, fNavMeshSize, 0.0f), domain); iNavMeshIndices[4] = CPathServer::GetNavMeshIndexFromPosition(vPos + Vector3(0.0f, fNavMeshSize, 0.0f), domain); CNavMesh::ms_fGetPolyBestOverlap = 0.0f; CNavMesh::ms_iGetPolyBestIndex = NAVMESH_POLY_INDEX_NONE; CNavMesh::ms_bGetPolyOnlyOnPavement = bOnlyOnPavement; CNavMesh::ms_bGetPolyClosestToCentre = bClosestToCentre; CNavMesh::ms_vGetPolyVolumeCentre = (vMin + vMax) * 0.5f; CNavMesh::ms_fGetPolyLeastDistSqr = FLT_MAX; u32 iBestNavMeshIndex = NAVMESH_NAVMESH_INDEX_NONE; for(s32 i=0; i<5; i++) { if(iNavMeshIndices[i] != NAVMESH_NAVMESH_INDEX_NONE) { CNavMesh * pNavMesh = CPathServer::GetNavMeshFromIndex(iNavMeshIndices[i], domain); if(pNavMesh) { CNavMesh::ms_bGetPolyFoundAnyPoly = false; pNavMesh->GetPolyMostlyWithinVolumeR(pNavMesh->GetQuadTree(), vMin, vMax); if(CNavMesh::ms_bGetPolyFoundAnyPoly) { iBestNavMeshIndex = iNavMeshIndices[i]; } } } } TNavMeshAndPoly meshAndPoly; meshAndPoly.m_iNavMeshIndex = iBestNavMeshIndex; meshAndPoly.m_iPolyIndex = CNavMesh::ms_iGetPolyBestIndex; return meshAndPoly; } EPathServerErrorCode CPathServerThread::GetMultipleNavMeshPolysForRouteEndPoints(CPathRequest * pPathRequest, TNavMeshPoly * pStartingPoly, CNavMesh * pNavMesh, const Vector3 & vPos, float fRadius, atArray & navMeshPolys, aiNavDomain domain) { Assert(pStartingPoly); IncTimeStamp(); pStartingPoly->m_TimeStamp = m_iNavMeshPolyTimeStamp; static const float fVerticalDist = 3.0f; Vector3 vMin = vPos - Vector3(fRadius,fRadius,fVerticalDist); Vector3 vMax = vPos + Vector3(fRadius,fRadius,fVerticalDist); m_Vars.m_MultipleNavMeshPolysMinMax.SetFloat(vMin.x, vMin.y, vMin.z, vMax.x, vMax.y, vMax.z); FloodFillToGetRouteEndPolys(pPathRequest, pStartingPoly, pNavMesh, m_Vars.m_MultipleNavMeshPolysMinMax, navMeshPolys, domain); return PATH_NO_ERROR; } void CPathServerThread::FloodFillToGetRouteEndPolys(CPathRequest * pPathRequest, TNavMeshPoly * pStartingPoly, CNavMesh * pNavMesh, const TShortMinMax & regionMinMax, atArray & navMeshPolys, aiNavDomain domain) { static const s32 MAX_NODES = 128; atBinHeap binHeap(MAX_NODES); pStartingPoly->m_TimeStamp = m_iNavMeshPolyTimeStamp; binHeap.Insert(0.0f, pStartingPoly); float fCurrentKey; TNavMeshPoly * pPoly; while( binHeap.ExtractMin(fCurrentKey, pPoly) ) { pNavMesh = CPathServer::GetNavMeshFromIndex(pPoly->GetNavMeshIndex(), domain); if(!pNavMesh) continue; for(u32 p=0; pGetNumVertices(); p++) { const TAdjPoly & adjPoly = pNavMesh->GetAdjacentPoly(pPoly->GetFirstVertexIndex() + p ); const u32 iAdjNavMesh = adjPoly.GetNavMeshIndex(pNavMesh->GetAdjacentMeshes()); if(iAdjNavMesh != NAVMESH_NAVMESH_INDEX_NONE && adjPoly.GetPolyIndex() != NAVMESH_POLY_INDEX_NONE && adjPoly.GetAdjacencyType() == ADJACENCY_TYPE_NORMAL) { CNavMesh * pAdjNavMesh = CPathServer::GetNavMeshFromIndex(iAdjNavMesh, domain); if(pAdjNavMesh) { TNavMeshPoly * pAdjPoly = pAdjNavMesh->GetPoly(adjPoly.GetPolyIndex()); if(pAdjPoly->m_TimeStamp != m_iNavMeshPolyTimeStamp) { pAdjPoly->m_TimeStamp = m_iNavMeshPolyTimeStamp; if(regionMinMax.Intersects(pAdjPoly->m_MinMax)) { if(!pAdjPoly->GetIsDegenerateConnectionPoly() && navMeshPolys.GetCount() < navMeshPolys.GetCapacity()) { // If this poly is suitable as a starting polygon for this route, then add to our list if((pPathRequest->m_bNeverLeavePavements || pPathRequest->m_bPreferPavements) && !pAdjPoly->GetIsPavement()) { } else if(pPathRequest->m_bNeverEnterWater && pAdjPoly->GetIsWater()) { } else if(pPathRequest->m_bNeverLeaveWater && !pAdjPoly->GetIsWater()) { } else { navMeshPolys.Append() = pAdjPoly; } if( binHeap.GetNodeCount() < binHeap.GetMaxSize() ) { binHeap.Insert(fCurrentKey + 1.0f, pAdjPoly); } } } } } } } } } //************************************************************************************************************************************* // fDistanceBelowPointToLook is a value by which to shift vPos downwards if no navmesh poly is found directly beneath the input point. // This is because typically the positions passed into the pathfinder are at waist height. If for example the ped was standing next // to a table which had some navmesh polys on it, then if we didn't adjust the position downwards then we'd pick up the polys on the // table instead of the ones nearby on the floor. // If the fDistanceAbovePointToLook value is not 0.0f, then it will look up FIRST. (for underwater pathfinding). EPathServerErrorCode CPathServerThread::GetNavMeshPolyForRouteEndPoints( const Vector3 & vPos, const TNavMeshAndPoly * pExistingNavMeshAndPoly, CNavMesh *& pNavMesh, TNavMeshPoly *& pPoly, Vector3 * pNewPos, const float fDistanceBelowPointToLook, const float fDistanceAbovePointToLook, const bool bCheckDynamicNavMeshes, const bool bSnapPointToNavMesh, aiNavDomain domain, const float fMaxDistanceToAdjustEndPoint, const Vector3* pvPolySearchDir) { // assuming that the input point is at a ped's origin, then foot position will be a metre below static const float fFootOffset = 1.0f; static const float fRaycastStartOffset = 1.0f; static const Vector3 vRaycastStartOffset = ZAXIS; if(pNewPos) *pNewPos = vPos; u32 iNavMeshIndex; if(pExistingNavMeshAndPoly && pExistingNavMeshAndPoly->m_iNavMeshIndex != NAVMESH_NAVMESH_INDEX_NONE) { iNavMeshIndex = pExistingNavMeshAndPoly->m_iNavMeshIndex; pNavMesh = CPathServer::GetNavMeshFromIndex(iNavMeshIndex, domain); } else { iNavMeshIndex = NAVMESH_NAVMESH_INDEX_NONE; if(bCheckDynamicNavMeshes) { Assert(pNavMesh->GetIsDynamic()); iNavMeshIndex = pNavMesh->GetIndexOfMesh(); } if(iNavMeshIndex == NAVMESH_NAVMESH_INDEX_NONE) { iNavMeshIndex = CPathServer::GetNavMeshIndexFromPosition(vPos, domain); } // Are coordinates outside of the map area ? if(iNavMeshIndex == NAVMESH_NAVMESH_INDEX_NONE) { return PATH_INVALID_COORDINATES; } pNavMesh = CPathServer::GetNavMeshFromIndex(iNavMeshIndex, domain); } // Coordinates are valid, but there's no navmesh loaded here if(!pNavMesh) { return PATH_NAVMESH_NOT_LOADED; } u32 iPolyIndex; Vector3 vIntersectPos; if(pExistingNavMeshAndPoly && pExistingNavMeshAndPoly->m_iPolyIndex != NAVMESH_POLY_INDEX_NONE && pNavMesh->RayIntersectsPoly( Vector3(vPos.x, vPos.y, vPos.z + fRaycastStartOffset), Vector3(vPos.x, vPos.y, vPos.z - fDistanceBelowPointToLook), pNavMesh->GetPoly(pExistingNavMeshAndPoly->m_iPolyIndex), vIntersectPos) ) { iPolyIndex = pExistingNavMeshAndPoly->m_iPolyIndex; if(pNewPos && bSnapPointToNavMesh) pNewPos->z = vIntersectPos.z; } else { iPolyIndex = NAVMESH_POLY_INDEX_NONE; // Optionally look upwards first if(fDistanceAbovePointToLook != 0.0f) { iPolyIndex = pNavMesh->GetPolyAbovePoint(vPos + vRaycastStartOffset, vIntersectPos, fDistanceAbovePointToLook); } // Can we get a polygon precisely underneath this location if(iPolyIndex == NAVMESH_POLY_INDEX_NONE) { iPolyIndex = pNavMesh->GetPolyBelowPoint(vPos + vRaycastStartOffset, vIntersectPos, fDistanceBelowPointToLook); } if(iPolyIndex == NAVMESH_POLY_INDEX_NONE) { // If we didn't find one exactly below, then try again with an approximation. // We'll try to find the closest polygon. Use a position 1m below the input point, to favour the floor polys // over any polys which may be up on low walls, etc. if (fMaxDistanceToAdjustEndPoint > 0.0f) { const TNavMeshIndex iDynamicNavMeshIndex = pNavMesh->GetIsDynamic() ? pNavMesh->GetIndexOfMesh() : NAVMESH_NAVMESH_INDEX_NONE; TNavMeshAndPoly navMeshAndPoly; if(pNewPos) { FindApproxNavMeshPolyForRouteEndPointsAndAdjustPosition(vPos - Vector3(0, 0, fFootOffset), fMaxDistanceToAdjustEndPoint, navMeshAndPoly, false, *pNewPos, iDynamicNavMeshIndex, domain, pvPolySearchDir); } else { FindApproxNavMeshPolyForRouteEndPoints(vPos - Vector3(0, 0, fFootOffset), fMaxDistanceToAdjustEndPoint, navMeshAndPoly, false, iDynamicNavMeshIndex, domain, pvPolySearchDir); } if(navMeshAndPoly.m_iNavMeshIndex == NAVMESH_NAVMESH_INDEX_NONE || navMeshAndPoly.m_iPolyIndex == NAVMESH_POLY_INDEX_NONE) { return PATH_NO_SURFACE_AT_COORDINATES; } pNavMesh = CPathServer::GetNavMeshFromIndex(navMeshAndPoly.m_iNavMeshIndex, domain); iPolyIndex = navMeshAndPoly.m_iPolyIndex; } else { return PATH_NO_SURFACE_AT_COORDINATES; } } else if(pNewPos && bSnapPointToNavMesh) { pNewPos->z = vIntersectPos.z; } } if(iPolyIndex == NAVMESH_POLY_INDEX_NONE) return PATH_NO_SURFACE_AT_COORDINATES; pPoly = pNavMesh->GetPoly(iPolyIndex); if(pPoly->GetIsDisabled()) return PATH_NO_SURFACE_AT_COORDINATES; CPathServerThread::OnFirstVisitingPoly(pPoly); // If this navmesh poly has been tessellated & replaced by a polygon in the // tessellation navmesh, then we should check that navmesh for a candidate poly u32 iLastPolyIndex = NAVMESH_POLY_INDEX_NONE; while(pPoly->TestFlags(NAVMESHPOLY_REPLACED_BY_TESSELLATION)) { pNavMesh = CPathServer::m_pTessellationNavMesh; iPolyIndex = pNavMesh->GetPolyBelowPoint(vPos + Vector3(0, 0, 1.0f), vIntersectPos, fDistanceBelowPointToLook); if(iPolyIndex == NAVMESH_POLY_INDEX_NONE) { if(fMaxDistanceToAdjustEndPoint == 0.0f) { return PATH_NO_SURFACE_AT_COORDINATES; } else { // If we didn't find one exactly below, then try again with an approximation. // We'll try to find the closest polygon. Use a position 1m below the input point, to favour the floor polys // over any polys which may be up on low walls, etc. const TNavMeshIndex iDynamicNavMeshIndex = pNavMesh->GetIsDynamic() ? pNavMesh->GetIndexOfMesh() : NAVMESH_NAVMESH_INDEX_NONE; TNavMeshAndPoly meshAndPoly; if(pNewPos) { FindApproxNavMeshPolyForRouteEndPointsAndAdjustPosition(vPos - Vector3(0, 0, fFootOffset), fMaxDistanceToAdjustEndPoint, meshAndPoly, true, *pNewPos, iDynamicNavMeshIndex, domain); } else { FindApproxNavMeshPolyForRouteEndPoints(vPos - Vector3(0, 0, fFootOffset), fMaxDistanceToAdjustEndPoint, meshAndPoly, true, iDynamicNavMeshIndex, domain); } if(meshAndPoly.m_iNavMeshIndex == NAVMESH_NAVMESH_INDEX_NONE || meshAndPoly.m_iPolyIndex == NAVMESH_POLY_INDEX_NONE) { return PATH_NO_SURFACE_AT_COORDINATES; } pNavMesh = CPathServer::GetNavMeshFromIndex(meshAndPoly.m_iNavMeshIndex, domain); iPolyIndex = meshAndPoly.m_iPolyIndex; // Has the algorithm got stuck finding the same poly over & over ? If so we'll return false.. if(iPolyIndex == iLastPolyIndex && iLastPolyIndex != NAVMESH_POLY_INDEX_NONE) { Assertf(0, "CPathServer : Found an infinite loop in GetNavMeshPolyForRouteEndPoints()..."); return PATH_NO_SURFACE_AT_COORDINATES; } iLastPolyIndex = iPolyIndex; } } else if(pNewPos && bSnapPointToNavMesh) { pNewPos->z = vIntersectPos.z; } pPoly = pNavMesh->GetPoly(iPolyIndex); if(pPoly->GetIsDisabled()) return PATH_NO_SURFACE_AT_COORDINATES; } return PATH_NO_ERROR; } #define MAX_NUM_ENDPOINT_NAVMESHES 8 bool CPathServerThread::FindApproxNavMeshPolyForRouteEndPoints(const Vector3 & vPos, float fRadius, TNavMeshAndPoly & closestPoly, bool bTessellationNavMesh, const TNavMeshIndex iThisDynamicNavMeshOnly, aiNavDomain domain, const Vector3* pvPolySearchDir) { //*********************************************************************************************** // We can have a number of potential navmeshes, if we are situated in the corner of a navmesh // Also we need room to be able to add intersecting dynamic navmeshes. // However, if a valid "iThisDynamicNavMeshOnly" is passed in we will ONLY look within this. u32 iNavMeshes[MAX_NUM_ENDPOINT_NAVMESHES]; s32 iNumNavMeshes = 0; if(iThisDynamicNavMeshOnly != NAVMESH_NAVMESH_INDEX_NONE) { iNavMeshes[0] = iThisDynamicNavMeshOnly; iNumNavMeshes = 1; } else if(bTessellationNavMesh) { iNavMeshes[0] = NAVMESH_INDEX_TESSELLATION; iNumNavMeshes = 1; } else { iNavMeshes[0] = CPathServer::GetNavMeshIndexFromPosition(vPos, domain); iNumNavMeshes = 1; } Vector3 vPolyPts[NAVMESHPOLY_MAX_NUM_VERTICES]; TShortMinMax minMax; minMax.SetFloat(vPos.x - fRadius, vPos.y - fRadius, vPos.z - fRadius, vPos.x + fRadius, vPos.y + fRadius, vPos.z + fRadius); s32 n; u32 p,v; float fDistSqr; float fMinDistSqr = FLT_MAX; float fRadiusSqr = fRadius * fRadius; Vector3 vDiff; closestPoly.Reset(); for(n=0; nGetQuadTree()) { Assert(!(pNavMesh->GetFlags() & NAVMESH_IS_DYNAMIC)); Vector3 vPosOnEdge; TNavMeshPoly * pPoly = pNavMesh->GetClosestNavMeshPolyEdge(vPos, fRadius, vPosOnEdge, NULL, NULL, false, 0, 0, NULL, pvPolySearchDir); if(pPoly) { fDistSqr = (vPosOnEdge - vPos).Mag2(); if(fDistSqr < fMinDistSqr) { fMinDistSqr = fDistSqr; closestPoly.m_iNavMeshIndex = iNavMeshes[n]; closestPoly.m_iPolyIndex = pNavMesh->GetPolyIndex(pPoly); } } } else { for(p=0; pGetNumPolys(); p++) { TNavMeshPoly * pPoly = pNavMesh->GetPoly(p); CPathServerThread::OnFirstVisitingPoly(pPoly); // If this is the tessellation navmesh then we don't want to consider polys which // have been replaced by tessellation. For other navmeshes this is ok, since we will // subsequently proceed to look in the tessellation navmesh for the nearest fragment if(bTessellationNavMesh && pPoly->GetReplacedByTessellation()) continue; if(!minMax.Intersects(pPoly->m_MinMax)) continue; for(v=0; vGetNumVertices(); v++) { pNavMesh->GetVertex( pNavMesh->GetPolyVertexIndex(pPoly, v), vPolyPts[v] ); } s32 lastv = pPoly->GetNumVertices()-1; for(v=0; vGetNumVertices(); v++) { fDistSqr = geomDistances::Distance2SegToPoint(vPolyPts[lastv], vPolyPts[v] - vPolyPts[lastv], vPos); if(fDistSqr < fRadiusSqr && fDistSqr < fMinDistSqr) { fMinDistSqr = fDistSqr; closestPoly.m_iNavMeshIndex = iNavMeshes[n]; closestPoly.m_iPolyIndex = p; } lastv = v; } } } } if(closestPoly.m_iNavMeshIndex != NAVMESH_NAVMESH_INDEX_NONE && closestPoly.m_iPolyIndex != NAVMESH_POLY_INDEX_NONE) { return true; } else { return false; } } bool CPathServerThread::FindApproxNavMeshPolyForRouteEndPointsAndAdjustPosition(const Vector3 & vPos, float fRadius, TNavMeshAndPoly & closestPoly, bool bTessellationNavMesh, Vector3 & vNewPos, const TNavMeshIndex iThisDynamicNavMeshOnly, aiNavDomain domain, const Vector3* pvPolySearchDir) { //*********************************************************************************************** // We can have a number of potential navmeshes, if we are situated in the corner of a navmesh // Also we need room to be able to add intersecting dynamic navmeshes. // However, if a valid "iThisDynamicNavMeshOnly" is passed in we will ONLY look within this. u32 iNavMeshes[MAX_NUM_ENDPOINT_NAVMESHES]; s32 iNumNavMeshes = 0; if(iThisDynamicNavMeshOnly != NAVMESH_NAVMESH_INDEX_NONE) { iNavMeshes[0] = iThisDynamicNavMeshOnly; iNumNavMeshes = 1; } else if(bTessellationNavMesh) { iNavMeshes[0] = NAVMESH_INDEX_TESSELLATION; iNumNavMeshes = 1; } else { iNumNavMeshes = CPathServer::GetNavMeshesIntersectingPosition(vPos, fRadius, &iNavMeshes[0], domain); } Vector3 vPolyPts[NAVMESHPOLY_MAX_NUM_VERTICES]; TShortMinMax minMax; minMax.SetFloat(vPos.x - fRadius, vPos.y - fRadius, vPos.z - fRadius, vPos.x + fRadius, vPos.y + fRadius, vPos.z + fRadius); s32 n; u32 p,v; float fDistSqr; float fMinDistSqr = FLT_MAX; float fRadiusSqr = fRadius * fRadius; Vector3 vDiff; closestPoly.Reset(); for(n=0; nGetQuadTree()) { Assert(!(pNavMesh->GetFlags() & NAVMESH_IS_DYNAMIC)); Vector3 vPosOnEdge; TNavMeshPoly * pPoly = pNavMesh->GetClosestNavMeshPolyEdge(vPos, fRadius, vPosOnEdge, NULL, NULL, false, 0, 0, NULL, pvPolySearchDir); if(pPoly) { fDistSqr = (vPosOnEdge - vPos).Mag2(); if(fDistSqr < fMinDistSqr) { fMinDistSqr = fDistSqr; closestPoly.m_iNavMeshIndex = iNavMeshes[n]; closestPoly.m_iPolyIndex = pNavMesh->GetPolyIndex(pPoly); vNewPos = vPosOnEdge; #if !__FINAL && __BANK if (CPathServer::m_bVisualisePolygonRequests) { grcDebugDraw::Line(vNewPos, vNewPos + ZAXIS * 5.f, Color_green, 10); //Vector3 vPosCenter; //pNavMesh->GetPolyCentroidQuick(pPoly, vPosCenter); //grcDebugDraw::Line(vPosCenter, vPosCenter + ZAXIS * 5.f, Color_red, 10); } #endif } } } else { for(p=0; pGetNumPolys(); p++) { TNavMeshPoly * pPoly = pNavMesh->GetPoly(p); if(pPoly->GetIsDisabled()) continue; CPathServerThread::OnFirstVisitingPoly(pPoly); // If this is the tessellation navmesh then we don't want to consider polys which // have been replaced by tessellation. For other navmeshes this is ok, since we will // subsequently proceed to look in the tessellation navmesh for the nearest fragment if(bTessellationNavMesh && pPoly->GetReplacedByTessellation()) continue; if(!minMax.Intersects(pPoly->m_MinMax)) continue; for(v=0; vGetNumVertices(); v++) { pNavMesh->GetVertex( pNavMesh->GetPolyVertexIndex(pPoly, v), vPolyPts[v] ); } s32 lastv = pPoly->GetNumVertices()-1; for(v=0; vGetNumVertices(); v++) { fDistSqr = geomDistances::Distance2SegToPoint(vPolyPts[lastv], vPolyPts[v] - vPolyPts[lastv], vPos); if(fDistSqr < fRadiusSqr && fDistSqr < fMinDistSqr) { fMinDistSqr = fDistSqr; closestPoly.m_iNavMeshIndex = iNavMeshes[n]; closestPoly.m_iPolyIndex = p; const Vector3 vDiff = vPolyPts[v] - vPolyPts[lastv]; const float fTVal = (vDiff.Mag2() > 0.0f) ? geomTValues::FindTValueSegToPoint(vPolyPts[lastv], vDiff, vPos) : 0.0f; vNewPos = vPolyPts[lastv] + ((vPolyPts[v] - vPolyPts[lastv]) * fTVal); } lastv = v; } } } } if(closestPoly.m_iNavMeshIndex != NAVMESH_NAVMESH_INDEX_NONE && closestPoly.m_iPolyIndex != NAVMESH_POLY_INDEX_NONE) { return true; } else { return false; } } bool CPathServerThread::ProcessLineOfSightRequest(CLineOfSightRequest * pLosRequest) { Assert(pLosRequest->m_iNumPts >= 2); // Have to call this before we process any request which may use dynamic objects UpdateAllDynamicObjectsPositions(); pLosRequest->m_bLineOfSightExists = true; u32 iLosFlags = 0; if(pLosRequest->m_fMaxAngle > 0.0f) { m_Vars.m_fMinSlopeDotProductWithUpVector = rage::Cosf(pLosRequest->m_fMaxAngle); iLosFlags |= CTestNavMeshLosVars::FLAG_NO_LOS_ACROSS_MAXSLOPE_BOUNDARY; } if ( pLosRequest->m_bNoLosAcrossWaterBoundary ) { iLosFlags |= CTestNavMeshLosVars::FLAG_NO_LOS_ACROSS_WATER_BOUNDARY; } const aiNavDomain domain = pLosRequest->GetMeshDataSet(); CNavMesh * pStartNavMesh, * pEndNavMesh; TNavMeshPoly * pStartPoly, * pEndPoly; const EPathServerErrorCode eStartRet = GetNavMeshPolyForRouteEndPoints( pLosRequest->m_vPoints[0], NULL, pStartNavMesh, pStartPoly, NULL, ms_fNormalDistBelowToLookForPoly, 0.0f, false, false, domain, 0.0f ); if(eStartRet != PATH_NO_ERROR) { pLosRequest->m_bLineOfSightExists = false; pLosRequest->m_bRequestActive = false; pLosRequest->m_bComplete = true; pLosRequest->m_iCompletionCode = eStartRet; return false; } // store if we are starting in water or not if ( pLosRequest->m_bStartsInWater ) { if ( !pStartPoly || !pStartPoly->GetIsWater() ) { pLosRequest->m_bLineOfSightExists = false; pLosRequest->m_bRequestActive = false; pLosRequest->m_bComplete = true; pLosRequest->m_iCompletionCode = eStartRet; return false; } } s32 iProgress; for(iProgress=1; iProgressm_iNumPts; iProgress++) { const EPathServerErrorCode eEndRet = GetNavMeshPolyForRouteEndPoints( pLosRequest->m_vPoints[iProgress], NULL, pEndNavMesh, pEndPoly, NULL, ms_fNormalDistBelowToLookForPoly, 0.0f, false, false, domain, 0.0f ); if(eEndRet != PATH_NO_ERROR) { pLosRequest->m_bLineOfSightExists = false; pLosRequest->m_bRequestActive = false; pLosRequest->m_bComplete = true; pLosRequest->m_iCompletionCode = eEndRet; return false; } // NB : ?? Is this really needed ?? pStartPoly->m_PathParentPoly = NULL; pEndPoly->m_PathParentPoly = NULL; const Vector3 & vStartPos = pLosRequest->m_vPoints[iProgress-1]; const Vector3 & vEndPos = pLosRequest->m_vPoints[iProgress]; // Increment timestamp before LOS. IncTimeStamp(); bool bLosExists = TestNavMeshLOS( vStartPos, vEndPos, pEndPoly, pStartPoly, NULL, iLosFlags, domain ); if(pLosRequest->m_bDynamicObjects && bLosExists) { bLosExists = TestDynamicObjectLOS(vStartPos, vEndPos); } pLosRequest->m_bLosResults[iProgress-1] = bLosExists; // This test can be made to quit out the first time that a LOS fails. This will save us from processing the later segments. if(!bLosExists) { pLosRequest->m_bLineOfSightExists = false; if(pLosRequest->m_bQuitAtFirstLosFail) { while(iProgress < pLosRequest->m_iNumPts) { pLosRequest->m_bLosResults[iProgress] = false; iProgress++; } break; } } pStartPoly = pEndPoly; pStartNavMesh = pEndNavMesh; } return true; } // Added these to help avoid possible recursion stack overflow TShortMinMax g_AudioPropertiesMinMax; CAudioRequest * g_pCurrentAudioRequest = NULL; bool CPathServerThread::ProcessAudioPropertiesRequest(CAudioRequest * pAudioRequest) { #if !__FINAL if(CPathServer::m_bDisableAudioRequests) { pAudioRequest->m_iAudioProperties[0] = 0; pAudioRequest->m_fPolyAreas[pAudioRequest->m_iNumAudioProperties] = 1.0f; pAudioRequest->m_vPolyCentroids[0] = pAudioRequest->m_vPosition; pAudioRequest->m_iNumAudioProperties = 1; return true; } #endif CNavMesh * pNavMesh = NULL; TNavMeshPoly * pPoly = NULL; EPathServerErrorCode eRet = PATH_NO_ERROR; const aiNavDomain domain = pAudioRequest->GetMeshDataSet(); // If the issuing entity is being tracked in the navmesh, then its possible that we already know its // position to good degree of accuracy. By using this we can avoid a costly search through navmesh polys. if(pAudioRequest->m_KnownNavmeshPosition.m_iNavMeshIndex != NAVMESH_NAVMESH_INDEX_NONE && pAudioRequest->m_KnownNavmeshPosition.m_iPolyIndex != NAVMESH_POLY_INDEX_NONE) { pNavMesh = CPathServer::GetNavMeshFromIndex(pAudioRequest->m_KnownNavmeshPosition.m_iNavMeshIndex, domain); if(pNavMesh && pAudioRequest->m_KnownNavmeshPosition.m_iPolyIndex < pNavMesh->GetNumPolys()) { pPoly = pNavMesh->GetPoly(pAudioRequest->m_KnownNavmeshPosition.m_iPolyIndex); } } if(!pPoly) { eRet = GetNavMeshPolyForRouteEndPoints( pAudioRequest->m_vPosition, NULL, pNavMesh, pPoly, NULL, ms_fNormalDistBelowToLookForPoly, 0.0f, false, false, domain ); } if( (eRet != PATH_NO_ERROR) || (!pNavMesh) || (!pPoly) || (pPoly->GetNavMeshIndex() != pNavMesh->GetIndexOfMesh())) { pAudioRequest->m_iNumAudioProperties = 0; pAudioRequest->m_bRequestActive = false; pAudioRequest->m_bComplete = true; pAudioRequest->m_iCompletionCode = eRet; return false; } IncAStarTimeStamp(); pPoly->SetTimeStamp(m_iAStarTimeStamp); u8 iAudioFlags = 0; if(pPoly->GetIsSheltered()) iAudioFlags |= CAudioRequest::FLAG_POLYGON_IS_SHELTERED; pAudioRequest->m_iNumAudioProperties = 1; pAudioRequest->m_iAudioProperties[0] = pPoly->GetAudioProperties(); pAudioRequest->m_fPolyAreas[0] = pNavMesh->GetPolyArea(pNavMesh->GetPolyIndex(pPoly)); pAudioRequest->m_iAdditionalFlags[0] = iAudioFlags; pNavMesh->GetPolyCentroidQuick(pPoly, pAudioRequest->m_vPolyCentroids[0]); TShortMinMax minMax; f32 radius = pAudioRequest->m_fRadius; // We behave differently if we have a direction - we do an angle-based search, but with an extra box around the centre, // to catch nearby poly's who's centre's not in line, but who cover the segment we're interested in. if (pAudioRequest->m_vDirection.Mag2() > 0.25f) { radius = CPathServer::m_fAudioVelocityCubeSize; } radius += TShortMinMax::ms_fResolution; minMax.SetFloat( pAudioRequest->m_vPosition.x - radius, pAudioRequest->m_vPosition.y - radius, pAudioRequest->m_vPosition.z - radius, pAudioRequest->m_vPosition.x + radius, pAudioRequest->m_vPosition.y + radius, pAudioRequest->m_vPosition.z + radius ); // See what sort of search we're doing. // No direction and -ve radius means return the one we're on, and all its neighbours - used for static things. TYPE 1. // No direction and a +ve radius means return anything within that radius - used for listener metrics. TYPE 2. // A direction and a radius means return anything within an angle of that direction, up to a distance of that radius. TYPE 3. bool directionPresent = (pAudioRequest->m_vDirection.Mag2() > 0.25f); bool useRadius = (pAudioRequest->m_fRadius > 0.0f); if(directionPresent && !useRadius) { pAudioRequest->m_iAudioReqType = CAudioRequest::DIRECTION_NO_RADIUS; } else if(!directionPresent && useRadius) { pAudioRequest->m_iAudioReqType = CAudioRequest::RADIUS_NO_DIRECTION; } else if(directionPresent && useRadius) { pAudioRequest->m_iAudioReqType = CAudioRequest::DIRECTION_AND_RADIUS; } else { pAudioRequest->m_iAudioReqType = CAudioRequest::NO_DIRECTION_NO_RADIUS; // No more work to do for this audio query type return true; } g_AudioPropertiesMinMax = minMax; g_pCurrentAudioRequest = pAudioRequest; ProcessAudioPropertiesRequestR(pNavMesh, pPoly, 1); g_pCurrentAudioRequest = NULL; return true; } const s32 sMaxNumRecursions = 24; bool CPathServerThread::ProcessAudioPropertiesRequestR(CNavMesh * pNavMesh, TNavMeshPoly * pPoly, const s32 iNumRecursions) { CAudioRequest * pAudioRequest = g_pCurrentAudioRequest; const aiNavDomain domain = pAudioRequest->GetMeshDataSet(); u32 v; for(v=0; vGetNumVertices(); v++) { const TAdjPoly & adjPoly = pNavMesh->GetAdjacentPoly(pPoly->GetFirstVertexIndex() + v); const u32 iAdjNavMesh = adjPoly.GetNavMeshIndex(pNavMesh->GetAdjacentMeshes()); if(iAdjNavMesh==NAVMESH_NAVMESH_INDEX_NONE || adjPoly.GetPolyIndex()==NAVMESH_POLY_INDEX_NONE || adjPoly.GetAdjacencyType()!=ADJACENCY_TYPE_NORMAL) continue; CNavMesh * pAdjNavMesh = CPathServer::GetNavMeshFromIndex(iAdjNavMesh, domain); Vector3 vCentroid; if(pAdjNavMesh) { Assert(pAdjNavMesh->GetIndexOfMesh() == iAdjNavMesh); TNavMeshPoly * pAdjPoly = pAdjNavMesh->GetPoly(adjPoly.GetPolyIndex()); if(pAdjPoly->GetTimeStamp() != m_iAStarTimeStamp) { pAdjPoly->SetTimeStamp(m_iAStarTimeStamp); u8 iAudioFlags = 0; if(pAdjPoly->GetIsSheltered()) iAudioFlags |= CAudioRequest::FLAG_POLYGON_IS_SHELTERED; // TODO : If pAdjNavMesh is a *dynamic* navmesh, then we need to calculate this poly's m_MinMax now.. switch(pAudioRequest->m_iAudioReqType) { case CAudioRequest::DIRECTION_NO_RADIUS: { pAdjNavMesh->GetPolyCentroidQuick(pAdjPoly, vCentroid); pAudioRequest->InsertAudioProperty((vCentroid-pAudioRequest->m_vPosition).Mag2(), pAdjPoly->GetAudioProperties(), vCentroid, pAdjNavMesh->GetPolyArea(pAdjNavMesh->GetPolyIndex(pAdjPoly)), iAudioFlags); break; } case CAudioRequest::RADIUS_NO_DIRECTION: { if (g_AudioPropertiesMinMax.Intersects(pAdjPoly->m_MinMax)) { // We don't store results for wee polys - the worry is we want a decent size radius, but not too many results, // so try ignoring small ones. const f32 polyArea = pAdjNavMesh->GetPolyArea(pAdjNavMesh->GetPolyIndex(pAdjPoly)); if (polyArea >= CPathServer::m_fAudioMinPolySizeForRadiusSearch) { Vector3 vEdgePoint; pAdjNavMesh->GetPolyClosestEdgePointToPoint(pAudioRequest->m_vPosition, pAdjPoly, vEdgePoint); if( (vEdgePoint - pAudioRequest->m_vPosition).Mag2() < pAudioRequest->m_fRadius*pAudioRequest->m_fRadius) { pAdjNavMesh->GetPolyCentroidQuick(pAdjPoly, vCentroid); pAudioRequest->InsertAudioProperty((vEdgePoint-pAudioRequest->m_vPosition).Mag2(), pAdjPoly->GetAudioProperties(), vCentroid, pAdjNavMesh->GetPolyArea(pAdjNavMesh->GetPolyIndex(pAdjPoly)), iAudioFlags); } } bool bContinue = (iNumRecursions >= sMaxNumRecursions) ? false : ProcessAudioPropertiesRequestR(pAdjNavMesh, pAdjPoly, iNumRecursions+1); if(!bContinue) return false; } break; } case CAudioRequest::DIRECTION_AND_RADIUS: { Vector3 polyCentroid; pAdjNavMesh->GetPolyCentroidQuick(pAdjPoly, polyCentroid); Vector3 startPositionToPoly = pAudioRequest->m_vPosition - polyCentroid; startPositionToPoly.NormalizeSafe(); f32 dotProd = startPositionToPoly.Dot(pAudioRequest->m_vDirection); if ((dotProd > CPathServer::m_fAudioVelocityDotProduct) || (dotProd > 0.0f && g_AudioPropertiesMinMax.Intersects(pAdjPoly->m_MinMax))) { pAdjNavMesh->GetPolyCentroidQuick(pAdjPoly, vCentroid); pAudioRequest->InsertAudioProperty((vCentroid-pAudioRequest->m_vPosition).Mag2(), pAdjPoly->GetAudioProperties(), vCentroid, pAdjNavMesh->GetPolyArea(pAdjNavMesh->GetPolyIndex(pAdjPoly)), iAudioFlags); bool bContinue = (iNumRecursions >= sMaxNumRecursions) ? false : ProcessAudioPropertiesRequestR(pAdjNavMesh, pAdjPoly, iNumRecursions+1); if(!bContinue) return false; } break; } default: Assert(false); break; } } } } return true; } bool CPathServerThread::ProcessFloodFillRequest(CFloodFillRequest * pFloodFillRequest) { CNavMesh * pStartNavMesh = NULL; TNavMeshPoly * pStartPoly = NULL; const aiNavDomain domain = pFloodFillRequest->GetMeshDataSet(); const EPathServerErrorCode eRet = GetNavMeshPolyForRouteEndPoints( pFloodFillRequest->m_vStartPos, NULL, pStartNavMesh, pStartPoly, NULL, ms_fNormalDistBelowToLookForPoly, 0.0f, false, false, domain ); if(eRet != PATH_NO_ERROR || !pStartNavMesh || !pStartPoly) { pFloodFillRequest->m_bRequestActive = false; pFloodFillRequest->m_bComplete = true; pFloodFillRequest->m_iCompletionCode = eRet; return false; } Assertf(pStartNavMesh->GetIndexOfMesh() != NAVMESH_INDEX_TESSELLATION, "FloodFill - chose tessellation mesh as start!"); IncAStarTimeStamp(); TShortMinMax minMax; minMax.SetFloat( pFloodFillRequest->m_vStartPos.x - pFloodFillRequest->m_fMaxRadius, pFloodFillRequest->m_vStartPos.y - pFloodFillRequest->m_fMaxRadius, pFloodFillRequest->m_vStartPos.z - pFloodFillRequest->m_fMaxRadius, pFloodFillRequest->m_vStartPos.x + pFloodFillRequest->m_fMaxRadius, pFloodFillRequest->m_vStartPos.y + pFloodFillRequest->m_fMaxRadius, pFloodFillRequest->m_vStartPos.z + pFloodFillRequest->m_fMaxRadius ); switch(pFloodFillRequest->m_FloodFillType) { case CFloodFillRequest::EAudioPropertiesFloodFill: { Assertf(0, "Not yet implemented!"); return false; } case CFloodFillRequest::EFindClosestCarNodeFloodFill: { pFloodFillRequest->m_pShouldVisitPolyFn = FloodFill_FindClosestCarNode_ShouldVisitPolyFn; pFloodFillRequest->m_pShouldEndOnPolyFn = FloodFill_FindClosestCarNode_ShouldEndOnThisPolyFn; pFloodFillRequest->GetFindClosestCarNodeData()->m_bFoundCarNode = false; pFloodFillRequest->GetFindClosestCarNodeData()->m_fClosestCarNodeDistSqr = FLT_MAX; break; } case CFloodFillRequest::EFindClosestShelteredPolyFloodFill: { pFloodFillRequest->m_pShouldVisitPolyFn = FloodFill_FindClosestShelteredPoly_ShouldVisitPolyFn; pFloodFillRequest->m_pShouldEndOnPolyFn = FloodFill_FindClosestShelteredPoly_ShouldEndOnThisPolyFn; pFloodFillRequest->GetFindClosestShelteredPolyData()->m_bFound = false; pFloodFillRequest->GetFindClosestShelteredPolyData()->m_fClosestDistSqr = FLT_MAX; break; } case CFloodFillRequest::EFindClosestUnshelteredPolyFloodFill: { pFloodFillRequest->m_pShouldVisitPolyFn = FloodFill_FindClosestUnshelteredPoly_ShouldVisitPolyFn; pFloodFillRequest->m_pShouldEndOnPolyFn = FloodFill_FindClosestUnshelteredPoly_ShouldEndOnThisPolyFn; pFloodFillRequest->GetFindClosestUnshelteredPolyData()->m_startPolyIndex = pStartNavMesh->GetPolyIndex(pStartPoly); pFloodFillRequest->GetFindClosestUnshelteredPolyData()->m_startNavMeshIndex = pStartPoly->GetNavMeshIndex(); pFloodFillRequest->GetFindClosestUnshelteredPolyData()->m_fClosestDistSqr = FLT_MAX; break; } case CFloodFillRequest::ECalcAreaUnderfoot: { pFloodFillRequest->m_pShouldVisitPolyFn = FloodFill_CalcAreaUnderfoot_ShouldVisitPolyFn; pFloodFillRequest->m_pShouldEndOnPolyFn = FloodFill_CalcAreaUnderfoot_ShouldEndOnThisPolyFn; pFloodFillRequest->GetCalcAreaUnderfootData()->m_fArea = 0.0f; break; } case CFloodFillRequest::EFindClosestPositionOnLand: { pFloodFillRequest->m_pShouldVisitPolyFn = FloodFill_FindClosestPositionOnLand_ShouldVisitPolyFn; pFloodFillRequest->m_pShouldEndOnPolyFn = FloodFill_FindClosestPositionOnLand_ShouldEndOnThisPolyFn; pFloodFillRequest->GetFindClosestPositionOnLandData()->m_bFound = false; pFloodFillRequest->GetFindClosestPositionOnLandData()->m_fClosestDistSqr = FLT_MAX; break; } case CFloodFillRequest::EFindNearbyPavementFloodFill: { pFloodFillRequest->m_pShouldVisitPolyFn = NULL; // FloodFill_FindNearbyPavement_ShouldVisitPolyFn; pFloodFillRequest->m_pShouldEndOnPolyFn = FloodFill_FindNearbyPavementPoly_ShouldEndOnThisPolyFn; pFloodFillRequest->GetFindNearbyPavementPolyData()->m_bFound = false; break; } default: { Assert(0); return false; } } //**************************************************************************** Vector3 vDirFromLast(0.0f,0.0f,0.0f); Vector3 vPolyCentroid; pStartNavMesh->GetPolyCentroidQuick(pStartPoly, vPolyCentroid); float fDistSqr = (vPolyCentroid - pFloodFillRequest->m_vStartPos).Mag2(); m_PathSearchPriorityQueue->Clear(); pStartPoly->m_AStarTimeStamp = m_iAStarTimeStamp; m_PathSearchPriorityQueue->Insert(fDistSqr, 0.0f, pStartPoly, vPolyCentroid, vDirFromLast, 0, NAVMESH_POLY_INDEX_NONE); //**************************************************************************** float fParentCost, fDistanceTravelled; TNavMeshPoly * pPoly; CNavMesh * pNavMesh; u32 iBinHeapFlags = 0; u32 iTmpIndex; while(m_PathSearchPriorityQueue->RemoveTop(fParentCost, fDistanceTravelled, pPoly, m_Vars.m_vLastClosestPoint, m_Vars.m_vDirFromPrevious, iBinHeapFlags, iTmpIndex)) { if(!pPoly) continue; Assertf(pPoly->GetNavMeshIndex() != NAVMESH_INDEX_TESSELLATION, "FloodFill - visiting poly in tessellation mesh"); Assert(!pPoly->GetIsTessellatedFragment()); if(CPathServer::m_bForceAbortCurrentRequest) { pFloodFillRequest->m_iCompletionCode = PATH_ABORTED_DUE_TO_ANOTHER_THREAD; return false; } #if NAVMESH_OPTIMISATIONS_OFF Assert(!pPoly->GetReplacedByTessellation()); #endif if(pPoly->GetReplacedByTessellation()) continue; pNavMesh = CPathServer::GetNavMeshFromIndex(pPoly->GetNavMeshIndex(), domain); if(!pNavMesh) continue; Assertf(pNavMesh->GetIndexOfMesh()!=NAVMESH_INDEX_TESSELLATION, "FloodFill - poly belongs to tessellation mesh"); if(!pFloodFillRequest->m_pShouldEndOnPolyFn || pFloodFillRequest->m_pShouldEndOnPolyFn(pPoly, pNavMesh, pFloodFillRequest, fDistanceTravelled)) { return true; } for(u32 a=0; aGetNumVertices(); a++) { const TAdjPoly & adjPoly = pNavMesh->GetAdjacentPoly(pPoly->GetFirstVertexIndex() + a); const u32 iAdjNavMesh = adjPoly.GetOriginalNavMeshIndex(pNavMesh->GetAdjacentMeshes()); Assertf(adjPoly.GetNavMeshIndex(pNavMesh->GetAdjacentMeshes())!=NAVMESH_INDEX_TESSELLATION, "FloodFill - TAdjPoly adjacency is tessellation mesh!"); if(iAdjNavMesh!=NAVMESH_NAVMESH_INDEX_NONE && adjPoly.GetOriginalPolyIndex()!=NAVMESH_POLY_INDEX_NONE) { // Usage of climbs & drops can be enabled/disabled in the floodfill-request itself, or can be // handled specifically in the callback fn. if(!pFloodFillRequest->m_bUseClimbsAndDrops && (adjPoly.GetAdjacencyType()==ADJACENCY_TYPE_CLIMB_LOW || adjPoly.GetAdjacencyType()==ADJACENCY_TYPE_CLIMB_HIGH || adjPoly.GetAdjacencyType()==ADJACENCY_TYPE_DROPDOWN)) { continue; } CNavMesh * pAdjNavMesh = CPathServer::GetNavMeshFromIndex(iAdjNavMesh, domain); if(pAdjNavMesh) { Assertf(pAdjNavMesh->GetIndexOfMesh()!=NAVMESH_INDEX_TESSELLATION, "adjacent navmesh is tessellation mesh"); TNavMeshPoly * pAdjPoly = pAdjNavMesh->GetPoly(adjPoly.GetOriginalPolyIndex()); Assert(pAdjPoly); if(pAdjPoly && minMax.Intersects(pAdjPoly->m_MinMax) && pAdjPoly->m_AStarTimeStamp!=m_iAStarTimeStamp && (!pFloodFillRequest->m_pShouldVisitPolyFn || pFloodFillRequest->m_pShouldVisitPolyFn(pAdjPoly, pAdjNavMesh, pPoly, pNavMesh, &adjPoly))) { pAdjNavMesh->GetPolyCentroidQuick(pAdjPoly, vPolyCentroid); if(!pFloodFillRequest->m_bConsiderDynamicObjects || TestDynamicObjectLOS(m_Vars.m_vLastClosestPoint, vPolyCentroid)) { pAdjPoly->m_AStarTimeStamp = m_iAStarTimeStamp; fDistSqr = (vPolyCentroid - pFloodFillRequest->m_vStartPos).Mag2(); fDistanceTravelled += (m_Vars.m_vLastClosestPoint - vPolyCentroid).Mag(); m_PathSearchPriorityQueue->Insert(fDistSqr, fDistanceTravelled, pAdjPoly, vPolyCentroid, vDirFromLast, 0, NAVMESH_POLY_INDEX_NONE); } } } } } // Do ladders & other special-links here.. if(pPoly->GetNumSpecialLinks()!=0) { const u32 iPolyIndex = pNavMesh->GetPolyIndex(pPoly); s32 iLinkLookupIndex = pPoly->GetSpecialLinksStartIndex(); for(s32 s=0; sGetNumSpecialLinks(); s++) { const u16 iLinkIndex = pNavMesh->GetSpecialLinkIndex(iLinkLookupIndex++); Assert(iLinkIndex < pNavMesh->GetNumSpecialLinks()); const CSpecialLinkInfo & linkInfo = pNavMesh->GetSpecialLinks()[iLinkIndex]; if(linkInfo.GetOriginalLinkFromPoly() == iPolyIndex) { switch(linkInfo.GetLinkType()) { default: Assert(false); break; case NAVMESH_LINKTYPE_CLIMB_LADDER: case NAVMESH_LINKTYPE_DESCEND_LADDER: { if(pFloodFillRequest->m_bUseLadders) { CNavMesh * pLinkToNavMesh = CPathServer::GetNavMeshFromIndex(linkInfo.GetOriginalLinkToNavMesh(), domain); if(pLinkToNavMesh) { TNavMeshPoly * pLinkToPoly = pLinkToNavMesh->GetPoly(linkInfo.GetOriginalLinkToPoly()); if(minMax.Intersects(pLinkToPoly->m_MinMax) && pLinkToPoly->m_AStarTimeStamp!=m_iAStarTimeStamp && (!pFloodFillRequest->m_pShouldVisitPolyFn || pFloodFillRequest->m_pShouldVisitPolyFn(pLinkToPoly, pLinkToNavMesh, pPoly, pNavMesh, NULL))) { pLinkToPoly->m_AStarTimeStamp = m_iAStarTimeStamp; pLinkToNavMesh->GetPolyCentroidQuick(pLinkToPoly, vPolyCentroid); fDistSqr = (vPolyCentroid - pFloodFillRequest->m_vStartPos).Mag2(); fDistanceTravelled += (m_Vars.m_vLastClosestPoint - vPolyCentroid).Mag(); m_PathSearchPriorityQueue->Insert(fDistSqr, fDistanceTravelled, pLinkToPoly, vPolyCentroid, vDirFromLast, 0, NAVMESH_POLY_INDEX_NONE); } } } break; } case NAVMESH_LINKTYPE_CLIMB_OBJECT: { if(pFloodFillRequest->m_bUseClimbsAndDrops) { CNavMesh * pLinkToNavMesh = CPathServer::GetNavMeshFromIndex(linkInfo.GetOriginalLinkToNavMesh(), domain); if(pLinkToNavMesh) { TNavMeshPoly * pLinkToPoly = pLinkToNavMesh->GetPoly(linkInfo.GetOriginalLinkToPoly()); if(minMax.Intersects(pLinkToPoly->m_MinMax) && pLinkToPoly->m_AStarTimeStamp!=m_iAStarTimeStamp && (!pFloodFillRequest->m_pShouldVisitPolyFn || pFloodFillRequest->m_pShouldVisitPolyFn(pLinkToPoly, pLinkToNavMesh, pPoly, pNavMesh, NULL))) { pLinkToPoly->m_AStarTimeStamp = m_iAStarTimeStamp; pLinkToNavMesh->GetPolyCentroidQuick(pLinkToPoly, vPolyCentroid); fDistSqr = (vPolyCentroid - pFloodFillRequest->m_vStartPos).Mag2(); fDistanceTravelled += (m_Vars.m_vLastClosestPoint - vPolyCentroid).Mag(); m_PathSearchPriorityQueue->Insert(fDistSqr, fDistanceTravelled, pLinkToPoly, vPolyCentroid, vDirFromLast, 0, NAVMESH_POLY_INDEX_NONE); } } } } } } } } } return true; } bool CPathServerThread::FloodFill_FindClosestCarNode_ShouldVisitPolyFn(const TNavMeshPoly * UNUSED_PARAM(pAdjPoly), const CNavMesh * UNUSED_PARAM(pAdjNavMesh), const TNavMeshPoly * UNUSED_PARAM(pFromPoly), const CNavMesh * UNUSED_PARAM(pFromNavMesh), const TAdjPoly * UNUSED_PARAM(pAdjacency)) { return true; } bool CPathServerThread::FloodFill_FindClosestCarNode_ShouldEndOnThisPolyFn(TNavMeshPoly * pAdjPoly, CNavMesh * pAdjNavMesh, CFloodFillRequest * pFloodFillRequest, const float fDistanceTravelled) { if(pAdjPoly->GetIsNearCarNode()) { Vector3 vPolyCentroid; pAdjNavMesh->GetPolyCentroid(pAdjNavMesh->GetPolyIndex(pAdjPoly), vPolyCentroid); //float fDistSqr = (pFloodFillRequest->GetFindClosestCarNodeData()->GetCarNodePos() - pFloodFillRequest->m_vStartPos).Mag2(); const float fDistSqr = fDistanceTravelled*fDistanceTravelled; if(fDistSqr < pFloodFillRequest->GetFindClosestCarNodeData()->m_fClosestCarNodeDistSqr) { pFloodFillRequest->GetFindClosestCarNodeData()->m_fClosestCarNodeDistSqr = fDistSqr; pFloodFillRequest->GetFindClosestCarNodeData()->SetCarNodePos(vPolyCentroid); pFloodFillRequest->GetFindClosestCarNodeData()->m_bFoundCarNode = true; } } // Never terminate this search early - we want to ensure we find the closest node by an exhaustive search return false; } bool CPathServerThread::FloodFill_FindClosestShelteredPoly_ShouldVisitPolyFn(const TNavMeshPoly * UNUSED_PARAM(pAdjPoly), const CNavMesh * UNUSED_PARAM(pAdjNavMesh), const TNavMeshPoly * UNUSED_PARAM(pFromPoly), const CNavMesh * UNUSED_PARAM(pFromNavMesh), const TAdjPoly * pAdjacency) { // When looking for sheltered polys don't use climbs/drops if(pAdjacency->GetAdjacencyType()==ADJACENCY_TYPE_NORMAL) return true; return false; } bool CPathServerThread::FloodFill_FindClosestShelteredPoly_ShouldEndOnThisPolyFn(TNavMeshPoly * pAdjPoly, CNavMesh * pAdjNavMesh, CFloodFillRequest * pFloodFillRequest, const float UNUSED_PARAM(fDistanceTravelled)) { if(pAdjPoly->GetIsSheltered()) { Vector3 vPolyCentroid; pAdjNavMesh->GetPolyCentroid(pAdjNavMesh->GetPolyIndex(pAdjPoly), vPolyCentroid); float fDistSqr = (pFloodFillRequest->GetFindClosestShelteredPolyData()->GetPos() - pFloodFillRequest->m_vStartPos).Mag2(); if(fDistSqr < pFloodFillRequest->GetFindClosestShelteredPolyData()->m_fClosestDistSqr) { pFloodFillRequest->GetFindClosestShelteredPolyData()->m_fClosestDistSqr = fDistSqr; pFloodFillRequest->GetFindClosestShelteredPolyData()->SetPos(vPolyCentroid); pFloodFillRequest->GetFindClosestShelteredPolyData()->m_bFound = true; } } // Never terminate this search early - we want to ensure we find the closest node by an exhaustive search return false; } bool CPathServerThread::FloodFill_FindClosestUnshelteredPoly_ShouldVisitPolyFn(const TNavMeshPoly * UNUSED_PARAM(pAdjPoly), const CNavMesh * UNUSED_PARAM(pAdjNavMesh), const TNavMeshPoly * UNUSED_PARAM(pFromPoly), const CNavMesh * UNUSED_PARAM(pFromNavMesh), const TAdjPoly * pAdjacency) { // When looking for sheltered polys don't use climbs/drops if(pAdjacency->GetAdjacencyType()==ADJACENCY_TYPE_NORMAL) return true; return false; } bool CPathServerThread::FloodFill_FindClosestUnshelteredPoly_ShouldEndOnThisPolyFn(TNavMeshPoly * pAdjPoly, CNavMesh * pAdjNavMesh, CFloodFillRequest * pFloodFillRequest, const float UNUSED_PARAM(fDistanceTravelled)) { if(!pAdjPoly->GetIsSheltered()) { // special case - if the start poly is unsheltered then we're good to end immediately if(pFloodFillRequest->GetFindClosestUnshelteredPolyData()->m_startNavMeshIndex == pAdjPoly->GetNavMeshIndex() && pAdjNavMesh->GetPolyIndex(pAdjPoly) == pFloodFillRequest->GetFindClosestUnshelteredPolyData()->m_startPolyIndex) { pFloodFillRequest->GetFindClosestUnshelteredPolyData()->m_fClosestDistSqr = 0.f; return true; } else { Vector3 vPolyCentroid; pAdjNavMesh->GetPolyCentroid(pAdjNavMesh->GetPolyIndex(pAdjPoly), vPolyCentroid); float fDistSqr = (vPolyCentroid - pFloodFillRequest->m_vStartPos).Mag2(); pFloodFillRequest->GetFindClosestUnshelteredPolyData()->m_fClosestDistSqr = Min(fDistSqr, pFloodFillRequest->GetFindClosestUnshelteredPolyData()->m_fClosestDistSqr); if(pFloodFillRequest->GetFindClosestUnshelteredPolyData()->m_fClosestDistSqr <= 9.0f) { // 3m is close enough... return true; } } } return false; } bool CPathServerThread::FloodFill_CalcAreaUnderfoot_ShouldVisitPolyFn(const TNavMeshPoly * UNUSED_PARAM(pAdjPoly), const CNavMesh * UNUSED_PARAM(pAdjNavMesh), const TNavMeshPoly * UNUSED_PARAM(pFromPoly), const CNavMesh * UNUSED_PARAM(pFromNavMesh), const TAdjPoly * pAdjacency) { // When looking for sheltered polys don't use climbs/drops if(pAdjacency->GetAdjacencyType()==ADJACENCY_TYPE_NORMAL) return true; return false; } bool CPathServerThread::FloodFill_CalcAreaUnderfoot_ShouldEndOnThisPolyFn(TNavMeshPoly * pAdjPoly, CNavMesh * pAdjNavMesh, CFloodFillRequest * pFloodFillRequest, const float UNUSED_PARAM(fDistanceTravelled)) { const float fPolyArea = pAdjNavMesh->GetPolyArea( pAdjNavMesh->GetPolyIndex(pAdjPoly) ); #ifdef GTA_ENGINE Assert(rage::FPIsFinite(fPolyArea)); #endif pFloodFillRequest->GetCalcAreaUnderfootData()->m_fArea += fPolyArea; return false; } bool CPathServerThread::FloodFill_FindClosestPositionOnLand_ShouldVisitPolyFn(const TNavMeshPoly * UNUSED_PARAM(pAdjPoly), const CNavMesh * UNUSED_PARAM(pAdjNavMesh), const TNavMeshPoly * UNUSED_PARAM(pFromPoly), const CNavMesh * UNUSED_PARAM(pFromNavMesh), const TAdjPoly * pAdjacency) { if(pAdjacency && pAdjacency->GetAdjacencyType()!=ADJACENCY_TYPE_NORMAL && pAdjacency->GetAdjacencyDisabled()) return false; return true; } bool CPathServerThread::FloodFill_FindClosestPositionOnLand_ShouldEndOnThisPolyFn(TNavMeshPoly * pAdjPoly, CNavMesh * pAdjNavMesh, CFloodFillRequest * pFloodFillRequest, const float UNUSED_PARAM(fDistanceTravelled)) { if(!pAdjPoly->GetIsWater() && !pAdjPoly->GetIsIsolated()) { Vector3 vPolyCentroid; pAdjNavMesh->GetPolyCentroid(pAdjNavMesh->GetPolyIndex(pAdjPoly), vPolyCentroid); const Vector3 vDiff = pFloodFillRequest->GetFindClosestPositionOnLandData()->GetPos() - pFloodFillRequest->m_vStartPos; const float fDistSqr = vDiff.Mag2(); if(fDistSqr < pFloodFillRequest->GetFindClosestPositionOnLandData()->m_fClosestDistSqr) { pFloodFillRequest->GetFindClosestPositionOnLandData()->m_fClosestDistSqr = fDistSqr; pFloodFillRequest->GetFindClosestPositionOnLandData()->SetPos(vPolyCentroid); pFloodFillRequest->GetFindClosestPositionOnLandData()->m_bFound = true; } } // Never end the search early, we want to make sure we find the closest place to get out return false; } bool CPathServerThread::FloodFill_FindNearbyPavement_ShouldVisitPolyFn (const TNavMeshPoly * UNUSED_PARAM(pAdjPoly), const CNavMesh * UNUSED_PARAM(pAdjNavMesh), const TNavMeshPoly * UNUSED_PARAM(pFromPoly), const CNavMesh * UNUSED_PARAM(pFromNavMesh), const TAdjPoly * UNUSED_PARAM(pAdjacency)) { return true; } bool CPathServerThread::FloodFill_FindNearbyPavementPoly_ShouldEndOnThisPolyFn (TNavMeshPoly * pAdjPoly, CNavMesh * UNUSED_PARAM(pAdjNavMesh), CFloodFillRequest * pFloodFillRequest, const float UNUSED_PARAM(fDistanceTravelled)) { if (pAdjPoly->GetIsPavement()) { pFloodFillRequest->GetFindNearbyPavementPolyData()->m_bFound = true; return true; } return false; } int ClearArea_SortPolyListCB(const TNavProcessClearAreaEntry * p1, const TNavProcessClearAreaEntry * p2) { return (p1->m_fDistSqr < p2->m_fDistSqr) ? -1 : 1; } bool CPathServerThread::ProcessClearAreaRequest(CClearAreaRequest * pClearAreaRequest) { #ifdef GTA_ENGINE const Vector3 vAreaMin = pClearAreaRequest->m_vSearchOrigin - Vector3(pClearAreaRequest->m_fSearchRadiusXY, pClearAreaRequest->m_fSearchRadiusXY, pClearAreaRequest->m_fSearchDistZ); const Vector3 vAreaMax = pClearAreaRequest->m_vSearchOrigin + Vector3(pClearAreaRequest->m_fSearchRadiusXY, pClearAreaRequest->m_fSearchRadiusXY, pClearAreaRequest->m_fSearchDistZ); TShortMinMax areaMinMax; areaMinMax.Set(vAreaMin, vAreaMax); const aiNavDomain domain = pClearAreaRequest->GetMeshDataSet(); aiNavMeshStore * pStore = CPathServer::GetNavMeshStore(domain); atArray loadedMeshes; LOCK_STORE_LOADED_MESHES { const atArray & storeLoadedMeshes = pStore->GetLoadedMeshes(); loadedMeshes.CopyFrom( storeLoadedMeshes.GetElements(), (u16) storeLoadedMeshes.GetCount() ); } UNLOCK_STORE_LOADED_MESHES static const u32 iMaxPolys = 4096; atArray sortedPolys; sortedPolys.Reserve(iMaxPolys); Vector3 vPolyCentroid; Vector2 vMeshMin, vMeshMax; s32 iNavMesh; const float fMinDistSqr = pClearAreaRequest->m_fMinimumDistanceFromOrigin * pClearAreaRequest->m_fMinimumDistanceFromOrigin; for(int m=0; mGetNumPolys(); p++) { TNavMeshPoly * pPoly = pNavMesh->GetPoly(p); if(!pPoly->m_MinMax.Intersects(areaMinMax)) continue; if(pPoly->GetIsWater() && !pClearAreaRequest->m_bConsiderWater) continue; if(pPoly->GetIsInterior() && !pClearAreaRequest->m_bConsiderInterior) continue; if(!pPoly->GetIsInterior() && !pClearAreaRequest->m_bConsiderExterior) continue; if(pPoly->GetIsSheltered() && !pClearAreaRequest->m_bConsiderSheltered) continue; pNavMesh->GetPolyCentroidQuick(pPoly, vPolyCentroid); // Test optional minimum distance which center of area must be from the supplied origin. (url:bugstar:738465) if(fMinDistSqr > 0.0f) { if( (vPolyCentroid - pClearAreaRequest->m_vSearchOrigin).XYMag2() < fMinDistSqr ) continue; } sortedPolys.PushAndGrow( TNavProcessClearAreaEntry(pPoly, (vPolyCentroid-pClearAreaRequest->m_vSearchOrigin).Mag2()) ); if(sortedPolys.GetCount()==sortedPolys.GetCapacity()) break; } } // Sort the poly list by distance from the search origin sortedPolys.QSort(0, -1, ClearArea_SortPolyListCB); //---------------------------------- // Now process each polygon // Calculate the test directions // TODO: increase the number of tests based upon the desired clear area int iNumTestDirections = 8; Vector2 vTestDirections[8]; Vector3 vIsectPositions[8]; TNavMeshPoly * pEndPolys[8]; const float fInc = (360.0f / ((float)iNumTestDirections)) * DtoR; const Vector2 vOriginal(pClearAreaRequest->m_fDesiredClearAreaRadius, 0.0f); float fRot = 0.0f; for(int i=0; im_bConsiderInterior != pClearAreaRequest->m_bConsiderExterior) iLosFlags |= CTestNavMeshLosVars::FLAG_NO_LOS_ACROSS_ACROSS_INTERIOR_EXTERIOR_BOUNDARY; if(!pClearAreaRequest->m_bConsiderSheltered) iLosFlags |= CTestNavMeshLosVars::FLAG_NO_LOS_ACROSS_ACROSS_SHELTERED_BOUNDARY; if(!pClearAreaRequest->m_bConsiderWater) iLosFlags |= CTestNavMeshLosVars::FLAG_NO_LOS_ACROSS_WATER_BOUNDARY; Vector3 vIsectPos; static dev_u32 iNumPolysToProcessBeforeYield = 256; u32 iYieldCounter = iNumPolysToProcessBeforeYield; for(u32 p=0; pStop(); #endif // give up the rest of our timeslice sysIpcYield(0); iYieldCounter = iNumPolysToProcessBeforeYield; #if !__FINAL m_RequestProcessingOverallTimer->Start(); pClearAreaRequest->m_iNumTimesSlept++; #endif } // Enable streaming to abort this request instantly or we may incur a stall if(CPathServer::m_bForceAbortCurrentRequest) { pClearAreaRequest->m_iCompletionCode = PATH_ABORTED_DUE_TO_ANOTHER_THREAD; return false; } TNavMeshPoly * pPoly = sortedPolys[p].m_pPoly; CNavMesh * pNavMesh = CPathServer::GetNavMeshFromIndex(pPoly->GetNavMeshIndex(), domain); // NB: Do we want to calculate this again, or should we have cached it in TNavProcessClearAreaEntry? pNavMesh->GetPolyCentroidQuick(pPoly, vPolyCentroid); // TODO: Opportunity to early out on this polygon if the space around it's vertices/edges violates our desired radius constraint? // Test line-of-sight from polygon centroid out to the maximum radius, in a number of directions int t; for(t=0; tm_bConsiderDynamicObjects) if(!TestDynamicObjectLOS(vPolyCentroid, vIsectPositions[t])) pEndPoly = NULL; #if __BANK && DEBUG_DRAW if(CPathServer::m_iVisualisePathServerInfo == CPathServer::PathServerVis_ClearAreaRequests) grcDebugDraw::Line(vPolyCentroid, vPolyCentroid+Vector3(vTestDirections[t].x,vTestDirections[t].y,0.0f), pEndPoly ? Color_green:Color_red, 10); #endif if(!pEndPoly) break; pEndPolys[t] = pEndPoly; } // If any LOS failed then proceed to next polygon if(t != iNumTestDirections) continue; // Test line-of-sight in a circle at the maximum radius for(t=0; tm_bConsiderDynamicObjects) if(!TestDynamicObjectLOS(vIsectPositions[t], vIsectPos)) pEndPoly = NULL; #if __BANK && DEBUG_DRAW if(CPathServer::m_iVisualisePathServerInfo == CPathServer::PathServerVis_ClearAreaRequests) grcDebugDraw::Line(vIsectPositions[t], vIsectPositions[t]+Vector3(vTestDir.x,vTestDir.y,0.0f), pEndPoly ? Color_green:Color_red, 10); #endif if(!pEndPoly) break; } // If any LOS failed then proceed to next polygon if(t != iNumTestDirections) continue; // This polygon has passed the area test, so we can use its centroid as the result of our clear-area search pClearAreaRequest->m_vResultOrigin = vPolyCentroid; pClearAreaRequest->m_iCompletionCode = PATH_FOUND; return true; } #endif // GTA_ENGINE pClearAreaRequest->m_iCompletionCode = PATH_NOT_FOUND; return false; } // Called prior to visiting any polygon during ProcessClosestPositionRequest() bool CPathServerThread::ClosestPositionRequest_PerPolyCallback(const CNavMesh * UNUSED_PARAM(pNavMesh), const TNavMeshPoly * pPoly) { CClosestPositionRequest * pRequest = CPathServer::GetPathServerThread()->m_pCurrentActiveClosestPositionRequest; Assert(pRequest); if(!pRequest->m_bConsiderInterior && pPoly->GetIsInterior()) return false; if(!pRequest->m_bConsiderExterior && !pPoly->GetIsInterior()) return false; if(pRequest->m_bConsiderOnlyLand && pPoly->GetIsWater()) return false; if(pRequest->m_bConsiderOnlyWater && !pPoly->GetIsWater()) return false; if(pRequest->m_bConsiderOnlyPavement && !pPoly->GetIsPavement()) return false; if(pRequest->m_bConsiderOnlyNonIsolated && pPoly->GetIsIsolated()) return false; if(pRequest->m_bConsiderOnlySheltered && !pPoly->GetIsSheltered()) return false; if(pRequest->m_bConsiderOnlyNetworkSpawn && !pPoly->GetIsNetworkSpawnCandidate()) return false; if(pRequest->m_bConsiderDynamicObjects) { CPathServerThread::ms_dynamicObjectsIntersectingPolygons.clear(); CDynamicObjectsContainer::GetObjectsIntersectingRegion(pPoly->m_MinMax, CPathServerThread::ms_dynamicObjectsIntersectingPolygons); } return true; } // Called prior to visiting each point-in-polygon during ProcessClosestPositionRequest() float CPathServerThread::ClosestPositionRequest_PerPointCallback(const CNavMesh * UNUSED_PARAM(pNavMesh), const TNavMeshPoly * UNUSED_PARAM(pPoly), const Vector3 & vPos) { CClosestPositionRequest * pRequest = CPathServer::GetPathServerThread()->m_pCurrentActiveClosestPositionRequest; Assert(pRequest); // Calculate sqr distance from sphere origin Vector3 vDiff = pRequest->m_vSearchOrigin - vPos; float fScore = vDiff.Mag2(); // If we're outside of the search range, then reject if(fScore > pRequest->m_fSearchRadius*pRequest->m_fSearchRadius) { return FLT_MAX; } // Check avoid apheres for(s32 s=0; sm_iNumAvoidSpheres; s++) { if(pRequest->m_AvoidSpheres[s].ContainsPoint( VECTOR3_TO_VEC3V(vPos) ) ) { return FLT_MAX; } } // Check dynamic objects if(pRequest->m_bConsiderDynamicObjects) { for(s32 o=0; om_Bounds.m_vEdgePlaneNormals[p].Dot3(vPos) + pObj->m_Bounds.m_vEdgePlaneNormals[p].w) + fDynObjPlaneEpsilon[p]; // If we're outside of any edge plane, then we cannot be within the object at all if(fEdgePlaneDist > PATHSERVER_PED_RADIUS) { break; } } if(p == 6) { return FLT_MAX; } } } // Check spacing constraints if(pRequest->m_fMinimumSpacing > 0.0f) { for(s32 s=0; sm_Results.m_iNumResults; s++) { if((pRequest->m_Results.m_vResults[s] - vPos).Mag2() < pRequest->m_fMinimumSpacing*pRequest->m_fMinimumSpacing) return FLT_MAX; } } // Handle optionally Z-weighting above/below the search origin if(vDiff.z < 0.0f) // point is above search origin ? { if(pRequest->m_fZWeightingAbove != 1.0f) { vDiff.z *= pRequest->m_fZWeightingAbove; fScore = vDiff.Mag2(); } } else // point is at or below search origin ? { if(pRequest->m_fZWeightingAtOrBelow != 1.0f) { vDiff.z *= pRequest->m_fZWeightingAtOrBelow; fScore = vDiff.Mag2(); } } // NB: Do we want to take the Sqrt() of fScore now, or are we happy using squared distances? //--------------------- // Insert result if(!pRequest->m_Results.m_iNumResults) { // If we have no results yet, then add as first member pRequest->m_Results.m_vResults[0] = vPos; pRequest->m_Results.m_vResults[0].w = fScore; pRequest->m_Results.m_iNumResults++; } else { // If results buffer is full, and score is greater than last item's score - just return if(pRequest->m_Results.m_iNumResults == pRequest->m_iMaxResults && fScore >= pRequest->m_Results.m_vResults[pRequest->m_Results.m_iNumResults-1].w) return fScore; // Otherwise we insert the result somewhere along the list s32 r; for(r=0; rm_Results.m_iNumResults; r++) { if(fScore < pRequest->m_Results.m_vResults[r].w) { s32 end = Min(pRequest->m_Results.m_iNumResults, pRequest->m_iMaxResults-1); for(s32 j=end; j>r; j--) { pRequest->m_Results.m_vResults[j] = pRequest->m_Results.m_vResults[j-1]; } pRequest->m_Results.m_vResults[r] = vPos; pRequest->m_Results.m_vResults[r].w = fScore; pRequest->m_Results.m_iNumResults++; pRequest->m_Results.m_iNumResults = Min(pRequest->m_Results.m_iNumResults, pRequest->m_iMaxResults); return fScore; } } if(r < pRequest->m_iMaxResults) { // Add result at the end of the list pRequest->m_Results.m_vResults[pRequest->m_Results.m_iNumResults] = vPos; pRequest->m_Results.m_vResults[pRequest->m_Results.m_iNumResults].w = fScore; pRequest->m_Results.m_iNumResults++; return fScore; } } return fScore; } bool CPathServerThread::ProcessClosestPositionRequest(CClosestPositionRequest * pClosestPosRequest) { Assertf(pClosestPosRequest->m_fSearchRadius < CPathServerExtents::GetSizeOfNavMesh(), "Search radius %.1f is too great, must be under %.1f", pClosestPosRequest->m_fSearchRadius, CPathServerExtents::GetSizeOfNavMesh()); pClosestPosRequest->m_fSearchRadius = Min(pClosestPosRequest->m_fSearchRadius, CPathServerExtents::GetSizeOfNavMesh()); //--------------------------------------------------- // Determine which navmeshes we might need to visit const Vector3 vMeshOffsets[8] = { Vector3(-pClosestPosRequest->m_fSearchRadius, 0.0f, 0.0f), Vector3(-pClosestPosRequest->m_fSearchRadius * 0.707f, pClosestPosRequest->m_fSearchRadius * 0.707f, 0.0f), Vector3(0.0f, pClosestPosRequest->m_fSearchRadius, 0.0f), Vector3(pClosestPosRequest->m_fSearchRadius * 0.707f, pClosestPosRequest->m_fSearchRadius * 0.707f, 0.0f), Vector3(pClosestPosRequest->m_fSearchRadius, 0.0f, 0.0f), Vector3(pClosestPosRequest->m_fSearchRadius * 0.707f, -pClosestPosRequest->m_fSearchRadius * 0.707f, 0.0f), Vector3(0.0f, -pClosestPosRequest->m_fSearchRadius, 0.0f), Vector3(-pClosestPosRequest->m_fSearchRadius * 0.707f, -pClosestPosRequest->m_fSearchRadius * 0.707f, 0.0f) }; TNavMeshIndex indices[9]; indices[0] = CPathServer::GetNavMeshIndexFromPosition(pClosestPosRequest->m_vSearchOrigin, pClosestPosRequest->m_NavDomain); int iNumIndices = 1; //----------------------------------------------------------------------------------- // Add indices of surrounding navmeshes. Ensure that we don't store any duplicates int o,o2; for(o=0; o<8; o++) { TNavMeshIndex index = CPathServer::GetNavMeshIndexFromPosition(pClosestPosRequest->m_vSearchOrigin + vMeshOffsets[o], pClosestPosRequest->m_NavDomain); for(o2=0; o2m_Results.m_iNumResults = 0; //----------------------------------------------------------------------- // Initially add a point directly under the input position, if one exists CNavMesh * pMeshUnderInputPos = CPathServer::GetNavMeshFromIndex( CPathServer::GetNavMeshIndexFromPosition(pClosestPosRequest->m_vSearchOrigin, pClosestPosRequest->m_NavDomain), pClosestPosRequest->m_NavDomain ); if(pMeshUnderInputPos) { Vector3 vPointUnderInputPos; u32 iPoly = pMeshUnderInputPos->GetPolyBelowPoint( pClosestPosRequest->m_vSearchOrigin, vPointUnderInputPos, pClosestPosRequest->m_fSearchRadius ); if(iPoly != NAVMESH_POLY_INDEX_NONE) { TNavMeshPoly * pPolyUnderInputPos = pMeshUnderInputPos->GetPoly(iPoly); if(ClosestPositionRequest_PerPolyCallback(pMeshUnderInputPos, pPolyUnderInputPos)) { ClosestPositionRequest_PerPointCallback(pMeshUnderInputPos, pPolyUnderInputPos, vPointUnderInputPos); } } } //------------------------- // Visit each navmesh for(o=0; om_NavDomain); if(pNavMesh) { pNavMesh->ForAllPolys( pClosestPosRequest->m_vSearchOrigin, pClosestPosRequest->m_fSearchRadius, ClosestPositionRequest_PerPolyCallback, ClosestPositionRequest_PerPointCallback, TForAllPolysStruct::FLAG_MULTIPLE_POINTS_PER_POLYGON); } } if(pClosestPosRequest->m_Results.m_iNumResults != 0) { pClosestPosRequest->m_iCompletionCode = PATH_FOUND; } else { pClosestPosRequest->m_iCompletionCode = PATH_NOT_FOUND; } return true; } void CPathServerThread::IncTimeStamp(void) { m_iNavMeshPolyTimeStamp++; // If our timestamp has reached whatever maximum value we have for it, then we must reset all the polys' timestamps to zero if(m_iNavMeshPolyTimeStamp == TIMESTAMP_MAX_VALUE) { #ifdef NAVGEN_TOOL HandleTimeStampOverFlow(); #else #if AI_OPTIMISATIONS_OFF || NAVMESH_OPTIMISATIONS_OFF Assertf(!m_bHandleTimestampOverflow, "Timestamp has overflowed twice during a single request - there will have been errors."); #endif // Flag the system to handle the overflow directly after this request m_bHandleTimestampOverflow = true; #endif } } void CPathServerThread::IncAStarTimeStamp() { m_iAStarTimeStamp++; // If our timestamp has reached whatever maximum value we have for it, then we must reset all the polys' timestamps to zero if(m_iAStarTimeStamp == TIMESTAMP_MAX_VALUE) { #ifdef NAVGEN_TOOL HandleAStarTimeStampOverFlow(); #else #if AI_OPTIMISATIONS_OFF || NAVMESH_OPTIMISATIONS_OFF Assertf(!m_bHandleAStarTimestampOverflow, "A* Timestamp has overflowed twice during a single request - there will have been errors."); #endif // Flag the system to handle the overflow directly after this request m_bHandleAStarTimestampOverflow = true; #endif } } // This function needs to visit all loaded polys in all nav-meshes, and has // to reset their timestamps to zero.. void CPathServerThread::HandleTimeStampOverFlow(void) { m_iNavMeshPolyTimeStamp = 1; #ifdef GTA_ENGINE #if __DEV printf("!!! CPathServerThread::HandleTimeStampOverFlow() !!!\n"); #endif LOCK_NAVMESH_DATA; LOCK_IMMEDIATE_DATA; LOCK_STORE_LOADED_MESHES; #endif // GTA_ENGINE for(int domain=0; domain & loadedMeshes = fwPathServer::GetNavMeshStore((aiNavDomain)domain)->GetLoadedMeshes(); int n; for(n=0; nGetNumPolys(); p++) { TNavMeshPoly & poly = *pNavMesh->GetPoly(p); poly.m_TimeStamp = 0; } } } } for(s32 n=0; nGetNumPolys(); p++) { TNavMeshPoly & poly = *pNavMesh->GetPoly(p); poly.m_TimeStamp = 0; } } } } void CPathServerThread::HandleAStarTimeStampOverFlow(void) { m_iAStarTimeStamp = 1; #if __DEV printf("!!! CPathServerThread::HandleAStarTimeStampOverFlow() !!!\n"); #endif #ifdef GTA_ENGINE LOCK_NAVMESH_DATA; LOCK_IMMEDIATE_DATA; LOCK_STORE_LOADED_MESHES; #endif // GTA_ENGINE for(int domain=0; domain & loadedMeshes = fwPathServer::GetNavMeshStore((aiNavDomain)domain)->GetLoadedMeshes(); int n; for(n=0; nGetNumPolys(); p++) { TNavMeshPoly & poly = *pNavMesh->GetPoly(p); poly.m_AStarTimeStamp = 0; } for(u32 s=0; sGetNumSpecialLinks(); s++) { pNavMesh->GetSpecialLinks()[s].SetAStarTimeStamp(0); } } } } for(s32 n=0; nGetNumPolys(); p++) { TNavMeshPoly & poly = *pNavMesh->GetPoly(p); poly.m_AStarTimeStamp = 0; } } } } void CPathServerThread::CalculatePolyMinMaxForDynamicNavMesh(TNavMeshPoly * pPoly, aiNavDomain domain) { CNavMesh * pNavMesh = CPathServer::GetNavMeshFromIndex(pPoly->GetNavMeshIndex(), domain); Assert(pNavMesh); pNavMesh->CalculatePolyMinMaxForDynamicNavMesh(pPoly); } bool CPathServerThread::TestNavMeshLOS(const Vector3 & vStartPos, const Vector3 & vEndPos, const u32 iLosFlags, aiNavDomain domain) { m_LosVars.Init(vStartPos, vEndPos, iLosFlags); CNavMesh * pStartNavMesh, * pEndNavMesh; TNavMeshPoly * pStartPoly, * pEndPoly; const EPathServerErrorCode eStartRet = GetNavMeshPolyForRouteEndPoints( vStartPos, NULL, pStartNavMesh, pStartPoly, NULL, ms_fNormalDistBelowToLookForPoly, 0.0f, false, false, domain ); if(eStartRet != PATH_NO_ERROR) { return false; } const EPathServerErrorCode eEndRet = GetNavMeshPolyForRouteEndPoints( vEndPos, NULL, pEndNavMesh, pEndPoly, NULL, ms_fNormalDistBelowToLookForPoly, 0.0f, false, false, domain ); if(eEndRet != PATH_NO_ERROR) { return false; } Assert(!pStartPoly || pStartPoly->GetNavMeshIndex() != NAVMESH_INDEX_TESSELLATION); Assert(!pEndPoly || pEndPoly->GetNavMeshIndex() != NAVMESH_INDEX_TESSELLATION); // Increment timestamp before LOS. IncTimeStamp(); const bool bLosExists = TestNavMeshLOS( vStartPos, vEndPos, pEndPoly, pStartPoly, NULL, iLosFlags, domain ); return bLosExists; } //************************************************************************************ // pToPoly is the poly upon which the vEndPos is situated // when first called pTestPoly will be the poly upon which vStartPos is situated bool CPathServerThread::TestNavMeshLOS(const Vector3 & vStartPos, const Vector3 & vEndPos, TNavMeshPoly * pToPoly, TNavMeshPoly * pTestPoly, TNavMeshPoly * pLastPoly, const u32 iLosFlags, aiNavDomain domain) { if(pToPoly && pToPoly->GetNavMeshIndex()==NAVMESH_INDEX_TESSELLATION) { const u32 iPoly = CPathServer::GetTessellationNavMesh()->GetPolyIndex(pToPoly); TTessInfo * pTess = CPathServer::GetTessInfo(iPoly); Assert(pTess); CNavMesh * pOriginalMesh = CPathServer::GetNavMeshFromIndex(pTess->m_iNavMeshIndex, kNavDomainRegular); if(!pOriginalMesh) return false; pToPoly = pOriginalMesh->GetPoly(pTess->m_iPolyIndex); } if(pTestPoly && pTestPoly->GetNavMeshIndex()==NAVMESH_INDEX_TESSELLATION) { const u32 iPoly = CPathServer::GetTessellationNavMesh()->GetPolyIndex(pTestPoly); TTessInfo * pTess = CPathServer::GetTessInfo(iPoly); Assert(pTess); CNavMesh * pOriginalMesh = CPathServer::GetNavMeshFromIndex(pTess->m_iNavMeshIndex, kNavDomainRegular); if(!pOriginalMesh) return false; pTestPoly = pOriginalMesh->GetPoly(pTess->m_iPolyIndex); } if(pLastPoly && pLastPoly->GetNavMeshIndex()==NAVMESH_INDEX_TESSELLATION) { const u32 iPoly = CPathServer::GetTessellationNavMesh()->GetPolyIndex(pLastPoly); TTessInfo * pTess = CPathServer::GetTessInfo(iPoly); Assert(pTess); CNavMesh * pOriginalMesh = CPathServer::GetNavMeshFromIndex(pTess->m_iNavMeshIndex, kNavDomainRegular); if(!pOriginalMesh) return false; pLastPoly = pOriginalMesh->GetPoly(pTess->m_iPolyIndex); } Assert(!pToPoly || pToPoly->GetNavMeshIndex() != NAVMESH_INDEX_TESSELLATION); Assert(!pTestPoly || pTestPoly->GetNavMeshIndex() != NAVMESH_INDEX_TESSELLATION); Assert(!pLastPoly || pLastPoly->GetNavMeshIndex() != NAVMESH_INDEX_TESSELLATION); if(pToPoly && pToPoly->GetIsDisabled()) return false; if(pTestPoly && pTestPoly->GetIsDisabled()) return false; m_LosVars.Init(vStartPos, vEndPos, pToPoly, iLosFlags); return TestNavMeshLOS_R(pTestPoly, pLastPoly, domain); } TNavMeshPoly * CPathServerThread::TestNavMeshLOS(const Vector3 & vStartPos, const Vector2 & vLosDir, Vector3 * pvOut_IntersectionPos, TNavMeshPoly * pStartPoly, const u32 iLosFlags, aiNavDomain domain) { if(!pStartPoly) { Vector3 vTmp; const u32 iStartNavMesh = CPathServer::GetNavMeshIndexFromPosition(vStartPos, domain); if(iStartNavMesh == NAVMESH_NAVMESH_INDEX_NONE) return NULL; CNavMesh * pStartNavMesh = CPathServer::GetNavMeshFromIndex(iStartNavMesh, domain); if(!pStartNavMesh) return NULL; const u32 iStartPolyIndex = pStartNavMesh->GetPolyBelowPoint(vStartPos + Vector3(0, 0, 1.0f), vTmp, 5.0f); if(iStartPolyIndex == NAVMESH_POLY_INDEX_NONE) return NULL; pStartPoly = pStartNavMesh->GetPoly(iStartPolyIndex); Assert(pStartPoly); Assert(pStartPoly->GetNavMeshIndex() != NAVMESH_INDEX_TESSELLATION); } if(pStartPoly && pStartPoly->GetIsDisabled()) return NULL; if(pStartPoly && pStartPoly->GetNavMeshIndex()==NAVMESH_INDEX_TESSELLATION) { const u32 iPoly = CPathServer::GetTessellationNavMesh()->GetPolyIndex(pStartPoly); TTessInfo * pTess = CPathServer::GetTessInfo(iPoly); Assert(pTess); CNavMesh * pOriginalMesh = CPathServer::GetNavMeshFromIndex(pTess->m_iNavMeshIndex, kNavDomainRegular); if(!pOriginalMesh) return NULL; pStartPoly = pOriginalMesh->GetPoly(pTess->m_iPolyIndex); } m_LosVars.Init(vStartPos, vLosDir, iLosFlags); const bool bIntersection = TestNavMeshLOS_R(pStartPoly, NULL, domain); if(bIntersection) { if(pvOut_IntersectionPos) *pvOut_IntersectionPos = m_LosVars.m_vIntersectionPosition; return m_LosVars.m_pPolyEndedUpon; } return NULL; } bool CPathServerThread::TestNavMeshLOS_R(TNavMeshPoly * pTestPoly, TNavMeshPoly * pLastPoly, aiNavDomain domain) { #if !__FINAL CPathServer::ms_iNumTestNavMeshLOS++; #endif m_LosVars.m_iTestLosStackNumEntries = 0; Vector3 vEdgeVert1, vEdgeVert2; u32 v; s32 lastv; u32 iTestAdjNavMesh; TAdjPoly * pAdjacency; RESTART_FOR_RECURSION: Assert(pTestPoly->GetNavMeshIndex()!=NAVMESH_INDEX_TESSELLATION); Assert(!pLastPoly || pLastPoly->GetNavMeshIndex()!=NAVMESH_INDEX_TESSELLATION); CNavMesh * pTestPolyNavMesh = CPathServer::GetNavMeshFromIndex(pTestPoly->GetNavMeshIndex(), domain); if(!pTestPolyNavMesh) return false; if(m_LosVars.m_pToPoly) { #if NAVMESH_OPTIMISATIONS_OFF Assert(!m_LosVars.m_pToPoly->GetIsDisabled()); Assert(!m_LosVars.m_pToPoly->GetIsDegenerateConnectionPoly()); Assert(m_LosVars.m_pToPoly->GetNavMeshIndex()!=NAVMESH_INDEX_TESSELLATION); #endif // In the case where we know the destination poly, then see if we've arrived there if(pTestPoly == m_LosVars.m_pToPoly && !pTestPoly->GetIsDegenerateConnectionPoly()) { // LINE OF SIGHT EXISTS m_LosVars.m_iTestLosStackNumEntries = 0; return true; } } else { // In cases where we have no destination poly, then check for an minmax intersection with the known endpos // versus this polygon. If this succeeds, then try a full polygon containment test. if(!pTestPoly->GetIsDegenerateConnectionPoly() && m_LosVars.m_EndPosMinMax.IntersectsXY(pTestPoly->m_MinMax)) { m_LosVars.m_vAboveEndPos.z = pTestPoly->m_MinMax.GetMaxZAsFloat() + 1.0f; m_LosVars.m_vBelowEndPos.z = pTestPoly->m_MinMax.GetMinZAsFloat() - 1.0f; const bool bIntersects = pTestPolyNavMesh->RayIntersectsPoly(m_LosVars.m_vAboveEndPos, m_LosVars.m_vBelowEndPos, pTestPoly, m_LosVars.m_vIntersectionPosition); if(bIntersects) { m_LosVars.m_iTestLosStackNumEntries = 0; m_LosVars.m_pPolyEndedUpon = pTestPoly; return true; } } } lastv = pTestPoly->GetNumVertices()-1; for(v=0; vGetNumVertices(); v++) { pAdjacency = pTestPolyNavMesh->GetAdjacentPolysArray().Get(pTestPoly->GetFirstVertexIndex() + lastv); iTestAdjNavMesh = pAdjacency->GetOriginalNavMeshIndex(pTestPolyNavMesh->GetAdjacentMeshes()); #if !__FINAL && __BANK // if (CPathServer::m_bVisualisePolygonRequests) // { // Vector3 vPosCenter; // pTestPolyNavMesh->GetPolyCentroidQuick(pTestPoly, vPosCenter); // grcDebugDraw::Line(vPosCenter, vPosCenter + ZAXIS * 5.f, Color_VioletRed, 10); // } #endif // We cannot complete a LOS over an adjacency which is a climb-up/drop-down etc. if(pAdjacency->GetOriginalPolyIndex() != NAVMESH_POLY_INDEX_NONE && pAdjacency->GetAdjacencyType() == ADJACENCY_TYPE_NORMAL) { // Check that this NavMesh exists & is loaded. (It's quite possible for the refinement algorithm to // visit polys which the poly-pathfinder didn't..) CNavMesh * pAdjMesh = CPathServer::GetNavMeshFromIndex(iTestAdjNavMesh, domain); if(!pAdjMesh) { lastv = v; continue; } TNavMeshPoly * pAdjPoly = pAdjMesh->GetPoly(pAdjacency->GetOriginalPolyIndex()); if(pAdjPoly->GetIsDisabled()) { #if !__FINAL && __BANK if (CPathServer::m_bVisualisePolygonRequests) { Vector3 vPosCenter; pAdjMesh->GetPolyCentroidQuick(pAdjPoly, vPosCenter); grcDebugDraw::Line(vPosCenter, vPosCenter + ZAXIS * 5.f, Color_red, 10); } #endif lastv = v; continue; } Assert(pAdjPoly->GetNavMeshIndex()!=NAVMESH_INDEX_TESSELLATION); #if __BREAK_UPON_PROCESSING_CONNECTION_POLY bool bPause = false; if(pAdjPoly->GetIsDegenerateConnectionPoly()) bPause = true; #endif // If this path is requested to keep to pavements, then don't allow a line-of-sight to // be clear when leaving a pavement poly onto a non-pavement poly - otherwise the // path may diverge from the pavement polygons if(m_pCurrentActiveRequest && !pAdjPoly->GetIsDegenerateConnectionPoly()) { if(m_LosVars.m_iLosFlags & CTestNavMeshLosVars::FLAG_NO_LOS_ACROSS_PAVEMENT_BOUNDARY) { if(pTestPoly->GetIsPavement() && !pAdjPoly->GetIsPavement()) { #if !__FINAL && __BANK if (CPathServer::m_bVisualisePolygonRequests) { Vector3 vPosCenter; pAdjMesh->GetPolyCentroidQuick(pAdjPoly, vPosCenter); grcDebugDraw::Line(vPosCenter, vPosCenter + ZAXIS * 5.f, Color_yellow, 10); } #endif lastv = v; continue; } } // Likewise for entering/exiting water if(m_LosVars.m_iLosFlags & CTestNavMeshLosVars::FLAG_NO_LOS_ACROSS_WATER_BOUNDARY) { if(pTestPoly->GetIsWater() != pAdjPoly->GetIsWater()) { #if !__FINAL && __BANK if (CPathServer::m_bVisualisePolygonRequests) { Vector3 vPosCenter; pAdjMesh->GetPolyCentroidQuick(pAdjPoly, vPosCenter); grcDebugDraw::Line(vPosCenter, vPosCenter + ZAXIS * 5.f, Color_copper, 10); } #endif lastv = v; continue; } } // And for transitioning to a steep surface from a non-steep surface if(m_LosVars.m_iLosFlags & CTestNavMeshLosVars::FLAG_NO_LOS_ACROSS_TOOSTEEP_BOUNDARY) { if(pAdjPoly->TestFlags(NAVMESHPOLY_TOO_STEEP_TO_WALK_ON) && !pTestPoly->TestFlags(NAVMESHPOLY_TOO_STEEP_TO_WALK_ON)) { #if !__FINAL && __BANK if (CPathServer::m_bVisualisePolygonRequests) { Vector3 vPosCenter; pAdjMesh->GetPolyCentroidQuick(pAdjPoly, vPosCenter); grcDebugDraw::Line(vPosCenter, vPosCenter + ZAXIS * 5.f, Color_orange, 10); } #endif lastv = v; continue; } } if(m_LosVars.m_iLosFlags & CTestNavMeshLosVars::FLAG_NO_LOS_ACROSS_MAXSLOPE_BOUNDARY) { if(pAdjPoly->m_MinMax.m_iMaxZ > pTestPoly->m_MinMax.m_iMaxZ) { // Get the normal of this pAdjPoly // NB: Hugely inefficient. This needs to be cached per-poly. Quake had an lookup-table of // normals, indexed by a single byte - we could do something similar, and quite possibly // at a lower resolution. Our normals' Z component will always be > 0. Vector3 vAdjPolyNormal; if(pAdjMesh->GetPolyNormal(vAdjPolyNormal, pAdjPoly)) { if(DotProduct(vAdjPolyNormal, ZAXIS) < m_Vars.m_fMinSlopeDotProductWithUpVector) { #if !__FINAL && __BANK if (CPathServer::m_bVisualisePolygonRequests) { Vector3 vPosCenter; pAdjMesh->GetPolyCentroidQuick(pAdjPoly, vPosCenter); grcDebugDraw::Line(vPosCenter, vPosCenter + ZAXIS * 5.f, Color_blue, 10); } #endif lastv = v; continue; } } } } } // Make sure we don't recurse back & forwards indefinitely // However, disregard the timestamp for the "pToPoly" because we don't want to screw our chances // of getting a decent LOS via another polygon if the "pToPoly" is adjacent to the "pFromPoly" // but happens to be not directly visible by the adjoining edge.. if(pAdjPoly != pLastPoly && (pAdjPoly->m_TimeStamp != m_iNavMeshPolyTimeStamp || pAdjPoly == m_LosVars.m_pToPoly)) { pTestPolyNavMesh->GetVertex( pTestPolyNavMesh->GetPolyVertexIndex(pTestPoly, lastv), vEdgeVert1); pTestPolyNavMesh->GetVertex( pTestPolyNavMesh->GetPolyVertexIndex(pTestPoly, v), vEdgeVert2); if(m_LosVars.m_iLosFlags & CTestNavMeshLosVars::FLAG_USE_ENTITY_RADIUS) { const float fDistToVert1 = geomDistances::DistanceSegToPoint( m_LosVars.m_vStartPos, m_LosVars.m_vLosVec, vEdgeVert1 ); const float fVertexSpace1 = pTestPolyNavMesh->GetAdjacentPoly( pTestPoly->GetFirstVertexIndex()+lastv ).GetFreeSpaceAroundVertex(); if(fDistToVert1 - m_LosVars.m_fEntityRadius + fVertexSpace1 < 0.0f) { #if !__FINAL && __BANK if (CPathServer::m_bVisualisePolygonRequests) { Vector3 vPosCenter; pAdjMesh->GetPolyCentroidQuick(pAdjPoly, vPosCenter); grcDebugDraw::Line(vPosCenter, vPosCenter + ZAXIS * 5.f, Color_white, 10); } #endif lastv = v; continue; } const float fDistToVert2 = geomDistances::DistanceSegToPoint( m_LosVars.m_vStartPos, m_LosVars.m_vLosVec, vEdgeVert2 ); const float fVertexSpace2 = pTestPolyNavMesh->GetAdjacentPoly(pTestPoly->GetFirstVertexIndex()+v).GetFreeSpaceAroundVertex(); if(fDistToVert2 - m_LosVars.m_fEntityRadius + fVertexSpace2 < 0.0f) { #if !__FINAL && __BANK if (CPathServer::m_bVisualisePolygonRequests) { Vector3 vPosCenter; pAdjMesh->GetPolyCentroidQuick(pAdjPoly, vPosCenter); grcDebugDraw::Line(vPosCenter, vPosCenter + ZAXIS * 5.f, Color_white, 10); } #endif lastv = v; continue; } } if(CNavMesh::LineSegsIntersect2D_LOS(m_LosVars.m_vStartPos, m_LosVars.m_vEndPos, vEdgeVert1, vEdgeVert2)) { pAdjPoly->m_TimeStamp = m_iNavMeshPolyTimeStamp; if(m_pCurrentActiveRequest && !pAdjPoly->GetIsDegenerateConnectionPoly()) { //************************************************************************************* // If this path is using influence spheres, then don't allow a line-of-sight to // cross a sphere's boundary (defined by its radius). We set a flag if we've // crossed a boundary, which we'll need to examine in the refinement code. // NB : It might not be sufficient to rely on this flag, since we might at this point // be visiting polys which we didn't visit during the path-search. So, we may need // to actually do a sphere/point containment test ): if(m_LosVars.m_iLosFlags & CTestNavMeshLosVars::FLAG_NO_LOS_ACROSS_INFLUENCESPHERES_BOUNDARY) { const bool bStartIsWithinSphere = (pTestPoly->TestFlags(NAVMESHPOLY_IS_WITHIN_INFLUENCE_SPHERE)); const bool bAdjacentIsWithinSphere = (pAdjPoly->TestFlags(NAVMESHPOLY_IS_WITHIN_INFLUENCE_SPHERE)); if(bStartIsWithinSphere != bAdjacentIsWithinSphere) { m_bLosHitInfluenceBoundary = true; } } // And for transitioning between interior/exterior navmesh if(m_LosVars.m_iLosFlags & CTestNavMeshLosVars::FLAG_NO_LOS_ACROSS_ACROSS_INTERIOR_EXTERIOR_BOUNDARY) { if(pAdjPoly->GetIsInterior() != pTestPoly->GetIsInterior()) { #if !__FINAL && __BANK if (CPathServer::m_bVisualisePolygonRequests) { Vector3 vPosCenter; pAdjMesh->GetPolyCentroidQuick(pAdjPoly, vPosCenter); grcDebugDraw::Line(vPosCenter, vPosCenter + ZAXIS * 5.f, Color_pink, 10); } #endif lastv = v; continue; } } // And for transitioning across sheltered/unsheltered boundaries if(m_LosVars.m_iLosFlags & CTestNavMeshLosVars::FLAG_NO_LOS_ACROSS_ACROSS_SHELTERED_BOUNDARY) { if(pAdjPoly->GetIsSheltered() != pTestPoly->GetIsSheltered()) { #if !__FINAL && __BANK if (CPathServer::m_bVisualisePolygonRequests) { Vector3 vPosCenter; pAdjMesh->GetPolyCentroidQuick(pAdjPoly, vPosCenter); grcDebugDraw::Line(vPosCenter, vPosCenter + ZAXIS * 5.f, Color_brown, 10); } #endif lastv = v; continue; } } } //**************************************************************** // This is the point of recursion in the original algorithm. // Instead of recursing, we'll store off the algorithm state in a // stack entry in "m_LosVars.m_TestLosStack", and will jump to the label // "RESTART_FOR_RECURSION". if(m_LosVars.m_iTestLosStackNumEntries >= SIZE_TEST_LOS_STACK) { #if NAVMESH_OPTIMISATIONS_OFF Assertf(m_LosVars.m_iTestLosStackNumEntries < SIZE_TEST_LOS_STACK, "TestNavMeshLOS recursion stack reached its limit of %i.", SIZE_TEST_LOS_STACK); #endif m_LosVars.m_iTestLosStackNumEntries = 0; return false; } { TTestLosStack & stackEntry = m_LosVars.m_TestLosStack[m_LosVars.m_iTestLosStackNumEntries]; m_LosVars.m_iTestLosStackNumEntries++; stackEntry.pTestPoly = pTestPoly; stackEntry.pLastPoly = pLastPoly; stackEntry.iVertex = v; // Set pTestPoly & pLastPoly to their new values, as would occur in recursive call. pTestPoly = pAdjPoly; pLastPoly = pTestPoly; } goto RESTART_FOR_RECURSION; } } } DROP_OUT_OF_RECURSION: ; #if !__FINAL && __BANK if (CPathServer::m_bVisualisePolygonRequests) { Vector3 vPosCenter; pTestPolyNavMesh->GetPolyCentroidQuick(pTestPoly, vPosCenter); grcDebugDraw::Line(vPosCenter, vPosCenter + ZAXIS * 5.f, Color_green, 10); } #endif lastv = v; } //******************************************************************** // This is the point at which we would drop out of recursion. If we // have any points of recursion stored in out stack, then re-enter // them now. if(m_LosVars.m_iTestLosStackNumEntries > 0) { m_LosVars.m_iTestLosStackNumEntries--; TTestLosStack & stackEntry = m_LosVars.m_TestLosStack[m_LosVars.m_iTestLosStackNumEntries]; pTestPoly = stackEntry.pTestPoly; pLastPoly = stackEntry.pLastPoly; v = stackEntry.iVertex; lastv = stackEntry.iVertex-1; if(lastv < 0) lastv = pTestPoly->GetNumVertices()-1; pTestPolyNavMesh = CPathServer::GetNavMeshFromIndex(pTestPoly->GetNavMeshIndex(), domain); goto DROP_OUT_OF_RECURSION; } m_LosVars.m_iTestLosStackNumEntries = 0; return false; } //****************************************************************************************** // Similar to TestNavMeshLOS except finds the first change in slope of the navmesh // underneath the line vStartPos to vEndPos. // pToPoly is the poly upon which the vEndPos is situated // when first called pTestPoly will be the poly upon which vStartPos is situated // These variables are global to reduce stack overhead on recursion. // We may need to do the "non re-entrant" treatment on this function.. Vector3 g_vFindHeightChange_StartPos(0.0f,0.0f,0.0f); Vector3 g_vFindHeightChange_EndPos(0.0f,0.0f,0.0f); Vector3 g_vFindHeightChange_Vec(0.0f,0.0f,0.0f); const TNavMeshPoly * g_pFindHeightChange_ToPoly = NULL; TNavMeshPoly * g_pFindHeightChange_OutHeightChangePoly = NULL; Vector3 g_vFindHeightChange_HeightChangePos(0.0f,0.0f,0.0f); int g_iFindHeightChange_NumRecursions = 0; bool g_bFindHeightChange_OnlyUphill = false; // set to true when underwater static const int g_iFindHeightChange_MaxNumRecursions = 20; bool CPathServerThread::FindHeightChangePosAlongLine(const Vector3 & vStartPos, const Vector3 & vEndPos, const TNavMeshPoly * pToPoly, TNavMeshPoly * pTestPoly, TNavMeshPoly *& pHeightChangePoly, Vector3 & vHeightChangePos, bool bOnlyUphill, aiNavDomain domain) { Assert(pToPoly && pToPoly->GetNavMeshIndex()!=NAVMESH_INDEX_TESSELLATION); Assert(pTestPoly && pTestPoly->GetNavMeshIndex()!=NAVMESH_INDEX_TESSELLATION); Assert(pHeightChangePoly==NULL || pHeightChangePoly->GetNavMeshIndex()!=NAVMESH_INDEX_TESSELLATION); g_vFindHeightChange_StartPos = vStartPos; g_vFindHeightChange_EndPos = vEndPos; g_pFindHeightChange_ToPoly = pToPoly; g_vFindHeightChange_Vec = vEndPos - vStartPos; g_bFindHeightChange_OnlyUphill = bOnlyUphill; g_iFindHeightChange_NumRecursions = 0; if(!g_vFindHeightChange_Vec.Mag2()) return false; g_vFindHeightChange_Vec.Normalize(); g_pFindHeightChange_OutHeightChangePoly = NULL; const bool bRet = FindHeightChangePosAlongLineR(pTestPoly, NULL, domain); pHeightChangePoly = g_pFindHeightChange_OutHeightChangePoly; vHeightChangePos = g_vFindHeightChange_HeightChangePos; return bRet; } bool CPathServerThread::FindHeightChangePosAlongLineR(TNavMeshPoly * pTestPoly, TNavMeshPoly * pLastPoly, aiNavDomain domain) { Assert(!pTestPoly || pTestPoly->GetNavMeshIndex()!=NAVMESH_INDEX_TESSELLATION); Assert(!pLastPoly || pLastPoly->GetNavMeshIndex()!=NAVMESH_INDEX_TESSELLATION); g_iFindHeightChange_NumRecursions++; if(pTestPoly == g_pFindHeightChange_ToPoly || !pTestPoly || !g_pFindHeightChange_ToPoly) { return false; } static dev_float fMaxHeightChange = 0.5f; u32 v, lastv; int bIsectRet; Vector3 vEdgeVert1, vEdgeVert2, vIsectPos; const CNavMesh * pTestPolyNavMesh = CPathServer::GetNavMeshFromIndex(pTestPoly->GetNavMeshIndex(), domain); if(pTestPolyNavMesh) { lastv = pTestPoly->GetNumVertices()-1; for(v=0; vGetNumVertices(); v++) { const TAdjPoly & adjPoly = pTestPolyNavMesh->GetAdjacentPoly(pTestPoly->GetFirstVertexIndex() + lastv); const u32 iTestAdjNavMesh = adjPoly.GetOriginalNavMeshIndex(pTestPolyNavMesh->GetAdjacentMeshes()); // We cannot complete a LOS over an adjacency which is a climb-up/drop-down etc. if(adjPoly.GetOriginalPolyIndex() != NAVMESH_POLY_INDEX_NONE && adjPoly.GetAdjacencyType() == ADJACENCY_TYPE_NORMAL) { // Check that this NavMesh exists & is loaded. (It's quite possible for the refinement algorithm to // visit polys which the poly-pathfinder didn't..) //const CNavMesh * pAdjMesh = CPathServer::GetNavMeshFromIndex(adjPoly.GetNavMeshIndex()); const CNavMesh * pAdjMesh = CPathServer::GetNavMeshFromIndex(iTestAdjNavMesh, domain); if(!pAdjMesh) { lastv = v; continue; } Assert(pAdjMesh->GetIndexOfMesh() != NAVMESH_INDEX_TESSELLATION); TNavMeshPoly * pAdjPoly = pAdjMesh->GetPoly(adjPoly.GetOriginalPolyIndex()); if(pAdjPoly->GetIsDisabled()) { lastv = v; continue; } // Make sure we don't recurse back & forwards indefinitely. // However, disregard the timestamp for the "pToPoly" because we don't want to screw our chances // of getting a decent LOS via another polygon if the "pToPoly" is adjacent to the "pFromPoly" // but happens to be not directly visible by the adjoining edge.. if(pAdjPoly != pLastPoly && (pAdjPoly->m_TimeStamp != m_iNavMeshPolyTimeStamp || pAdjPoly == g_pFindHeightChange_ToPoly)) { pTestPolyNavMesh->GetVertex( pTestPolyNavMesh->GetPolyVertexIndex(pTestPoly, lastv), vEdgeVert1); pTestPolyNavMesh->GetVertex( pTestPolyNavMesh->GetPolyVertexIndex(pTestPoly, v), vEdgeVert2); //********************************************************************** // Get the intersection between this polygon edge and the path segment bIsectRet = CNavMesh::LineSegsIntersect2D(g_vFindHeightChange_StartPos, g_vFindHeightChange_EndPos, vEdgeVert1, vEdgeVert2, &vIsectPos); if(bIsectRet == SEGMENTS_INTERSECT) { pAdjPoly->m_TimeStamp = m_iNavMeshPolyTimeStamp; // Get the distance of this intersection point along the line. // Find the height of the line at this point Vector3 vFromStart = vIsectPos - g_vFindHeightChange_StartPos; vFromStart.z = 0.0f; float fDistAlong = vFromStart.Mag(); const Vector3 vLinePosAtIntersection = g_vFindHeightChange_StartPos + (g_vFindHeightChange_Vec * fDistAlong); const float fZDiff = vIsectPos.z - vLinePosAtIntersection.z; if(fZDiff > fMaxHeightChange) { g_pFindHeightChange_OutHeightChangePoly = pAdjPoly; g_vFindHeightChange_HeightChangePos = vIsectPos; return true; } else if (!g_bFindHeightChange_OnlyUphill && fZDiff < -fMaxHeightChange) { g_pFindHeightChange_OutHeightChangePoly = pAdjPoly; g_vFindHeightChange_HeightChangePos = vIsectPos; return true; } else if(g_iFindHeightChange_NumRecursions > g_iFindHeightChange_MaxNumRecursions) { g_pFindHeightChange_OutHeightChangePoly = pAdjPoly; g_vFindHeightChange_HeightChangePos = vIsectPos; return true; } if(FindHeightChangePosAlongLineR(pAdjPoly, pTestPoly, domain)) { return true; } g_iFindHeightChange_NumRecursions--; } } } lastv = v; } } return false; }