// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "NavMesh/RecastNavMesh.h" #include "Misc/Paths.h" #include "EngineGlobals.h" #include "Engine/World.h" #include "NavigationSystem.h" #include "Engine/Engine.h" #include "DrawDebugHelpers.h" #include "Misc/ConfigCacheIni.h" #include "EngineUtils.h" #include "NavMesh/RecastHelpers.h" #include "NavMesh/RecastVersion.h" #include "NavAreas/NavArea.h" #include "NavAreas/NavArea_Null.h" #include "NavAreas/NavArea_Default.h" #include "NavAreas/NavArea_LowHeight.h" #include "NavLinkCustomInterface.h" #include "NavMesh/RecastNavMeshDataChunk.h" #include "NavMesh/RecastQueryFilter.h" #include "VisualLogger/VisualLogger.h" #if WITH_EDITOR #include "ObjectEditorUtils.h" #endif #if WITH_RECAST #include "Detour/DetourAlloc.h" #endif // WITH_RECAST #include "NavMesh/NavMeshRenderingComponent.h" #if WITH_RECAST /// Helper for accessing navigation query from different threads #define INITIALIZE_NAVQUERY(NavQueryVariable, NumNodes) \ dtNavMeshQuery NavQueryVariable##Private; \ dtNavMeshQuery& NavQueryVariable = IsInGameThread() ? RecastNavMeshImpl->SharedNavQuery : NavQueryVariable##Private; \ NavQueryVariable.init(RecastNavMeshImpl->DetourNavMesh, NumNodes); #define INITIALIZE_NAVQUERY_WLINKFILTER(NavQueryVariable, NumNodes, LinkFilter) \ dtNavMeshQuery NavQueryVariable##Private; \ dtNavMeshQuery& NavQueryVariable = IsInGameThread() ? RecastNavMeshImpl->SharedNavQuery : NavQueryVariable##Private; \ NavQueryVariable.init(RecastNavMeshImpl->DetourNavMesh, NumNodes, &LinkFilter); #endif // WITH_RECAST static const int32 ArbitraryMaxVoxelTileSize = 1024; FNavMeshTileData::FNavData::~FNavData() { #if WITH_RECAST dtFree(RawNavData); #else FMemory::Free(RawNavData); #endif // WITH_RECAST } FNavMeshTileData::FNavMeshTileData(uint8* RawData, int32 RawDataSize, int32 LayerIdx, FBox LayerBounds) : LayerIndex(LayerIdx) , LayerBBox(LayerBounds) , DataSize(RawDataSize) { INC_MEMORY_STAT_BY(STAT_Navigation_TileCacheMemory, DataSize); NavData = MakeShareable(new FNavData(RawData)); } FNavMeshTileData::~FNavMeshTileData() { if (NavData.IsUnique() && NavData->RawNavData) { DEC_MEMORY_STAT_BY(STAT_Navigation_TileCacheMemory, DataSize); } } uint8* FNavMeshTileData::Release() { uint8* RawData = nullptr; if (NavData.IsValid() && NavData->RawNavData) { RawData = NavData->RawNavData; NavData->RawNavData = nullptr; DEC_MEMORY_STAT_BY(STAT_Navigation_TileCacheMemory, DataSize); } DataSize = 0; LayerIndex = 0; return RawData; } void FNavMeshTileData::MakeUnique() { if (DataSize > 0 && !NavData.IsUnique()) { INC_MEMORY_STAT_BY(STAT_Navigation_TileCacheMemory, DataSize); #if WITH_RECAST uint8* UniqueRawData = (uint8*)dtAlloc(sizeof(uint8)*DataSize, DT_ALLOC_PERM); #else uint8* UniqueRawData = (uint8*)FMemory::Malloc(sizeof(uint8)*DataSize); #endif //WITH_RECAST FMemory::Memcpy(UniqueRawData, NavData->RawNavData, DataSize); NavData = MakeShareable(new FNavData(UniqueRawData)); } } float ARecastNavMesh::DrawDistanceSq = 0.0f; #if !WITH_RECAST ARecastNavMesh::ARecastNavMesh(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } void ARecastNavMesh::Serialize( FArchive& Ar ) { Super::Serialize(Ar); uint32 NavMeshVersion; Ar << NavMeshVersion; //@todo: How to handle loading nav meshes saved w/ recast when recast isn't present???? // when writing, write a zero here for now. will come back and fill it in later. uint32 RecastNavMeshSizeBytes = 0; int64 RecastNavMeshSizePos = Ar.Tell(); Ar << RecastNavMeshSizeBytes; if (Ar.IsLoading()) { // incompatible, just skip over this data. navmesh needs rebuilt. Ar.Seek( RecastNavMeshSizePos + RecastNavMeshSizeBytes ); // Mark self for delete CleanUpAndMarkPendingKill(); } } #else // WITH_RECAST #include "Detour/DetourNavMesh.h" #include "Detour/DetourNavMeshQuery.h" #include "NavMesh/PImplRecastNavMesh.h" #include "NavMesh/RecastNavMeshGenerator.h" //----------------------------------------------------------------------// // FRecastDebugGeometry //----------------------------------------------------------------------// uint32 FRecastDebugGeometry::GetAllocatedSize() const { uint32 Size = sizeof(*this) + MeshVerts.GetAllocatedSize() + BuiltMeshIndices.GetAllocatedSize() + PolyEdges.GetAllocatedSize() + NavMeshEdges.GetAllocatedSize() + OffMeshLinks.GetAllocatedSize() + OffMeshSegments.GetAllocatedSize() + Clusters.GetAllocatedSize() + ClusterLinks.GetAllocatedSize(); for (int i = 0; i < RECAST_MAX_AREAS; ++i) { Size += AreaIndices[i].GetAllocatedSize(); } for (int i = 0; i < Clusters.Num(); ++i) { Size += Clusters[i].MeshIndices.GetAllocatedSize(); } return Size; } //----------------------------------------------------------------------// // ARecastNavMesh //----------------------------------------------------------------------// namespace ERecastNamedFilter { FRecastQueryFilter FilterOutNavLinksImpl; FRecastQueryFilter FilterOutAreasImpl; FRecastQueryFilter FilterOutNavLinksAndAreasImpl; } const FRecastQueryFilter* ARecastNavMesh::NamedFilters[] = { &ERecastNamedFilter::FilterOutNavLinksImpl , &ERecastNamedFilter::FilterOutAreasImpl , &ERecastNamedFilter::FilterOutNavLinksAndAreasImpl }; namespace FNavMeshConfig { ARecastNavMesh::FNavPolyFlags NavLinkFlag = ARecastNavMesh::FNavPolyFlags(0); FRecastNamedFiltersCreator::FRecastNamedFiltersCreator(bool bVirtualFilters) { // setting up the last bit available in dtPoly::flags NavLinkFlag = ARecastNavMesh::FNavPolyFlags(1 << (sizeof(((dtPoly*)0)->flags) * 8 - 1)); ERecastNamedFilter::FilterOutNavLinksImpl.SetIsVirtual(bVirtualFilters); ERecastNamedFilter::FilterOutAreasImpl.SetIsVirtual(bVirtualFilters); ERecastNamedFilter::FilterOutNavLinksAndAreasImpl.SetIsVirtual(bVirtualFilters); ERecastNamedFilter::FilterOutNavLinksImpl.setExcludeFlags(NavLinkFlag); ERecastNamedFilter::FilterOutNavLinksAndAreasImpl.setExcludeFlags(NavLinkFlag); for (int32 AreaID = 0; AreaID < RECAST_MAX_AREAS; ++AreaID) { ERecastNamedFilter::FilterOutAreasImpl.setAreaCost(AreaID, RECAST_UNWALKABLE_POLY_COST); ERecastNamedFilter::FilterOutNavLinksAndAreasImpl.setAreaCost(AreaID, RECAST_UNWALKABLE_POLY_COST); } ERecastNamedFilter::FilterOutAreasImpl.setAreaCost(RECAST_DEFAULT_AREA, 1.f); ERecastNamedFilter::FilterOutNavLinksAndAreasImpl.setAreaCost(RECAST_DEFAULT_AREA, 1.f); } } ARecastNavMesh::FNavPolyFlags ARecastNavMesh::NavLinkFlag = ARecastNavMesh::FNavPolyFlags(0); ARecastNavMesh::ARecastNavMesh(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , bDrawFilledPolys(true) , bDrawNavMeshEdges(true) , bDrawNavLinks(true) , bDrawOctreeDetails(true) , bDrawMarkedForbiddenPolys(false) , bDistinctlyDrawTilesBeingBuilt(true) , bDrawNavMesh(true) , DrawOffset(10.f) , TilePoolSize(1024) , MaxSimplificationError(1.3f) // from RecastDemo , DefaultMaxSearchNodes(RECAST_MAX_SEARCH_NODES) , DefaultMaxHierarchicalSearchNodes(RECAST_MAX_SEARCH_NODES) , bPerformVoxelFiltering(true) , bMarkLowHeightAreas(false) , bFilterLowSpanSequences(false) , bFilterLowSpanFromTileCache(false) , bStoreEmptyTileLayers(false) , bUseVirtualFilters(true) , bAllowNavLinkAsPathEnd(false) , TileSetUpdateInterval(1.0f) , NavMeshVersion(NAVMESHVER_LATEST) , RecastNavMeshImpl(NULL) { HeuristicScale = 0.999f; RegionPartitioning = ERecastPartitioning::Watershed; LayerPartitioning = ERecastPartitioning::Watershed; RegionChunkSplits = 2; LayerChunkSplits = 2; MaxSimultaneousTileGenerationJobsCount = 1024; bDoFullyAsyncNavDataGathering = false; TileNumberHardLimit = 1 << 20; #if RECAST_ASYNC_REBUILDING BatchQueryCounter = 0; #endif // RECAST_ASYNC_REBUILDING if (HasAnyFlags(RF_ClassDefaultObject) == false) { INC_DWORD_STAT_BY( STAT_NavigationMemory, sizeof(*this) ); FindPathImplementation = FindPath; FindHierarchicalPathImplementation = FindPath; TestPathImplementation = TestPath; TestHierarchicalPathImplementation = TestHierarchicalPath; RaycastImplementation = NavMeshRaycast; RecastNavMeshImpl = new FPImplRecastNavMesh(this); // add predefined areas up front SupportedAreas.Add(FSupportedAreaData(UNavArea_Null::StaticClass(), RECAST_NULL_AREA)); SupportedAreas.Add(FSupportedAreaData(UNavArea_LowHeight::StaticClass(), RECAST_LOW_AREA)); SupportedAreas.Add(FSupportedAreaData(UNavArea_Default::StaticClass(), RECAST_DEFAULT_AREA)); } } ARecastNavMesh::~ARecastNavMesh() { if (HasAnyFlags(RF_ClassDefaultObject) == false) { DEC_DWORD_STAT_BY( STAT_NavigationMemory, sizeof(*this) ); DestroyRecastPImpl(); } } void ARecastNavMesh::DestroyRecastPImpl() { if (RecastNavMeshImpl != NULL) { delete RecastNavMeshImpl; RecastNavMeshImpl = NULL; } } ARecastNavMesh* ARecastNavMesh::SpawnInstance(UNavigationSystem* NavSys, const FNavDataConfig* AgentProps) { FActorSpawnParameters SpawnInfo; SpawnInfo.OverrideLevel = NavSys->GetWorld()->PersistentLevel; ARecastNavMesh* Instance = NavSys->GetWorld()->SpawnActor( SpawnInfo ); if (Instance != NULL && AgentProps != NULL) { Instance->SetConfig(*AgentProps); if (AgentProps->Name != NAME_None) { FString StrName = FString::Printf(TEXT("%s-%s"), *(Instance->GetFName().GetPlainNameString()), *(AgentProps->Name.ToString())); // temporary solution to make sure we don't try to change name while there's already // an object with this name UObject* ExistingObject = StaticFindObject(/*Class=*/ NULL, Instance->GetOuter(), *StrName, true); if (ExistingObject != NULL) { ExistingObject->Rename(NULL, NULL, REN_DontCreateRedirectors | REN_ForceGlobalUnique | REN_DoNotDirty | REN_NonTransactional); } // Set descriptive name Instance->Rename(*StrName); #if WITH_EDITOR Instance->SetActorLabel(StrName); #endif // WITH_EDITOR } } return Instance; } UPrimitiveComponent* ARecastNavMesh::ConstructRenderingComponent() { return NewObject(this, TEXT("NavRenderingComp"), RF_Transient); } void ARecastNavMesh::UpdateNavMeshDrawing() { #if !UE_BUILD_SHIPPING UNavMeshRenderingComponent* NavMeshRenderComp = Cast(RenderingComp); if (NavMeshRenderComp != nullptr && NavMeshRenderComp->bVisible && (NavMeshRenderComp->IsForcingUpdate() || UNavMeshRenderingComponent::IsNavigationShowFlagSet(GetWorld()))) { RenderingComp->MarkRenderStateDirty(); } #endif // UE_BUILD_SHIPPING } void ARecastNavMesh::CleanUp() { Super::CleanUp(); if (NavDataGenerator.IsValid()) { NavDataGenerator->CancelBuild(); NavDataGenerator.Reset(); } DestroyRecastPImpl(); } void ARecastNavMesh::PostLoad() { Super::PostLoad(); // @TODO tilesize validation. This is temporary and should get removed by 4.9 TileSizeUU = FMath::Clamp(TileSizeUU, CellSize, ArbitraryMaxVoxelTileSize * CellSize); RecreateDefaultFilter(); UpdatePolyRefBitsPreview(); } void ARecastNavMesh::PostRegisterAllComponents() { Super::PostRegisterAllComponents(); if (GetActorLocation().IsNearlyZero() == false) { ApplyWorldOffset(GetActorLocation(), /*unused*/false); } } void ARecastNavMesh::PostInitProperties() { if (HasAnyFlags(RF_ClassDefaultObject) == true) { SetDrawDistance(DefaultDrawDistance); static const FNavMeshConfig::FRecastNamedFiltersCreator RecastNamedFiltersCreator(bUseVirtualFilters); NavLinkFlag = FNavMeshConfig::NavLinkFlag; } UWorld* MyWorld = GetWorld(); if (MyWorld != nullptr && HasAnyFlags(RF_NeedLoad) // was loaded && FNavigationSystem::ShouldDiscardSubLevelNavData(*this)) { // get rid of instances saved within levels that are streamed-in if ((GEngine->IsSettingUpPlayWorld() == false) // this is a @HACK && (MyWorld->GetOutermost() != GetOutermost()) // If we are cooking, then let them all pass. // They will be handled at load-time when running. && (IsRunningCommandlet() == false)) { UE_LOG(LogNavigation, Log, TEXT("Discarding %s due to it not being part of PersistentLevel") , *GetNameSafe(this)); // marking self for deletion CleanUpAndMarkPendingKill(); } } Super::PostInitProperties(); TileSizeUU = FMath::Clamp(TileSizeUU, CellSize, ArbitraryMaxVoxelTileSize * CellSize); if (HasAnyFlags(RF_ClassDefaultObject | RF_NeedLoad) == false) { RecreateDefaultFilter(); } // voxel cache requires the same rasterization setup for all navmeshes, as it's stored in octree if (IsVoxelCacheEnabled() && !HasAnyFlags(RF_ClassDefaultObject)) { ARecastNavMesh* DefOb = (ARecastNavMesh*)ARecastNavMesh::StaticClass()->GetDefaultObject(); if (TileSizeUU != DefOb->TileSizeUU) { UE_LOG(LogNavigation, Warning, TEXT("%s param: TileSizeUU(%f) differs from config settings, forcing value %f so it can be used with voxel cache!"), *GetNameSafe(this), TileSizeUU, DefOb->TileSizeUU); TileSizeUU = DefOb->TileSizeUU; } if (CellSize != DefOb->CellSize) { UE_LOG(LogNavigation, Warning, TEXT("%s param: CellSize(%f) differs from config settings, forcing value %f so it can be used with voxel cache!"), *GetNameSafe(this), CellSize, DefOb->CellSize); CellSize = DefOb->CellSize; } if (CellHeight != DefOb->CellHeight) { UE_LOG(LogNavigation, Warning, TEXT("%s param: CellHeight(%f) differs from config settings, forcing value %f so it can be used with voxel cache!"), *GetNameSafe(this), CellHeight, DefOb->CellHeight); CellHeight = DefOb->CellHeight; } if (AgentMaxSlope != DefOb->AgentMaxSlope) { UE_LOG(LogNavigation, Warning, TEXT("%s param: AgentMaxSlope(%f) differs from config settings, forcing value %f so it can be used with voxel cache!"), *GetNameSafe(this), AgentMaxSlope, DefOb->AgentMaxSlope); AgentMaxSlope = DefOb->AgentMaxSlope; } if (AgentMaxStepHeight != DefOb->AgentMaxStepHeight) { UE_LOG(LogNavigation, Warning, TEXT("%s param: AgentMaxStepHeight(%f) differs from config settings, forcing value %f so it can be used with voxel cache!"), *GetNameSafe(this), AgentMaxStepHeight, DefOb->AgentMaxStepHeight); AgentMaxStepHeight = DefOb->AgentMaxStepHeight; } } UpdatePolyRefBitsPreview(); } void ARecastNavMesh::RecreateDefaultFilter() { DefaultQueryFilter->SetFilterType(); DefaultQueryFilter->SetMaxSearchNodes(DefaultMaxSearchNodes); FRecastQueryFilter* DetourFilter = static_cast(DefaultQueryFilter->GetImplementation()); DetourFilter->SetIsVirtual(bUseVirtualFilters); DetourFilter->setHeuristicScale(HeuristicScale); for (int32 Idx = 0; Idx < SupportedAreas.Num(); Idx++) { const FSupportedAreaData& AreaData = SupportedAreas[Idx]; UNavArea* DefArea = nullptr; if (AreaData.AreaClass) { DefArea = ((UClass*)AreaData.AreaClass)->GetDefaultObject(); } if (DefArea) { DetourFilter->SetAreaCost(AreaData.AreaID, DefArea->DefaultCost); DetourFilter->SetFixedAreaEnteringCost(AreaData.AreaID, DefArea->GetFixedAreaEnteringCost()); } } } void ARecastNavMesh::UpdatePolyRefBitsPreview() { static const int32 TotalBits = (sizeof(dtPolyRef) * 8); FRecastNavMeshGenerator::CalcPolyRefBits(this, PolyRefTileBits, PolyRefNavPolyBits); PolyRefSaltBits = TotalBits - PolyRefTileBits - PolyRefNavPolyBits; } void ARecastNavMesh::OnNavAreaAdded(const UClass* NavAreaClass, int32 AgentIndex) { Super::OnNavAreaAdded(NavAreaClass, AgentIndex); // update navmesh query filter with area costs const int32 AreaID = GetAreaID(NavAreaClass); if (AreaID != INDEX_NONE) { UNavArea* DefArea = ((UClass*)NavAreaClass)->GetDefaultObject(); DefaultQueryFilter->SetAreaCost(AreaID, DefArea->DefaultCost); DefaultQueryFilter->SetFixedAreaEnteringCost(AreaID, DefArea->GetFixedAreaEnteringCost()); } // update generator's cached data FRecastNavMeshGenerator* MyGenerator = static_cast(GetGenerator()); if (MyGenerator) { MyGenerator->OnAreaAdded(NavAreaClass, AreaID); } } void ARecastNavMesh::OnNavAreaChanged() { if (RecastNavMeshImpl) { RecastNavMeshImpl->OnAreaCostChanged(); } } int32 ARecastNavMesh::GetNewAreaID(const UClass* AreaClass) const { if (AreaClass == FNavigationSystem::GetDefaultWalkableArea()) { return RECAST_DEFAULT_AREA; } if (AreaClass == UNavArea_Null::StaticClass()) { return RECAST_NULL_AREA; } if (AreaClass == UNavArea_LowHeight::StaticClass()) { return RECAST_LOW_AREA; } int32 FreeAreaID = Super::GetNewAreaID(AreaClass); while (FreeAreaID == RECAST_NULL_AREA || FreeAreaID == RECAST_DEFAULT_AREA || FreeAreaID == RECAST_LOW_AREA) { FreeAreaID++; } check(FreeAreaID < GetMaxSupportedAreas()); return FreeAreaID; } FColor ARecastNavMesh::GetAreaIDColor(uint8 AreaID) const { const UClass* AreaClass = GetAreaClass(AreaID); const UNavArea* DefArea = AreaClass ? ((UClass*)AreaClass)->GetDefaultObject() : NULL; return DefArea ? DefArea->DrawColor : FColor::Red; } void ARecastNavMesh::SortAreasForGenerator(TArray& Modifiers) const { // initialize costs for sorting float AreaCosts[RECAST_MAX_AREAS]; float AreaFixedCosts[RECAST_MAX_AREAS]; DefaultQueryFilter->GetAllAreaCosts(AreaCosts, AreaFixedCosts, RECAST_MAX_AREAS); for (auto& Element : Modifiers) { if (Element.Areas.Num()) { FAreaNavModifier& AreaMod = Element.Areas[0]; const int32 AreaId = GetAreaID(AreaMod.GetAreaClass()); if (AreaId >= 0 && AreaId < RECAST_MAX_AREAS) { AreaMod.Cost = AreaCosts[AreaId]; AreaMod.FixedCost = AreaFixedCosts[AreaId]; } } } struct FNavAreaSortPredicate { FORCEINLINE bool operator()(const FRecastAreaNavModifierElement& ElA, const FRecastAreaNavModifierElement& ElB) const { if (ElA.Areas.Num() == 0 || ElB.Areas.Num() == 0) { return ElA.Areas.Num() <= ElB.Areas.Num(); } // assuming composite modifiers has same area type const FAreaNavModifier& A = ElA.Areas[0]; const FAreaNavModifier& B = ElB.Areas[0]; const bool bIsAReplacing = (A.GetAreaClassToReplace() != NULL); const bool bIsBReplacing = (B.GetAreaClassToReplace() != NULL); if (bIsAReplacing != bIsBReplacing) { return bIsAReplacing; } return A.Cost != B.Cost ? A.Cost < B.Cost : A.FixedCost < B.FixedCost; } }; Modifiers.Sort(FNavAreaSortPredicate()); } TArray& ARecastNavMesh::GetActiveTiles() { FRecastNavMeshGenerator* MyGenerator = static_cast(GetGenerator()); check(MyGenerator); return MyGenerator->ActiveTiles; } void ARecastNavMesh::RestrictBuildingToActiveTiles(bool InRestrictBuildingToActiveTiles) { FRecastNavMeshGenerator* MyGenerator = static_cast(GetGenerator()); if (MyGenerator) { MyGenerator->RestrictBuildingToActiveTiles(InRestrictBuildingToActiveTiles); } } void ARecastNavMesh::SerializeRecastNavMesh(FArchive& Ar, FPImplRecastNavMesh*& NavMesh, int32 InNavMeshVersion) { if (!Ar.IsLoading() && NavMesh == NULL) { return; } if (Ar.IsLoading()) { // allocate if necessary if (RecastNavMeshImpl == NULL) { RecastNavMeshImpl = new FPImplRecastNavMesh(this); } } if (RecastNavMeshImpl) { RecastNavMeshImpl->Serialize(Ar, InNavMeshVersion); } } void ARecastNavMesh::Serialize( FArchive& Ar ) { Super::Serialize(Ar); Ar << NavMeshVersion; //@todo: How to handle loading nav meshes saved w/ recast when recast isn't present???? // when writing, write a zero here for now. will come back and fill it in later. uint32 RecastNavMeshSizeBytes = 0; int64 RecastNavMeshSizePos = Ar.Tell(); { #if WITH_EDITOR FArchive::FScopeSetDebugSerializationFlags S(Ar, DSF_IgnoreDiff); #endif Ar << RecastNavMeshSizeBytes; } if (Ar.IsLoading()) { if (NavMeshVersion < NAVMESHVER_MIN_COMPATIBLE) { // incompatible, just skip over this data. navmesh needs rebuilt. Ar.Seek( RecastNavMeshSizePos + RecastNavMeshSizeBytes ); // Mark self for delete CleanUpAndMarkPendingKill(); } else if (RecastNavMeshSizeBytes > 4) { SerializeRecastNavMesh(Ar, RecastNavMeshImpl, NavMeshVersion); #if !(UE_BUILD_SHIPPING) RequestDrawingUpdate(); #endif //!(UE_BUILD_SHIPPING) } else { // empty, just skip over this data Ar.Seek( RecastNavMeshSizePos + RecastNavMeshSizeBytes ); // if it's not getting filled it's better to just remove it if (RecastNavMeshImpl) { RecastNavMeshImpl->ReleaseDetourNavMesh(); } } } else { SerializeRecastNavMesh(Ar, RecastNavMeshImpl, NavMeshVersion); if (Ar.IsSaving()) { int64 CurPos = Ar.Tell(); RecastNavMeshSizeBytes = CurPos - RecastNavMeshSizePos; Ar.Seek(RecastNavMeshSizePos); Ar << RecastNavMeshSizeBytes; Ar.Seek(CurPos); } } } void ARecastNavMesh::SetConfig(const FNavDataConfig& Src) { NavDataConfig = Src; AgentMaxHeight = AgentHeight = Src.AgentHeight; AgentRadius = Src.AgentRadius; if (Src.HasStepHeightOverride()) { AgentMaxStepHeight = Src.AgentStepHeight; } } void ARecastNavMesh::FillConfig(FNavDataConfig& Dest) { Dest = NavDataConfig; Dest.AgentHeight = AgentHeight; Dest.AgentRadius = AgentRadius; Dest.AgentStepHeight = AgentMaxStepHeight; } void ARecastNavMesh::BeginBatchQuery() const { #if RECAST_ASYNC_REBUILDING // lock critical section when no other batch queries are active if (BatchQueryCounter <= 0) { BatchQueryCounter = 0; } BatchQueryCounter++; #endif // RECAST_ASYNC_REBUILDING } void ARecastNavMesh::FinishBatchQuery() const { #if RECAST_ASYNC_REBUILDING BatchQueryCounter--; #endif // RECAST_ASYNC_REBUILDING } FBox ARecastNavMesh::GetNavMeshBounds() const { FBox Bounds; if (RecastNavMeshImpl) { Bounds = RecastNavMeshImpl->GetNavMeshBounds(); } return Bounds; } FBox ARecastNavMesh::GetNavMeshTileBounds(int32 TileIndex) const { FBox Bounds; if (RecastNavMeshImpl) { Bounds = RecastNavMeshImpl->GetNavMeshTileBounds(TileIndex); } return Bounds; } bool ARecastNavMesh::GetNavMeshTileXY(int32 TileIndex, int32& OutX, int32& OutY, int32& OutLayer) const { return RecastNavMeshImpl && RecastNavMeshImpl->GetNavMeshTileXY(TileIndex, OutX, OutY, OutLayer); } bool ARecastNavMesh::GetNavMeshTileXY(const FVector& Point, int32& OutX, int32& OutY) const { return RecastNavMeshImpl && RecastNavMeshImpl->GetNavMeshTileXY(Point, OutX, OutY); } void ARecastNavMesh::GetNavMeshTilesAt(int32 TileX, int32 TileY, TArray& Indices) const { if (RecastNavMeshImpl) { RecastNavMeshImpl->GetNavMeshTilesAt(TileX, TileY, Indices); } } bool ARecastNavMesh::GetPolysInTile(int32 TileIndex, TArray& Polys) const { return RecastNavMeshImpl && RecastNavMeshImpl->GetPolysInTile(TileIndex, Polys); } bool ARecastNavMesh::GetNavLinksInTile(const int32 TileIndex, TArray& Polys, const bool bIncludeLinksFromNeighborTiles) const { if (RecastNavMeshImpl == nullptr || RecastNavMeshImpl->DetourNavMesh == nullptr || TileIndex < 0 || TileIndex >= RecastNavMeshImpl->DetourNavMesh->getMaxTiles()) { return false; } const dtNavMesh* DetourNavMesh = RecastNavMeshImpl->DetourNavMesh; const int32 InitialLinkCount = Polys.Num(); const dtMeshTile* Tile = DetourNavMesh->getTile(TileIndex); if (Tile && Tile->header) { const int32 LinkCount = Tile->header->offMeshConCount; if (LinkCount > 0) { const int32 BaseIdx = Polys.Num(); Polys.AddZeroed(LinkCount); const dtPoly* Poly = Tile->polys; for (int32 LinkIndex = 0; LinkIndex < LinkCount; ++LinkIndex, ++Poly) { FNavPoly& OutPoly = Polys[BaseIdx + LinkIndex]; const int32 PolyIndex = Tile->header->offMeshBase + LinkIndex; OutPoly.Ref = DetourNavMesh->encodePolyId(Tile->salt, TileIndex, PolyIndex); OutPoly.Center = (Recast2UnrealPoint(&Tile->verts[Poly->verts[0] * 3]) + Recast2UnrealPoint(&Tile->verts[Poly->verts[1] * 3])) / 2; } } if (bIncludeLinksFromNeighborTiles) { TArray NeighborTiles; NeighborTiles.Reserve(32); for (int32 SideIndex = 0; SideIndex < 8; ++SideIndex) { const int32 StartIndex = NeighborTiles.Num(); const int32 NeighborCount = DetourNavMesh->getNeighbourTilesCountAt(Tile->header->x, Tile->header->y, SideIndex); if (NeighborCount > 0) { const unsigned char oppositeSide = (unsigned char)dtOppositeTile(SideIndex); NeighborTiles.AddZeroed(NeighborCount); int32 NeighborX = Tile->header->x; int32 NeighborY = Tile->header->y; if (DetourNavMesh->getNeighbourCoords(Tile->header->x, Tile->header->y, SideIndex, NeighborX, NeighborY)) { DetourNavMesh->getTilesAt(NeighborX, NeighborY, NeighborTiles.GetData() + StartIndex, NeighborCount); } for (const dtMeshTile* NeighborTile : NeighborTiles) { if (NeighborTile && NeighborTile->header && NeighborTile->offMeshCons) { const dtTileRef NeighborTileId = DetourNavMesh->getTileRef(NeighborTile); for (int32 LinkIndex = 0; LinkIndex < NeighborTile->header->offMeshConCount; ++LinkIndex) { dtOffMeshConnection* targetCon = &NeighborTile->offMeshCons[LinkIndex]; if (targetCon->side != oppositeSide) { continue; } const unsigned char biDirFlag = targetCon->getBiDirectional() ? DT_LINK_FLAG_OFFMESH_CON_BIDIR : 0; const dtPoly* targetPoly = &NeighborTile->polys[targetCon->poly]; // Skip off-mesh connections which start location could not be connected at all. if (targetPoly->firstLink == DT_NULL_LINK) { continue; } FNavPoly& OutPoly = Polys[Polys.AddZeroed()]; OutPoly.Ref = NeighborTileId | targetCon->poly; OutPoly.Center = (Recast2UnrealPoint(&targetCon->pos[0]) + Recast2UnrealPoint(&targetCon->pos[3])) / 2; } } } NeighborTiles.Reset(); } } } } return (Polys.Num() - InitialLinkCount > 0); } int32 ARecastNavMesh::GetNavMeshTilesCount() const { int32 NumTiles = 0; if (RecastNavMeshImpl) { NumTiles = RecastNavMeshImpl->GetNavMeshTilesCount(); } return NumTiles; } void ARecastNavMesh::RemoveTileCacheLayers(int32 TileX, int32 TileY) { if (RecastNavMeshImpl) { RecastNavMeshImpl->RemoveTileCacheLayers(TileX, TileY); } } void ARecastNavMesh::AddTileCacheLayers(int32 TileX, int32 TileY, const TArray& InLayers) { if (RecastNavMeshImpl) { RecastNavMeshImpl->AddTileCacheLayers(TileX, TileY, InLayers); } } void ARecastNavMesh::MarkEmptyTileCacheLayers(int32 TileX, int32 TileY) { if (RecastNavMeshImpl && bStoreEmptyTileLayers) { RecastNavMeshImpl->MarkEmptyTileCacheLayers(TileX, TileY); } } TArray ARecastNavMesh::GetTileCacheLayers(int32 TileX, int32 TileY) const { if (RecastNavMeshImpl) { return RecastNavMeshImpl->GetTileCacheLayers(TileX, TileY); } return TArray(); } #if !UE_BUILD_SHIPPING int32 ARecastNavMesh::GetCompressedTileCacheSize() { return RecastNavMeshImpl ? RecastNavMeshImpl->GetCompressedTileCacheSize() : 0; } #endif bool ARecastNavMesh::IsResizable() const { return !bFixedTilePoolSize; } void ARecastNavMesh::GetEdgesForPathCorridor(const TArray* PathCorridor, TArray* PathCorridorEdges) const { check(PathCorridor != NULL && PathCorridorEdges != NULL); if (RecastNavMeshImpl) { RecastNavMeshImpl->GetEdgesForPathCorridor(PathCorridor, PathCorridorEdges); } } FNavLocation ARecastNavMesh::GetRandomPoint(FSharedConstNavQueryFilter Filter, const UObject* QueryOwner) const { FNavLocation RandomPt; if (RecastNavMeshImpl) { RandomPt = RecastNavMeshImpl->GetRandomPoint(GetRightFilterRef(Filter), QueryOwner); } return RandomPt; } bool ARecastNavMesh::GetRandomReachablePointInRadius(const FVector& Origin, float Radius, FNavLocation& OutResult, FSharedConstNavQueryFilter Filter, const UObject* QueryOwner) const { if (RecastNavMeshImpl == nullptr || RecastNavMeshImpl->DetourNavMesh == nullptr || Radius <= 0.f) { return false; } const FNavigationQueryFilter& FilterInstance = GetRightFilterRef(Filter); FRecastSpeciaLinkFilter LinkFilter(FNavigationSystem::GetCurrent(GetWorld()), QueryOwner); INITIALIZE_NAVQUERY_WLINKFILTER(NavQuery, FilterInstance.GetMaxSearchNodes(), LinkFilter); // inits to "pass all" const dtQueryFilter* QueryFilter = (static_cast(FilterInstance.GetImplementation()))->GetAsDetourQueryFilter(); ensure(QueryFilter); if (QueryFilter) { // find starting poly // convert start/end pos to Recast coords const float Extent[3] = { Radius, Radius, Radius }; const FVector RecastOrigin = Unreal2RecastPoint(Origin); NavNodeRef OriginPolyID = INVALID_NAVNODEREF; NavQuery.findNearestPoly(&RecastOrigin.X, Extent, QueryFilter, &OriginPolyID, nullptr); dtPolyRef Poly; float RandPt[3]; dtStatus Status = NavQuery.findRandomPointAroundCircle(OriginPolyID, &RecastOrigin.X, Radius , QueryFilter, FMath::FRand, &Poly, RandPt); if (dtStatusSucceed(Status)) { OutResult = FNavLocation(Recast2UnrealPoint(RandPt), Poly); return true; } else { OutResult = FNavLocation(Origin, OriginPolyID); } } return false; } bool ARecastNavMesh::GetRandomPointInNavigableRadius(const FVector& Origin, float Radius, FNavLocation& OutResult, FSharedConstNavQueryFilter Filter, const UObject* Querier) const { const FVector ProjectionExtent(NavDataConfig.DefaultQueryExtent.X, NavDataConfig.DefaultQueryExtent.Y, BIG_NUMBER); OutResult = FNavLocation(FNavigationSystem::InvalidLocation); const float RandomAngle = 2.f * PI * FMath::FRand(); const float U = FMath::FRand() + FMath::FRand(); const float RandomRadius = Radius * (U > 1 ? 2.f - U : U); const FVector RandomOffset(FMath::Cos(RandomAngle) * RandomRadius, FMath::Sin(RandomAngle) * RandomRadius, 0); FVector RandomLocationInRadius = Origin + RandomOffset; // naive implementation ProjectPoint(RandomLocationInRadius, OutResult, ProjectionExtent, Filter); // if failed get a list of all nav polys in the area and do it the hard way if (OutResult.HasNodeRef() == false && RecastNavMeshImpl) { const float RadiusSq = FMath::Square(Radius); TArray Polys; const FVector FallbackExtent(Radius, Radius, BIG_NUMBER); const FBox Box(Origin - FallbackExtent, Origin + FallbackExtent); GetPolysInBox(Box, Polys, Filter, Querier); // @todo extremely naive implementation, barely random. To be improved while (Polys.Num() > 0) { const int32 RandomIndex = FMath::RandHelper(Polys.Num()); const FNavPoly& Poly = Polys[RandomIndex]; FVector PointOnPoly(0); if (RecastNavMeshImpl->GetClosestPointOnPoly(Poly.Ref, Origin, PointOnPoly) && FVector::DistSquared(PointOnPoly, Origin) < RadiusSq) { OutResult = FNavLocation(PointOnPoly, Poly.Ref); break; } Polys.RemoveAtSwap(RandomIndex, 1, /*bAllowShrinking=*/false); } } return OutResult.HasNodeRef() == true; } bool ARecastNavMesh::GetRandomPointInCluster(NavNodeRef ClusterRef, FNavLocation& OutLocation) const { return RecastNavMeshImpl && RecastNavMeshImpl->GetRandomPointInCluster(ClusterRef, OutLocation); } NavNodeRef ARecastNavMesh::GetClusterRef(NavNodeRef PolyRef) const { NavNodeRef ClusterRef = 0; if (RecastNavMeshImpl) { ClusterRef = RecastNavMeshImpl->GetClusterRefFromPolyRef(PolyRef); } return ClusterRef; } bool ARecastNavMesh::ProjectPoint(const FVector& Point, FNavLocation& OutLocation, const FVector& Extent, FSharedConstNavQueryFilter Filter, const UObject* QueryOwner) const { bool bSuccess = false; if (RecastNavMeshImpl) { bSuccess = RecastNavMeshImpl->ProjectPointToNavMesh(Point, OutLocation, Extent, GetRightFilterRef(Filter), QueryOwner); } return bSuccess; } void ARecastNavMesh::BatchProjectPoints(TArray& Workload, const FVector& Extent, FSharedConstNavQueryFilter Filter, const UObject* Querier) const { if (Workload.Num() == 0 || RecastNavMeshImpl == NULL || RecastNavMeshImpl->DetourNavMesh == NULL) { return; } const FNavigationQueryFilter& FilterToUse = GetRightFilterRef(Filter); FRecastSpeciaLinkFilter LinkFilter(FNavigationSystem::GetCurrent(GetWorld()), Querier); INITIALIZE_NAVQUERY_WLINKFILTER(NavQuery, FilterToUse.GetMaxSearchNodes(), LinkFilter); const dtQueryFilter* QueryFilter = static_cast(FilterToUse.GetImplementation())->GetAsDetourQueryFilter(); if (ensure(QueryFilter)) { const FVector ModifiedExtent = GetModifiedQueryExtent(Extent); FVector RcExtent = Unreal2RecastPoint(ModifiedExtent).GetAbs(); float ClosestPoint[3]; dtPolyRef PolyRef; for (int32 Idx = 0; Idx < Workload.Num(); Idx++) { FVector RcPoint = Unreal2RecastPoint(Workload[Idx].Point); if (Workload[Idx].bHintProjection2D) { NavQuery.findNearestPoly2D(&RcPoint.X, &RcExtent.X, QueryFilter, &PolyRef, ClosestPoint); } else { NavQuery.findNearestPoly(&RcPoint.X, &RcExtent.X, QueryFilter, &PolyRef, ClosestPoint); } // one last step required due to recast's BVTree imprecision if (PolyRef > 0) { const FVector& UnrealClosestPoint = Recast2UnrealPoint(ClosestPoint); if (FVector::DistSquared(UnrealClosestPoint, Workload[Idx].Point) <= ModifiedExtent.SizeSquared()) { Workload[Idx].OutLocation = FNavLocation(UnrealClosestPoint, PolyRef); Workload[Idx].bResult = true; } } } } } void ARecastNavMesh::BatchProjectPoints(TArray& Workload, FSharedConstNavQueryFilter Filter, const UObject* Querier) const { if (Workload.Num() == 0 || RecastNavMeshImpl == NULL || RecastNavMeshImpl->DetourNavMesh == NULL) { return; } const FNavigationQueryFilter& FilterToUse = GetRightFilterRef(Filter); FRecastSpeciaLinkFilter LinkFilter(FNavigationSystem::GetCurrent(GetWorld()), Querier); INITIALIZE_NAVQUERY_WLINKFILTER(NavQuery, FilterToUse.GetMaxSearchNodes(), LinkFilter); const dtQueryFilter* QueryFilter = static_cast(FilterToUse.GetImplementation())->GetAsDetourQueryFilter(); if (ensure(QueryFilter)) { float ClosestPoint[3]; dtPolyRef PolyRef; for (FNavigationProjectionWork& Work : Workload) { ensure(Work.ProjectionLimit.IsValid); const FVector RcReferencePoint = Unreal2RecastPoint(Work.Point); const FVector ModifiedExtent = GetModifiedQueryExtent(Work.ProjectionLimit.GetExtent()); const FVector RcExtent = Unreal2RecastPoint(ModifiedExtent).GetAbs(); const FVector RcBoxCenter = Unreal2RecastPoint(Work.ProjectionLimit.GetCenter()); if (Work.bHintProjection2D) { NavQuery.findNearestPoly2D(&RcBoxCenter.X, &RcExtent.X, QueryFilter, &PolyRef, ClosestPoint, &RcReferencePoint.X); } else { NavQuery.findNearestPoly(&RcBoxCenter.X, &RcExtent.X, QueryFilter, &PolyRef, ClosestPoint, &RcReferencePoint.X); } // one last step required due to recast's BVTree imprecision if (PolyRef > 0) { const FVector& UnrealClosestPoint = Recast2UnrealPoint(ClosestPoint); if (FVector::DistSquared(UnrealClosestPoint, Work.Point) <= ModifiedExtent.SizeSquared()) { Work.OutLocation = FNavLocation(UnrealClosestPoint, PolyRef); Work.bResult = true; } } } } } bool ARecastNavMesh::GetPolysInBox(const FBox& Box, TArray& Polys, FSharedConstNavQueryFilter Filter, const UObject* InOwner) const { // sanity check if (RecastNavMeshImpl->GetRecastMesh() == NULL) { return false; } bool bSuccess = false; const FNavigationQueryFilter& FilterToUse = GetRightFilterRef(Filter); FRecastSpeciaLinkFilter LinkFilter(FNavigationSystem::GetCurrent(GetWorld()), InOwner); INITIALIZE_NAVQUERY_WLINKFILTER(NavQuery, FilterToUse.GetMaxSearchNodes(), LinkFilter); const dtQueryFilter* QueryFilter = ((const FRecastQueryFilter*)(FilterToUse.GetImplementation()))->GetAsDetourQueryFilter(); ensure(QueryFilter); if (QueryFilter) { const FVector ModifiedExtent = GetModifiedQueryExtent(Box.GetExtent()); const FVector RcPoint = Unreal2RecastPoint( Box.GetCenter() ); const FVector RcExtent = Unreal2RecastPoint( ModifiedExtent ).GetAbs(); const int32 MaxHitPolys = 256; dtPolyRef HitPolys[MaxHitPolys]; int32 NumHitPolys = 0; dtStatus status = NavQuery.queryPolygons(&RcPoint.X, &RcExtent.X, QueryFilter, HitPolys, &NumHitPolys, MaxHitPolys); if (dtStatusSucceed(status)) { // only ground type polys int32 BaseIdx = Polys.Num(); Polys.AddZeroed(NumHitPolys); for (int32 i = 0; i < NumHitPolys; i++) { dtPoly const* Poly; dtMeshTile const* Tile; dtStatus Status = RecastNavMeshImpl->GetRecastMesh()->getTileAndPolyByRef(HitPolys[i], &Tile, &Poly); if (dtStatusSucceed(Status)) { FVector PolyCenter(0); for (int k = 0; k < Poly->vertCount; ++k) { PolyCenter += Recast2UnrealPoint(&Tile->verts[Poly->verts[k]*3]); } PolyCenter /= Poly->vertCount; FNavPoly& OutPoly = Polys[BaseIdx + i]; OutPoly.Ref = HitPolys[i]; OutPoly.Center = PolyCenter; } } bSuccess = true; } } return bSuccess; } bool ARecastNavMesh::ProjectPointMulti(const FVector& Point, TArray& OutLocations, const FVector& Extent, float MinZ, float MaxZ, FSharedConstNavQueryFilter Filter, const UObject* QueryOwner) const { return RecastNavMeshImpl && RecastNavMeshImpl->ProjectPointMulti(Point, OutLocations, Extent, MinZ, MaxZ, GetRightFilterRef(Filter), QueryOwner); } ENavigationQueryResult::Type ARecastNavMesh::CalcPathCost(const FVector& PathStart, const FVector& PathEnd, float& OutPathCost, FSharedConstNavQueryFilter QueryFilter, const UObject* QueryOwner) const { float TmpPathLength = 0.f; ENavigationQueryResult::Type Result = CalcPathLengthAndCost(PathStart, PathEnd, TmpPathLength, OutPathCost, QueryFilter, QueryOwner); return Result; } ENavigationQueryResult::Type ARecastNavMesh::CalcPathLength(const FVector& PathStart, const FVector& PathEnd, float& OutPathLength, FSharedConstNavQueryFilter QueryFilter, const UObject* QueryOwner) const { float TmpPathCost = 0.f; ENavigationQueryResult::Type Result = CalcPathLengthAndCost(PathStart, PathEnd, OutPathLength, TmpPathCost, QueryFilter, QueryOwner); return Result; } ENavigationQueryResult::Type ARecastNavMesh::CalcPathLengthAndCost(const FVector& PathStart, const FVector& PathEnd, float& OutPathLength, float& OutPathCost, FSharedConstNavQueryFilter QueryFilter, const UObject* QueryOwner) const { ENavigationQueryResult::Type Result = ENavigationQueryResult::Invalid; if (RecastNavMeshImpl) { if ((PathStart - PathEnd).IsNearlyZero() == true) { OutPathLength = 0.f; Result = ENavigationQueryResult::Success; } else { TSharedRef Path = MakeShareable(new FNavMeshPath()); Path->SetWantsStringPulling(false); Path->SetWantsPathCorridor(true); Result = RecastNavMeshImpl->FindPath(PathStart, PathEnd, Path.Get(), GetRightFilterRef(QueryFilter), QueryOwner); if (Result == ENavigationQueryResult::Success || (Result == ENavigationQueryResult::Fail && Path->IsPartial())) { OutPathLength = Path->GetTotalPathLength(); OutPathCost = Path->GetCost(); } } } return Result; } bool ARecastNavMesh::DoesNodeContainLocation(NavNodeRef NodeRef, const FVector& WorldSpaceLocation) const { bool bResult = false; if (RecastNavMeshImpl != nullptr && RecastNavMeshImpl->GetRecastMesh() != nullptr) { dtNavMeshQuery NavQuery; NavQuery.init(RecastNavMeshImpl->GetRecastMesh(), 0); const FVector RcLocation = Unreal2RecastPoint(WorldSpaceLocation); if (dtStatusFailed(NavQuery.isPointInsidePoly(NodeRef, &RcLocation.X, bResult))) { bResult = false; } } return bResult; } NavNodeRef ARecastNavMesh::FindNearestPoly(FVector const& Loc, FVector const& Extent, FSharedConstNavQueryFilter Filter, const UObject* QueryOwner) const { NavNodeRef PolyRef = 0; if (RecastNavMeshImpl) { PolyRef = RecastNavMeshImpl->FindNearestPoly(Loc, Extent, GetRightFilterRef(Filter), QueryOwner); } return PolyRef; } float ARecastNavMesh::FindDistanceToWall(const FVector& StartLoc, FSharedConstNavQueryFilter Filter, float MaxDistance, FVector* OutClosestPointOnWall) const { if (HasValidNavmesh() == false) { return 0.f; } const FNavigationQueryFilter& FilterToUse = GetRightFilterRef(Filter); INITIALIZE_NAVQUERY(NavQuery, FilterToUse.GetMaxSearchNodes()); const dtQueryFilter* QueryFilter = ((const FRecastQueryFilter*)(FilterToUse.GetImplementation()))->GetAsDetourQueryFilter(); if (QueryFilter == nullptr) { UE_VLOG(this, LogNavigation, Warning, TEXT("ARecastNavMesh::FindDistanceToWall failing due to QueryFilter == NULL")); return 0.f; } const FVector NavExtent = GetModifiedQueryExtent(GetDefaultQueryExtent()); const float Extent[3] = { NavExtent.X, NavExtent.Z, NavExtent.Y }; const FVector RecastStart = Unreal2RecastPoint(StartLoc); NavNodeRef StartNode = INVALID_NAVNODEREF; NavQuery.findNearestPoly(&RecastStart.X, Extent, QueryFilter, &StartNode, NULL); if (StartNode != INVALID_NAVNODEREF) { float TmpHitPos[3], TmpHitNormal[3]; float DistanceToWall = 0.f; const dtStatus RaycastStatus = NavQuery.findDistanceToWall(StartNode, &RecastStart.X, MaxDistance, QueryFilter , &DistanceToWall, TmpHitPos, TmpHitNormal); if (dtStatusSucceed(RaycastStatus)) { if (OutClosestPointOnWall) { *OutClosestPointOnWall = Recast2UnrealPoint(TmpHitPos); } return DistanceToWall; } } return 0.f; } void ARecastNavMesh::UpdateCustomLink(const INavLinkCustomInterface* CustomLink) { TSubclassOf AreaClass = CustomLink->GetLinkAreaClass(); const int32 UserId = CustomLink->GetLinkId(); const int32 AreaId = GetAreaID(AreaClass); if (AreaId >= 0 && RecastNavMeshImpl) { UNavArea* DefArea = (UNavArea*)(AreaClass->GetDefaultObject()); const uint16 PolyFlags = DefArea->GetAreaFlags() | ARecastNavMesh::GetNavLinkFlag(); RecastNavMeshImpl->UpdateNavigationLinkArea(UserId, AreaId, PolyFlags); RecastNavMeshImpl->UpdateSegmentLinkArea(UserId, AreaId, PolyFlags); } } void ARecastNavMesh::UpdateNavigationLinkArea(int32 UserId, TSubclassOf AreaClass) const { int32 AreaId = GetAreaID(AreaClass); if (AreaId >= 0 && RecastNavMeshImpl) { UNavArea* DefArea = (UNavArea*)(AreaClass->GetDefaultObject()); const uint16 PolyFlags = DefArea->GetAreaFlags() | ARecastNavMesh::GetNavLinkFlag(); RecastNavMeshImpl->UpdateNavigationLinkArea(UserId, AreaId, PolyFlags); } } void ARecastNavMesh::UpdateSegmentLinkArea(int32 UserId, TSubclassOf AreaClass) const { int32 AreaId = GetAreaID(AreaClass); if (AreaId >= 0 && RecastNavMeshImpl) { UNavArea* DefArea = (UNavArea*)(AreaClass->GetDefaultObject()); const uint16 PolyFlags = DefArea->GetAreaFlags() | ARecastNavMesh::GetNavLinkFlag(); RecastNavMeshImpl->UpdateSegmentLinkArea(UserId, AreaId, PolyFlags); } } bool ARecastNavMesh::GetPolyCenter(NavNodeRef PolyID, FVector& OutCenter) const { return RecastNavMeshImpl && RecastNavMeshImpl->GetPolyCenter(PolyID, OutCenter); } bool ARecastNavMesh::GetPolyVerts(NavNodeRef PolyID, TArray& OutVerts) const { return RecastNavMeshImpl && RecastNavMeshImpl->GetPolyVerts(PolyID, OutVerts); } uint32 ARecastNavMesh::GetPolyAreaID(NavNodeRef PolyID) const { uint32 AreaID = RECAST_DEFAULT_AREA; if (RecastNavMeshImpl) { AreaID = RecastNavMeshImpl->GetPolyAreaID(PolyID); } return AreaID; } bool ARecastNavMesh::SetPolyArea(NavNodeRef PolyID, TSubclassOf AreaClass) { bool bSuccess = false; if (AreaClass && RecastNavMeshImpl) { dtNavMesh* NavMesh = RecastNavMeshImpl->GetRecastMesh(); const int32 AreaId = GetAreaID(AreaClass); const uint16 AreaFlags = AreaClass->GetDefaultObject()->GetAreaFlags(); if (AreaId != INDEX_NONE && NavMesh) { // @todo implement a single detour function that would do both bSuccess = dtStatusSucceed(NavMesh->setPolyArea(PolyID, AreaId)); bSuccess = (bSuccess && dtStatusSucceed(NavMesh->setPolyFlags(PolyID, AreaFlags))); } } return bSuccess; } void ARecastNavMesh::SetPolyArrayArea(const TArray& Polys, TSubclassOf AreaClass) { if (AreaClass && RecastNavMeshImpl) { dtNavMesh* NavMesh = RecastNavMeshImpl->GetRecastMesh(); const int32 AreaId = GetAreaID(AreaClass); const uint16 AreaFlags = AreaClass->GetDefaultObject()->GetAreaFlags(); if (AreaId != INDEX_NONE && NavMesh) { for (int32 Idx = 0; Idx < Polys.Num(); Idx++) { NavMesh->setPolyArea(Polys[Idx].Ref, AreaId); NavMesh->setPolyFlags(Polys[Idx].Ref, AreaFlags); } } } } int32 ARecastNavMesh::ReplaceAreaInTileBounds(const FBox& Bounds, TSubclassOf OldArea, TSubclassOf NewArea, bool ReplaceLinks, TArray* OutTouchedNodes) { int32 PolysTouched = 0; if (RecastNavMeshImpl && RecastNavMeshImpl->GetRecastMesh()) { QUICK_SCOPE_CYCLE_COUNTER(STAT_RecastNavMesh_ReplaceAreaInTiles); const int32 OldAreaID = GetAreaID(OldArea); ensure(OldAreaID != INDEX_NONE); const int32 NewAreaID = 34;// GetAreaID(NewArea); ensure(NewAreaID != INDEX_NONE); ensure(NewAreaID != OldAreaID); // workaround for privacy issue in the recast API dtNavMesh* DetourNavMesh = RecastNavMeshImpl->GetRecastMesh(); dtNavMesh const* const ConstDetourNavMesh = RecastNavMeshImpl->GetRecastMesh(); const FVector RcNavMeshOrigin = Unreal2RecastPoint(NavMeshOriginOffset); const float RcTileSize = FMath::TruncToInt(TileSizeUU / CellSize); const float TileSizeInWorldUnits = RcTileSize * CellSize; const FRcTileBox TileBox(Bounds, RcNavMeshOrigin, TileSizeInWorldUnits); for (int32 TileY = TileBox.YMin; TileY <= TileBox.YMax; ++TileY) { for (int32 TileX = TileBox.XMin; TileX <= TileBox.XMax; ++TileX) { const int32 MaxTiles = ConstDetourNavMesh->getTileCountAt(TileX, TileY); if (MaxTiles == 0) { continue; } TArray Tiles; Tiles.AddZeroed(MaxTiles); const int32 NumTiles = ConstDetourNavMesh->getTilesAt(TileX, TileY, Tiles.GetData(), MaxTiles); for (int32 i = 0; i < NumTiles; i++) { dtTileRef TileRef = ConstDetourNavMesh->getTileRef(Tiles[i]); if (TileRef) { const int32 TileIndex = (int32)ConstDetourNavMesh->decodePolyIdTile(TileRef); const dtMeshTile* Tile = ((const dtNavMesh*)DetourNavMesh)->getTile(TileIndex); //const int32 MaxPolys = Tile && Tile->header ? Tile->header->offMeshBase : 0; const int32 MaxPolys = Tile && Tile->header ? (ReplaceLinks ? Tile->header->polyCount : Tile->header->offMeshBase) : 0; if (MaxPolys > 0) { dtPoly* Poly = Tile->polys; for (int32 PolyIndex = 0; PolyIndex < MaxPolys; PolyIndex++, Poly++) { if (Poly->getArea() == OldAreaID) { Poly->setArea(NewAreaID); ++PolysTouched; } } } } } } } } return PolysTouched; } bool ARecastNavMesh::GetPolyFlags(NavNodeRef PolyID, uint16& PolyFlags, uint16& AreaFlags) const { bool bFound = false; if (RecastNavMeshImpl) { uint8 AreaType = RECAST_DEFAULT_AREA; bFound = RecastNavMeshImpl->GetPolyData(PolyID, PolyFlags, AreaType); if (bFound) { const UClass* AreaClass = GetAreaClass(AreaType); const UNavArea* DefArea = AreaClass ? ((UClass*)AreaClass)->GetDefaultObject() : NULL; AreaFlags = DefArea ? DefArea->GetAreaFlags() : 0; } } return bFound; } bool ARecastNavMesh::GetPolyFlags(NavNodeRef PolyID, FNavMeshNodeFlags& Flags) const { bool bFound = false; if (RecastNavMeshImpl) { uint16 PolyFlags = 0; bFound = RecastNavMeshImpl->GetPolyData(PolyID, PolyFlags, Flags.Area); if (bFound) { const UClass* AreaClass = GetAreaClass(Flags.Area); const UNavArea* DefArea = AreaClass ? ((UClass*)AreaClass)->GetDefaultObject() : NULL; Flags.AreaFlags = DefArea ? DefArea->GetAreaFlags() : 0; Flags.PathFlags = (PolyFlags & GetNavLinkFlag()) ? 4 : 0; } } return bFound; } bool ARecastNavMesh::GetPolyNeighbors(NavNodeRef PolyID, TArray& Neighbors) const { return RecastNavMeshImpl && RecastNavMeshImpl->GetPolyNeighbors(PolyID, Neighbors); } bool ARecastNavMesh::GetPolyNeighbors(NavNodeRef PolyID, TArray& Neighbors) const { return RecastNavMeshImpl && RecastNavMeshImpl->GetPolyNeighbors(PolyID, Neighbors); } bool ARecastNavMesh::GetPolyEdges(NavNodeRef PolyID, TArray& Neighbors) const { bool bFound = false; if (RecastNavMeshImpl) { bFound = RecastNavMeshImpl->GetPolyEdges(PolyID, Neighbors); } return bFound; } bool ARecastNavMesh::GetClosestPointOnPoly(NavNodeRef PolyID, const FVector& TestPt, FVector& PointOnPoly) const { return RecastNavMeshImpl && RecastNavMeshImpl->GetClosestPointOnPoly(PolyID, TestPt, PointOnPoly); } bool ARecastNavMesh::GetPolyTileIndex(NavNodeRef PolyID, uint32& PolyIndex, uint32& TileIndex) const { return RecastNavMeshImpl && RecastNavMeshImpl->GetPolyTileIndex(PolyID, PolyIndex, TileIndex); } bool ARecastNavMesh::GetLinkEndPoints(NavNodeRef LinkPolyID, FVector& PointA, FVector& PointB) const { return RecastNavMeshImpl && RecastNavMeshImpl->GetLinkEndPoints(LinkPolyID, PointA, PointB); } bool ARecastNavMesh::IsCustomLink(NavNodeRef LinkPolyID) const { return RecastNavMeshImpl && RecastNavMeshImpl->IsCustomLink(LinkPolyID); } bool ARecastNavMesh::GetClusterBounds(NavNodeRef ClusterRef, FBox& OutBounds) const { return RecastNavMeshImpl && RecastNavMeshImpl->GetClusterBounds(ClusterRef, OutBounds); } bool ARecastNavMesh::GetPolysWithinPathingDistance(FVector const& StartLoc, const float PathingDistance, TArray& FoundPolys, FSharedConstNavQueryFilter Filter, const UObject* QueryOwner, FRecastDebugPathfindingData* DebugData) const { return RecastNavMeshImpl && RecastNavMeshImpl->GetPolysWithinPathingDistance(StartLoc, PathingDistance, GetRightFilterRef(Filter), QueryOwner, FoundPolys, DebugData); } void ARecastNavMesh::GetDebugGeometry(FRecastDebugGeometry& OutGeometry, int32 TileIndex) const { if (RecastNavMeshImpl) { RecastNavMeshImpl->GetDebugGeometry(OutGeometry, TileIndex); } } void ARecastNavMesh::RequestDrawingUpdate(bool bForce) { #if !UE_BUILD_SHIPPING if (bForce || UNavMeshRenderingComponent::IsNavigationShowFlagSet(GetWorld())) { if (bForce) { UNavMeshRenderingComponent* NavRenderingComp = Cast(RenderingComp); if (NavRenderingComp) { NavRenderingComp->ForceUpdate(); } } DECLARE_CYCLE_STAT(TEXT("FSimpleDelegateGraphTask.Requesting navmesh redraw"), STAT_FSimpleDelegateGraphTask_RequestingNavmeshRedraw, STATGROUP_TaskGraphTasks); FSimpleDelegateGraphTask::CreateAndDispatchWhenReady( FSimpleDelegateGraphTask::FDelegate::CreateUObject(this, &ARecastNavMesh::UpdateDrawing), GET_STATID(STAT_FSimpleDelegateGraphTask_RequestingNavmeshRedraw), NULL, ENamedThreads::GameThread); } #endif // !UE_BUILD_SHIPPING } void ARecastNavMesh::UpdateDrawing() { UpdateNavMeshDrawing(); } void ARecastNavMesh::DrawDebugPathCorridor(NavNodeRef const* PathPolys, int32 NumPathPolys, bool bPersistent) const { #if ENABLE_DRAW_DEBUG static const FColor PathLineColor(255, 128, 0); UWorld* World = GetWorld(); // draw poly outlines TArray PolyVerts; for (int32 PolyIdx=0; PolyIdx < NumPathPolys; ++PolyIdx) { if ( GetPolyVerts(PathPolys[PolyIdx], PolyVerts) ) { for (int32 VertIdx=0; VertIdx < PolyVerts.Num()-1; ++VertIdx) { DrawDebugLine(World, PolyVerts[VertIdx], PolyVerts[VertIdx+1], PathLineColor, bPersistent); } DrawDebugLine(World, PolyVerts[PolyVerts.Num()-1], PolyVerts[0], PathLineColor, bPersistent); } } // draw ordered poly links if (NumPathPolys > 0) { FVector PolyCenter; FVector NextPolyCenter; if ( GetPolyCenter(PathPolys[0], NextPolyCenter) ) // prime the pump { for (int32 PolyIdx=0; PolyIdx < NumPathPolys-1; ++PolyIdx) { PolyCenter = NextPolyCenter; if ( GetPolyCenter(PathPolys[PolyIdx+1], NextPolyCenter) ) { DrawDebugLine(World, PolyCenter, NextPolyCenter, PathLineColor, bPersistent); DrawDebugBox(World, PolyCenter, FVector(5.f), PathLineColor, bPersistent); } } } } #endif // ENABLE_DRAW_DEBUG } void ARecastNavMesh::OnNavMeshTilesUpdated(const TArray& ChangedTiles) { InvalidateAffectedPaths(ChangedTiles); } void ARecastNavMesh::InvalidateAffectedPaths(const TArray& ChangedTiles) { const int32 PathsCount = ActivePaths.Num(); const int32 ChangedTilesCount = ChangedTiles.Num(); if (ChangedTilesCount == 0 || PathsCount == 0) { return; } FNavPathWeakPtr* WeakPathPtr = (ActivePaths.GetData() + PathsCount - 1); for (int32 PathIndex = PathsCount - 1; PathIndex >= 0; --PathIndex, --WeakPathPtr) { FNavPathSharedPtr SharedPath = WeakPathPtr->Pin(); if (WeakPathPtr->IsValid() == false) { ActivePaths.RemoveAtSwap(PathIndex, 1, /*bAllowShrinking=*/false); } else { // iterate through all tile refs in FreshTilesCopy and const FNavMeshPath* Path = (const FNavMeshPath*)(SharedPath.Get()); if (Path->IsReady() == false || Path->GetIgnoreInvalidation() == true) { // path not filled yet or doesn't care about invalidation continue; } const int32 PathLenght = Path->PathCorridor.Num(); const NavNodeRef* PathPoly = Path->PathCorridor.GetData(); for (int32 NodeIndex = 0; NodeIndex < PathLenght; ++NodeIndex, ++PathPoly) { const uint32 NodeTileIdx = RecastNavMeshImpl->GetTileIndexFromPolyRef(*PathPoly); if (ChangedTiles.Contains(NodeTileIdx)) { SharedPath->Invalidate(); ActivePaths.RemoveAtSwap(PathIndex, 1, /*bAllowShrinking=*/false); break; } } } } } URecastNavMeshDataChunk* ARecastNavMesh::GetNavigationDataChunk(ULevel* InLevel) const { FName ThisName = GetFName(); int32 ChunkIndex = InLevel->NavDataChunks.IndexOfByPredicate([&](UNavigationDataChunk* Chunk) { return Chunk->NavigationDataName == ThisName; }); URecastNavMeshDataChunk* RcNavDataChunk = nullptr; if (ChunkIndex != INDEX_NONE) { RcNavDataChunk = Cast(InLevel->NavDataChunks[ChunkIndex]); } return RcNavDataChunk; } void ARecastNavMesh::EnsureBuildCompletion() { Super::EnsureBuildCompletion(); // Doing this as a safety net solution due to UE-20646, which was basically a result of random // over-releasing of default filter's shared pointer (it seemed). We might have time to get // back to this time some time in next 3 years :D RecreateDefaultFilter(); } void ARecastNavMesh::OnNavMeshGenerationFinished() { UWorld* World = GetWorld(); if (World != nullptr && World->IsPendingKill() == false) { #if WITH_EDITOR // For navmeshes that support streaming create navigation data holders in each streaming level // so parts of navmesh can be streamed in/out with those levels if (!World->IsGameWorld()) { const auto& Levels = World->GetLevels(); for (auto Level : Levels) { if (Level->IsPersistentLevel()) { continue; } URecastNavMeshDataChunk* NavDataChunk = GetNavigationDataChunk(Level); if (SupportsStreaming()) { // We use navigation volumes that belongs to this streaming level to find tiles we want to save TArray LevelTiles; TArray LevelNavBounds = GetNavigableBoundsInLevel(Level); RecastNavMeshImpl->GetNavMeshTilesIn(LevelNavBounds, LevelTiles); if (LevelTiles.Num()) { // Create new chunk only if we have something to save in it if (NavDataChunk == nullptr) { NavDataChunk = NewObject(Level); NavDataChunk->NavigationDataName = GetFName(); Level->NavDataChunks.Add(NavDataChunk); } NavDataChunk->GatherTiles(RecastNavMeshImpl, LevelTiles); NavDataChunk->MarkPackageDirty(); continue; } } // stale data that is left in the level if (NavDataChunk) { // clear it NavDataChunk->ReleaseTiles(); NavDataChunk->MarkPackageDirty(); Level->NavDataChunks.Remove(NavDataChunk); } } } // force navmesh drawing update RequestDrawingUpdate(/*bForce=*/true); #endif// WITH_EDITOR UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(World); if (NavSys) { NavSys->OnNavigationGenerationFinished(*this); } } } #if !UE_BUILD_SHIPPING uint32 ARecastNavMesh::LogMemUsed() const { const uint32 SuperMemUsed = Super::LogMemUsed(); const int32 headerSize = dtAlign4(sizeof(dtMeshHeader)); uint32 MemUsed = 0; if (RecastNavMeshImpl && RecastNavMeshImpl->DetourNavMesh) { const dtNavMesh* const ConstNavMesh = RecastNavMeshImpl->DetourNavMesh; for (int TileIndex = 0; TileIndex < RecastNavMeshImpl->DetourNavMesh->getMaxTiles(); ++TileIndex) { const dtMeshTile* Tile = ConstNavMesh->getTile(TileIndex); if (Tile && Tile->header) { dtMeshHeader* const H = (dtMeshHeader*)(Tile->header); const int32 vertsSize = dtAlign4(sizeof(float) * 3 * H->vertCount); const int32 polysSize = dtAlign4(sizeof(dtPoly) * H->polyCount); const int32 linksSize = dtAlign4(sizeof(dtLink) * H->maxLinkCount); const int32 detailMeshesSize = dtAlign4(sizeof(dtPolyDetail) * H->detailMeshCount); const int32 detailVertsSize = dtAlign4(sizeof(float) * 3 * H->detailVertCount); const int32 detailTrisSize = dtAlign4(sizeof(unsigned char) * 4 * H->detailTriCount); const int32 bvTreeSize = dtAlign4(sizeof(dtBVNode) * H->bvNodeCount); const int32 offMeshConsSize = dtAlign4(sizeof(dtOffMeshConnection) * H->offMeshConCount); const int32 offMeshSegsSize = dtAlign4(sizeof(dtOffMeshSegmentConnection) * H->offMeshSegConCount); const int32 clusterSize = dtAlign4(sizeof(dtCluster) * H->clusterCount); const int32 polyClustersSize = dtAlign4(sizeof(unsigned short) * H->detailMeshCount); const int32 TileDataSize = headerSize + vertsSize + polysSize + linksSize + detailMeshesSize + detailVertsSize + detailTrisSize + bvTreeSize + offMeshConsSize + offMeshSegsSize + clusterSize + polyClustersSize; MemUsed += TileDataSize; } } } UE_LOG(LogNavigation, Warning, TEXT("%s: ARecastNavMesh: %u\n self: %d"), *GetName(), MemUsed, sizeof(ARecastNavMesh)); return MemUsed + SuperMemUsed; } #endif // !UE_BUILD_SHIPPING uint16 ARecastNavMesh::GetDefaultForbiddenFlags() const { return FPImplRecastNavMesh::GetFilterForbiddenFlags((const FRecastQueryFilter*)DefaultQueryFilter->GetImplementation()); } void ARecastNavMesh::SetDefaultForbiddenFlags(uint16 ForbiddenAreaFlags) { FPImplRecastNavMesh::SetFilterForbiddenFlags((FRecastQueryFilter*)DefaultQueryFilter->GetImplementation(), ForbiddenAreaFlags); } void ARecastNavMesh::SetMaxSimultaneousTileGenerationJobsCount(int32 NewJobsCountLimit) { const int32 NewCount = NewJobsCountLimit > 0 ? NewJobsCountLimit : 1; if (MaxSimultaneousTileGenerationJobsCount != NewCount) { MaxSimultaneousTileGenerationJobsCount = NewCount; if (GetGenerator() != nullptr) { FRecastNavMeshGenerator* MyGenerator = static_cast(GetGenerator()); MyGenerator->SetMaxTileGeneratorTasks(NewCount); } } } bool ARecastNavMesh::FilterPolys(TArray& PolyRefs, const FRecastQueryFilter* Filter, const UObject* QueryOwner) const { bool bSuccess = false; if (RecastNavMeshImpl) { bSuccess = RecastNavMeshImpl->FilterPolys(PolyRefs, Filter, QueryOwner); } return bSuccess; } void ARecastNavMesh::ApplyWorldOffset(const FVector& InOffset, bool bWorldShift) { if (RecastNavMeshImpl) { RecastNavMeshImpl->ApplyWorldOffset(InOffset, bWorldShift); } Super::ApplyWorldOffset(InOffset, bWorldShift); RequestDrawingUpdate(); } void ARecastNavMesh::OnStreamingLevelAdded(ULevel* InLevel, UWorld* InWorld) { QUICK_SCOPE_CYCLE_COUNTER(STAT_RecastNavMesh_OnStreamingLevelAdded); if (SupportsStreaming() && RecastNavMeshImpl) { URecastNavMeshDataChunk* NavDataChunk = GetNavigationDataChunk(InLevel); if (NavDataChunk) { AttachNavMeshDataChunk(*NavDataChunk); } } } void ARecastNavMesh::AttachNavMeshDataChunk(URecastNavMeshDataChunk& NavDataChunk) { TArray AttachedIndices = NavDataChunk.AttachTiles(*RecastNavMeshImpl); if (AttachedIndices.Num() > 0) { InvalidateAffectedPaths(AttachedIndices); RequestDrawingUpdate(); } } void ARecastNavMesh::OnStreamingLevelRemoved(ULevel* InLevel, UWorld* InWorld) { QUICK_SCOPE_CYCLE_COUNTER(STAT_RecastNavMesh_OnStreamingLevelRemoved); if (SupportsStreaming() && RecastNavMeshImpl) { URecastNavMeshDataChunk* NavDataChunk = GetNavigationDataChunk(InLevel); if (NavDataChunk) { DetachNavMeshDataChunk(*NavDataChunk); } } } void ARecastNavMesh::DetachNavMeshDataChunk(URecastNavMeshDataChunk& NavDataChunk) { TArray DetachedIndices = NavDataChunk.DetachTiles(*RecastNavMeshImpl); if (DetachedIndices.Num() > 0) { InvalidateAffectedPaths(DetachedIndices); RequestDrawingUpdate(); } } bool ARecastNavMesh::AdjustLocationWithFilter(const FVector& StartLoc, FVector& OutAdjustedLocation, const FNavigationQueryFilter& Filter, const UObject* QueryOwner) const { INITIALIZE_NAVQUERY(NavQuery, Filter.GetMaxSearchNodes()); const FVector NavExtent = GetModifiedQueryExtent(GetDefaultQueryExtent()); const float Extent[3] = { NavExtent.X, NavExtent.Z, NavExtent.Y }; const dtQueryFilter* QueryFilter = ((const FRecastQueryFilter*)(Filter.GetImplementation()))->GetAsDetourQueryFilter(); ensure(QueryFilter); FVector RecastStart = Unreal2RecastPoint(StartLoc); FVector RecastAdjustedPoint = Unreal2RecastPoint(StartLoc); NavNodeRef StartPolyID = INVALID_NAVNODEREF; NavQuery.findNearestPoly(&RecastStart.X, Extent, QueryFilter, &StartPolyID, &RecastAdjustedPoint.X); if (FVector::DistSquared(RecastStart, RecastAdjustedPoint) < KINDA_SMALL_NUMBER) { OutAdjustedLocation = StartLoc; return false; } else { OutAdjustedLocation = Recast2UnrealPoint(RecastAdjustedPoint); // move it just a bit further - otherwise recast can still pick "wrong" poly when // later projecting StartLoc (meaning a poly we want to filter out with // QueryFilter here) OutAdjustedLocation += (OutAdjustedLocation - StartLoc).GetSafeNormal() * 0.1f; return true; } } FPathFindingResult ARecastNavMesh::FindPath(const FNavAgentProperties& AgentProperties, const FPathFindingQuery& Query) { SCOPE_CYCLE_COUNTER(STAT_Navigation_RecastPathfinding); CSV_SCOPED_TIMING_STAT_EXCLUSIVE(Pathfinding); const ANavigationData* Self = Query.NavData.Get(); check(Cast(Self)); const ARecastNavMesh* RecastNavMesh = (const ARecastNavMesh*)Self; if (Self == NULL || RecastNavMesh->RecastNavMeshImpl == NULL) { return ENavigationQueryResult::Error; } FPathFindingResult Result(ENavigationQueryResult::Error); FNavigationPath* NavPath = Query.PathInstanceToFill.Get(); FNavMeshPath* NavMeshPath = NavPath ? NavPath->CastPath() : nullptr; if (NavMeshPath) { Result.Path = Query.PathInstanceToFill; NavMeshPath->ResetForRepath(); } else { Result.Path = Self->CreatePathInstance(Query); NavPath = Result.Path.Get(); NavMeshPath = NavPath ? NavPath->CastPath() : nullptr; } const FNavigationQueryFilter* NavFilter = Query.QueryFilter.Get(); if (NavMeshPath && NavFilter) { NavMeshPath->ApplyFlags(Query.NavDataFlags); const FVector AdjustedEndLocation = NavFilter->GetAdjustedEndLocation(Query.EndLocation); if ((Query.StartLocation - AdjustedEndLocation).IsNearlyZero() == true) { Result.Path->GetPathPoints().Reset(); Result.Path->GetPathPoints().Add(FNavPathPoint(AdjustedEndLocation)); Result.Result = ENavigationQueryResult::Success; } else { Result.Result = RecastNavMesh->RecastNavMeshImpl->FindPath(Query.StartLocation, AdjustedEndLocation, *NavMeshPath, *NavFilter, Query.Owner.Get()); const bool bPartialPath = Result.IsPartial(); if (bPartialPath) { Result.Result = Query.bAllowPartialPaths ? ENavigationQueryResult::Success : ENavigationQueryResult::Fail; } } } return Result; } bool ARecastNavMesh::TestPath(const FNavAgentProperties& AgentProperties, const FPathFindingQuery& Query, int32* NumVisitedNodes) { SCOPE_CYCLE_COUNTER(STAT_Navigation_RecastTestPath); CSV_SCOPED_TIMING_STAT_EXCLUSIVE(Pathfinding); const ANavigationData* Self = Query.NavData.Get(); check(Cast(Self)); const ARecastNavMesh* RecastNavMesh = (const ARecastNavMesh*)Self; if (Self == NULL || RecastNavMesh->RecastNavMeshImpl == NULL) { return false; } bool bPathExists = true; const FNavigationQueryFilter* NavFilter = Query.QueryFilter.Get(); if (NavFilter) { const FVector AdjustedEndLocation = NavFilter->GetAdjustedEndLocation(Query.EndLocation); if ((Query.StartLocation - AdjustedEndLocation).IsNearlyZero() == false) { ENavigationQueryResult::Type Result = RecastNavMesh->RecastNavMeshImpl->TestPath(Query.StartLocation, AdjustedEndLocation, *NavFilter, Query.Owner.Get(), NumVisitedNodes); bPathExists = (Result == ENavigationQueryResult::Success); } } return bPathExists; } bool ARecastNavMesh::TestHierarchicalPath(const FNavAgentProperties& AgentProperties, const FPathFindingQuery& Query, int32* NumVisitedNodes) { const ANavigationData* Self = Query.NavData.Get(); check(Cast(Self)); const ARecastNavMesh* RecastNavMesh = (const ARecastNavMesh*)Self; if (Self == nullptr || RecastNavMesh->RecastNavMeshImpl == nullptr || RecastNavMesh->RecastNavMeshImpl->DetourNavMesh == nullptr) { return false; } const bool bCanUseHierachicalPath = (Query.QueryFilter == RecastNavMesh->GetDefaultQueryFilter()); bool bPathExists = true; const FNavigationQueryFilter* NavFilter = Query.QueryFilter.Get(); if (NavFilter) { const FVector AdjustedEndLocation = NavFilter->GetAdjustedEndLocation(Query.EndLocation); if ((Query.StartLocation - AdjustedEndLocation).IsNearlyZero() == false) { bool bUseFallbackSearch = false; if (bCanUseHierachicalPath) { ENavigationQueryResult::Type Result = RecastNavMesh->RecastNavMeshImpl->TestClusterPath(Query.StartLocation, AdjustedEndLocation, NumVisitedNodes); bPathExists = (Result == ENavigationQueryResult::Success); if (Result == ENavigationQueryResult::Error) { bUseFallbackSearch = true; } } else { UE_LOG(LogNavigation, Log, TEXT("Hierarchical path finding test failed: filter doesn't match!")); bUseFallbackSearch = true; } if (bUseFallbackSearch) { ENavigationQueryResult::Type Result = RecastNavMesh->RecastNavMeshImpl->TestPath(Query.StartLocation, AdjustedEndLocation, *NavFilter, Query.Owner.Get(), NumVisitedNodes); bPathExists = (Result == ENavigationQueryResult::Success); } } } return bPathExists; } bool ARecastNavMesh::NavMeshRaycast(const ANavigationData* Self, const FVector& RayStart, const FVector& RayEnd, FVector& HitLocation, FSharedConstNavQueryFilter QueryFilter,const UObject* QueryOwner, FRaycastResult& Result) { check(Cast(Self)); const ARecastNavMesh* RecastNavMesh = (const ARecastNavMesh*)Self; if (Self == NULL || RecastNavMesh->RecastNavMeshImpl == NULL) { HitLocation = RayStart; return true; } RecastNavMesh->RecastNavMeshImpl->Raycast(RayStart, RayEnd, RecastNavMesh->GetRightFilterRef(QueryFilter), QueryOwner, Result); HitLocation = Result.HasHit() ? (RayStart + (RayEnd - RayStart) * Result.HitTime) : RayEnd; return Result.HasHit(); } bool ARecastNavMesh::NavMeshRaycast(const ANavigationData* Self, NavNodeRef RayStartNode, const FVector& RayStart, const FVector& RayEnd, FVector& HitLocation, FSharedConstNavQueryFilter QueryFilter, const UObject* QueryOwner) { check(Cast(Self)); const ARecastNavMesh* RecastNavMesh = (const ARecastNavMesh*)Self; if (Self == NULL || RecastNavMesh->RecastNavMeshImpl == NULL) { HitLocation = RayStart; return true; } FRaycastResult Result; RecastNavMesh->RecastNavMeshImpl->Raycast(RayStart, RayEnd, RecastNavMesh->GetRightFilterRef(QueryFilter), QueryOwner, Result, RayStartNode); HitLocation = Result.HasHit() ? (RayStart + (RayEnd - RayStart) * Result.HitTime) : RayEnd; return Result.HasHit(); } void ARecastNavMesh::BatchRaycast(TArray& Workload, FSharedConstNavQueryFilter Filter, const UObject* Querier) const { if (RecastNavMeshImpl == NULL || Workload.Num() == 0 || RecastNavMeshImpl->DetourNavMesh == NULL) { return; } const FNavigationQueryFilter& FilterToUse = GetRightFilterRef(Filter); FRecastSpeciaLinkFilter LinkFilter(FNavigationSystem::GetCurrent(GetWorld()), Querier); INITIALIZE_NAVQUERY_WLINKFILTER(NavQuery, FilterToUse.GetMaxSearchNodes(), LinkFilter); const dtQueryFilter* QueryFilter = ((const FRecastQueryFilter*)(FilterToUse.GetImplementation()))->GetAsDetourQueryFilter(); if (QueryFilter == NULL) { UE_VLOG(this, LogNavigation, Warning, TEXT("FPImplRecastNavMesh::FindPath failing due to QueryFilter == NULL")); return; } const FVector NavExtent = GetModifiedQueryExtent(GetDefaultQueryExtent()); const float Extent[3] = { NavExtent.X, NavExtent.Z, NavExtent.Y }; for (FNavigationRaycastWork& WorkItem : Workload) { ARecastNavMesh::FRaycastResult RaycastResult; const FVector RecastStart = Unreal2RecastPoint(WorkItem.RayStart); const FVector RecastEnd = Unreal2RecastPoint(WorkItem.RayEnd); NavNodeRef StartNode = INVALID_NAVNODEREF; NavQuery.findNearestContainingPoly(&RecastStart.X, Extent, QueryFilter, &StartNode, NULL); if (StartNode != INVALID_NAVNODEREF) { float RecastHitNormal[3]; const dtStatus RaycastStatus = NavQuery.raycast(StartNode, &RecastStart.X, &RecastEnd.X , QueryFilter, &RaycastResult.HitTime, RecastHitNormal , RaycastResult.CorridorPolys, &RaycastResult.CorridorPolysCount, RaycastResult.GetMaxCorridorSize()); if (dtStatusSucceed(RaycastStatus) && RaycastResult.HasHit()) { WorkItem.bDidHit = true; WorkItem.HitLocation = FNavLocation(WorkItem.RayStart + (WorkItem.RayEnd - WorkItem.RayStart) * RaycastResult.HitTime, RaycastResult.GetLastNodeRef()); } } } } bool ARecastNavMesh::IsSegmentOnNavmesh(const FVector& SegmentStart, const FVector& SegmentEnd, FSharedConstNavQueryFilter Filter, const UObject* QueryOwner) const { if (RecastNavMeshImpl == NULL) { return false; } FRaycastResult Result; RecastNavMeshImpl->Raycast(SegmentStart, SegmentEnd, GetRightFilterRef(Filter), QueryOwner, Result); return Result.bIsRaycastEndInCorridor && !Result.HasHit(); } bool ARecastNavMesh::FindStraightPath(const FVector& StartLoc, const FVector& EndLoc, const TArray& PathCorridor, TArray& PathPoints, TArray* CustomLinks) const { bool bResult = false; if (RecastNavMeshImpl) { bResult = RecastNavMeshImpl->FindStraightPath(StartLoc, EndLoc, PathCorridor, PathPoints, CustomLinks); } return bResult; } int32 ARecastNavMesh::DebugPathfinding(const FPathFindingQuery& Query, TArray& Steps) { int32 NumSteps = 0; const ANavigationData* Self = Query.NavData.Get(); check(Cast(Self)); const ARecastNavMesh* RecastNavMesh = (const ARecastNavMesh*)Self; if (Self == NULL || RecastNavMesh->RecastNavMeshImpl == NULL) { return false; } if ((Query.StartLocation - Query.EndLocation).IsNearlyZero() == false) { NumSteps = RecastNavMesh->RecastNavMeshImpl->DebugPathfinding(Query.StartLocation, Query.EndLocation, *(Query.QueryFilter.Get()), Query.Owner.Get(), Steps); } return NumSteps; } void ARecastNavMesh::UpdateNavVersion() { NavMeshVersion = NAVMESHVER_LATEST; } #if WITH_EDITOR void ARecastNavMesh::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { static const FName NAME_Generation = FName(TEXT("Generation")); static const FName NAME_Display = FName(TEXT("Display")); static const FName NAME_RuntimeGeneration = FName(TEXT("RuntimeGeneration")); static const FName NAME_TileNumberHardLimit = GET_MEMBER_NAME_CHECKED(ARecastNavMesh, TileNumberHardLimit); static const FName NAME_Query = FName(TEXT("Query")); Super::PostEditChangeProperty(PropertyChangedEvent); if (PropertyChangedEvent.Property != NULL) { const FName CategoryName = FObjectEditorUtils::GetCategoryFName(PropertyChangedEvent.Property); if (CategoryName == NAME_Generation) { FName PropName = PropertyChangedEvent.Property->GetFName(); if (PropName == GET_MEMBER_NAME_CHECKED(ARecastNavMesh,AgentRadius) || PropName == GET_MEMBER_NAME_CHECKED(ARecastNavMesh,TileSizeUU) || PropName == GET_MEMBER_NAME_CHECKED(ARecastNavMesh,CellSize)) { // rule of thumb, dimension tile shouldn't be less than 16 * AgentRadius if (TileSizeUU < 16.f * AgentRadius) { TileSizeUU = FMath::Max(16.f * AgentRadius, RECAST_MIN_TILE_SIZE); } // tile's can't be too big, otherwise we'll crash while tryng to allocate // memory during navmesh generation TileSizeUU = FMath::Clamp(TileSizeUU, CellSize, ArbitraryMaxVoxelTileSize * CellSize); // update config FillConfig(NavDataConfig); } else if (PropName == NAME_TileNumberHardLimit) { TileNumberHardLimit = 1 << (FMath::CeilToInt(FMath::Log2(TileNumberHardLimit))); UpdatePolyRefBitsPreview(); } UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(GetWorld()); if (!HasAnyFlags(RF_ClassDefaultObject) && NavSys->GetIsAutoUpdateEnabled() && PropName != GET_MEMBER_NAME_CHECKED(ARecastNavMesh, MaxSimultaneousTileGenerationJobsCount)) { RebuildAll(); } } else if (CategoryName == NAME_Display) { RequestDrawingUpdate(); } else if (PropertyChangedEvent.Property->GetFName() == NAME_RuntimeGeneration) { // @todo this contraption is required to clear RuntimeGeneration value in DefaultEngine.ini // if it gets set to its default value (UE-23762). This is hopefully a temporary solution // since it's an Core-level issue (UE-23873). if (RuntimeGeneration == ERuntimeGenerationType::Static) { const FString EngineIniFilename = FPaths::ConvertRelativePathToFull(GetDefault()->GetDefaultConfigFilename()); GConfig->SetString(TEXT("/Script/NavigationSystem.RecastNavMesh"), *NAME_RuntimeGeneration.ToString(), TEXT("Static"), *EngineIniFilename); GConfig->Flush(false); } } else if (CategoryName == NAME_Query) { RecreateDefaultFilter(); } } } #endif // WITH_EDITOR bool ARecastNavMesh::NeedsRebuild() const { bool bLooksLikeNeeded = !RecastNavMeshImpl || RecastNavMeshImpl->GetRecastMesh() == 0; if (NavDataGenerator.IsValid()) { return bLooksLikeNeeded || NavDataGenerator->GetNumRemaningBuildTasks() > 0; } return bLooksLikeNeeded; } bool ARecastNavMesh::SupportsRuntimeGeneration() const { // Generator should be disabled for Static navmesh return (RuntimeGeneration != ERuntimeGenerationType::Static); } bool ARecastNavMesh::SupportsStreaming() const { // Actually nothing prevents us to support streaming with dynamic generation // Right now streaming in sub-level causes navmesh to build itself, so no point to stream tiles in return (RuntimeGeneration != ERuntimeGenerationType::Dynamic); } FRecastNavMeshGenerator* ARecastNavMesh::CreateGeneratorInstance() { return new FRecastNavMeshGenerator(*this); } void ARecastNavMesh::ConditionalConstructGenerator() { if (NavDataGenerator.IsValid()) { NavDataGenerator->CancelBuild(); NavDataGenerator.Reset(); } UWorld* World = GetWorld(); check(World); const bool bRequiresGenerator = SupportsRuntimeGeneration() || !World->IsGameWorld(); if (bRequiresGenerator) { FRecastNavMeshGenerator* Generator = CreateGeneratorInstance(); if (Generator) { NavDataGenerator = MakeShareable(Generator); Generator->Init(); } UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(World); if (NavSys) { RestrictBuildingToActiveTiles(NavSys->IsActiveTilesGenerationEnabled()); } } } bool ARecastNavMesh::IsVoxelCacheEnabled() { #if RECAST_ASYNC_REBUILDING // voxel cache is using static buffers to minimize memory impact // therefore it can run only with synchronous navmesh rebuilds return false; #endif ARecastNavMesh* DefOb = (ARecastNavMesh*)ARecastNavMesh::StaticClass()->GetDefaultObject(); return DefOb && DefOb->bUseVoxelCache; } const FRecastQueryFilter* ARecastNavMesh::GetNamedFilter(ERecastNamedFilter::Type FilterType) { check(FilterType < ERecastNamedFilter::NamedFiltersCount); return NamedFilters[FilterType]; } #undef INITIALIZE_NAVQUERY void ARecastNavMesh::UpdateNavObject() { OnNavMeshUpdate.Broadcast(); } #endif //WITH_RECAST bool ARecastNavMesh::HasValidNavmesh() const { #if WITH_RECAST return (RecastNavMeshImpl && RecastNavMeshImpl->DetourNavMesh && RecastNavMeshImpl->DetourNavMesh->isEmpty() == false); #else return false; #endif // WITH_RECAST } #if WITH_RECAST bool ARecastNavMesh::HasCompleteDataInRadius(const FVector& TestLocation, float TestRadius) const { if (HasValidNavmesh() == false) { return false; } const dtNavMesh* NavMesh = RecastNavMeshImpl->DetourNavMesh; const dtNavMeshParams* NavParams = RecastNavMeshImpl->DetourNavMesh->getParams(); const float NavTileSize = CellSize * FMath::TruncToInt(TileSizeUU / CellSize); const FVector RcNavOrigin(NavParams->orig[0], NavParams->orig[1], NavParams->orig[2]); const FBox RcBounds = Unreal2RecastBox(FBox::BuildAABB(TestLocation, FVector(TestRadius, TestRadius, 0))); const FVector RcTestLocation = Unreal2RecastPoint(TestLocation); const int32 MinTileX = FMath::FloorToInt((RcBounds.Min.X - RcNavOrigin.X) / NavTileSize); const int32 MaxTileX = FMath::CeilToInt((RcBounds.Max.X - RcNavOrigin.X) / NavTileSize); const int32 MinTileY = FMath::FloorToInt((RcBounds.Min.Z - RcNavOrigin.Z) / NavTileSize); const int32 MaxTileY = FMath::CeilToInt((RcBounds.Max.Z - RcNavOrigin.Z) / NavTileSize); const FVector RcTileExtent2D(NavTileSize * 0.5f, 0.f, NavTileSize * 0.5f); const float RadiusSq = FMath::Square(TestRadius); for (int32 TileX = MinTileX; TileX <= MaxTileX; TileX++) { for (int32 TileY = MinTileY; TileY <= MaxTileY; TileY++) { const FVector RcTileCenter(RcNavOrigin.X + ((TileX + 0.5f) * NavTileSize), RcTestLocation.Y, RcNavOrigin.Z + ((TileY + 0.5f) * NavTileSize)); const bool bInside = FMath::SphereAABBIntersection(RcTestLocation, RadiusSq, FBox::BuildAABB(RcTileCenter, RcTileExtent2D)); if (bInside) { const int32 NumTiles = NavMesh->getTileCountAt(TileX, TileY); if (NumTiles <= 0) { const bool bHasFailsafeData = bStoreEmptyTileLayers && RecastNavMeshImpl->HasTileCacheLayers(TileX, TileY); if (!bHasFailsafeData) { return false; } } } } } return true; } //----------------------------------------------------------------------// // RecastNavMesh: Active Tiles //----------------------------------------------------------------------// void ARecastNavMesh::UpdateActiveTiles(const TArray& InvokerLocations) { if (HasValidNavmesh() == false) { return; } FRecastNavMeshGenerator* MyGenerator = static_cast(GetGenerator()); if (MyGenerator == nullptr) { return; } const dtNavMeshParams* NavParams = GetRecastNavMeshImpl()->DetourNavMesh->getParams(); check(NavParams && MyGenerator); const FRecastBuildConfig& Config = MyGenerator->GetConfig(); const FVector NavmeshOrigin = Recast2UnrealPoint(NavParams->orig); const float TileDim = Config.tileSize * Config.cs; const FVector TileCenterOffset(TileDim, TileDim, 0); TArray& ActiveTiles = GetActiveTiles(); TArray OldActiveSet = ActiveTiles; TArray TilesInMinDistance; TArray TilesInMaxDistance; TilesInMinDistance.Reserve(ActiveTiles.Num()); TilesInMaxDistance.Reserve(ActiveTiles.Num()); ActiveTiles.Reset(); //const int32 TileRadius = FMath::CeilToInt(Radius / TileDim); static const float SqareRootOf2 = FMath::Sqrt(2.f); for (const FNavigationInvokerRaw& Invoker : InvokerLocations) { const FVector InvokerRelativeLocation = (NavmeshOrigin - Invoker.Location); const float TileCenterDistanceToRemoveSq = FMath::Square(TileDim * SqareRootOf2 / 2 + Invoker.RadiusMax); const float TileCenterDistanceToAddSq = FMath::Square(TileDim * SqareRootOf2 / 2 + Invoker.RadiusMin); const int32 MinTileX = FMath::FloorToInt((InvokerRelativeLocation.X - Invoker.RadiusMax) / TileDim); const int32 MaxTileX = FMath::CeilToInt((InvokerRelativeLocation.X + Invoker.RadiusMax) / TileDim); const int32 MinTileY = FMath::FloorToInt((InvokerRelativeLocation.Y - Invoker.RadiusMax) / TileDim); const int32 MaxTileY = FMath::CeilToInt((InvokerRelativeLocation.Y + Invoker.RadiusMax) / TileDim); for (int32 X = MinTileX; X <= MaxTileX; ++X) { for (int32 Y = MinTileY; Y <= MaxTileY; ++Y) { const float DistanceSq = (InvokerRelativeLocation - FVector(X * TileDim + TileDim / 2, Y * TileDim + TileDim / 2, 0.f)).SizeSquared2D(); if (DistanceSq < TileCenterDistanceToRemoveSq) { TilesInMaxDistance.AddUnique(FIntPoint(X, Y)); if (DistanceSq < TileCenterDistanceToAddSq) { TilesInMinDistance.AddUnique(FIntPoint(X, Y)); } } } } } ActiveTiles.Append(TilesInMinDistance); TArray TilesToRemove; TilesToRemove.Reserve(OldActiveSet.Num()); for (int32 Index = OldActiveSet.Num() - 1; Index >= 0; --Index) { if (TilesInMaxDistance.Find(OldActiveSet[Index]) == INDEX_NONE) { TilesToRemove.Add(OldActiveSet[Index]); OldActiveSet.RemoveAtSwap(Index, 1, /*bAllowShrinking=*/false); } else { ActiveTiles.AddUnique(OldActiveSet[Index]); } } TArray TilesToUpdate; TilesToUpdate.Reserve(ActiveTiles.Num()); for (int32 Index = TilesInMinDistance.Num() - 1; Index >= 0; --Index) { // check if it's a new tile if (OldActiveSet.Find(TilesInMinDistance[Index]) == INDEX_NONE) { TilesToUpdate.Add(TilesInMinDistance[Index]); } } RemoveTiles(TilesToRemove); RebuildTile(TilesToUpdate); if (TilesToRemove.Num() > 0 || TilesToUpdate.Num() > 0) { UpdateNavMeshDrawing(); } } void ARecastNavMesh::RemoveTiles(const TArray& Tiles) { if (Tiles.Num() > 0) { FRecastNavMeshGenerator* MyGenerator = static_cast(GetGenerator()); if (MyGenerator) { MyGenerator->RemoveTiles(Tiles); } } } void ARecastNavMesh::RebuildTile(const TArray& Tiles) { if (Tiles.Num() > 0) { FRecastNavMeshGenerator* MyGenerator = static_cast(GetGenerator()); if (MyGenerator) { MyGenerator->ReAddTiles(Tiles); } } } //----------------------------------------------------------------------// // FRecastNavMeshCachedData //----------------------------------------------------------------------// FRecastNavMeshCachedData FRecastNavMeshCachedData::Construct(const ARecastNavMesh* RecastNavMeshActor) { check(RecastNavMeshActor); FRecastNavMeshCachedData CachedData; CachedData.ActorOwner = RecastNavMeshActor; // create copies from crucial ARecastNavMesh data CachedData.bUseSortFunction = RecastNavMeshActor->bSortNavigationAreasByCost; TArray Areas; RecastNavMeshActor->GetSupportedAreas(Areas); FMemory::Memzero(CachedData.FlagsPerArea, sizeof(ARecastNavMesh::FNavPolyFlags) * RECAST_MAX_AREAS); for (int32 i = 0; i < Areas.Num(); i++) { const UClass* AreaClass = Areas[i].AreaClass; const UNavArea* DefArea = AreaClass ? ((UClass*)AreaClass)->GetDefaultObject() : NULL; if (DefArea) { CachedData.AreaClassToIdMap.Add(AreaClass, Areas[i].AreaID); CachedData.FlagsPerArea[Areas[i].AreaID] = DefArea->GetAreaFlags(); } } FMemory::Memcpy(CachedData.FlagsPerOffMeshLinkArea, CachedData.FlagsPerArea, sizeof(CachedData.FlagsPerArea)); static const ARecastNavMesh::FNavPolyFlags NavLinkFlag = ARecastNavMesh::GetNavLinkFlag(); if (NavLinkFlag != 0) { ARecastNavMesh::FNavPolyFlags* AreaFlag = CachedData.FlagsPerOffMeshLinkArea; for (int32 AreaIndex = 0; AreaIndex < RECAST_MAX_AREAS; ++AreaIndex, ++AreaFlag) { *AreaFlag |= NavLinkFlag; } } return CachedData; } void FRecastNavMeshCachedData::OnAreaAdded(const UClass* AreaClass, int32 AreaID) { const UNavArea* DefArea = AreaClass ? ((UClass*)AreaClass)->GetDefaultObject() : NULL; if (DefArea && AreaID >= 0) { AreaClassToIdMap.Add(AreaClass, AreaID); FlagsPerArea[AreaID] = DefArea->GetAreaFlags(); static const ARecastNavMesh::FNavPolyFlags NavLinkFlag = ARecastNavMesh::GetNavLinkFlag(); if (NavLinkFlag != 0) { FlagsPerOffMeshLinkArea[AreaID] = FlagsPerArea[AreaID] | NavLinkFlag; } } } uint32 ARecastNavMesh::GetLinkUserId(NavNodeRef LinkPolyID) const { return RecastNavMeshImpl ? RecastNavMeshImpl->GetLinkUserId(LinkPolyID) : 0; } dtNavMesh* ARecastNavMesh::GetRecastMesh() { return RecastNavMeshImpl ? RecastNavMeshImpl->GetRecastMesh() : nullptr; } const dtNavMesh* ARecastNavMesh::GetRecastMesh() const { return RecastNavMeshImpl ? RecastNavMeshImpl->GetRecastMesh() : nullptr; } #endif// WITH_RECAST //----------------------------------------------------------------------// // BP API //----------------------------------------------------------------------// bool ARecastNavMesh::K2_ReplaceAreaInTileBounds(FBox Bounds, TSubclassOf OldArea, TSubclassOf NewArea, bool ReplaceLinks) { bool bReplaced = ReplaceAreaInTileBounds(Bounds, OldArea, NewArea, ReplaceLinks) > 0; if (bReplaced) { RequestDrawingUpdate(); } return bReplaced; }