You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
[REVIEW] [at]Loic.Devaux, [at]Karl.Dubois, [at]Guillaume.Guay, [at]Aris.Theophanidis #ROBOMERGE-AUTHOR: luciano.ferraro #ROBOMERGE-SOURCE: CL 20178772 via CL 20181533 via CL 20181757 via CL 20181832 via CL 20181875 #ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v943-19904690) [CL 20183664 by luciano ferraro in ue5-main branch]
921 lines
36 KiB
C++
921 lines
36 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
#pragma once
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "Stats/Stats.h"
|
|
#include "AI/Navigation/NavigationTypes.h"
|
|
#include "EngineDefines.h"
|
|
#include "AI/NavigationModifier.h"
|
|
#include "NavigationOctree.h"
|
|
#include "NavMesh/RecastNavMesh.h"
|
|
#include "Async/AsyncWork.h"
|
|
#include "UObject/GCObject.h"
|
|
#include "AI/NavDataGenerator.h"
|
|
#include "NavMesh/RecastHelpers.h"
|
|
#include "NavDebugTypes.h"
|
|
|
|
#if WITH_RECAST
|
|
|
|
#include "Recast/Recast.h"
|
|
#include "Detour/DetourNavMesh.h"
|
|
|
|
#if RECAST_INTERNAL_DEBUG_DATA
|
|
#include "NavMesh/RecastInternalDebugData.h"
|
|
#endif
|
|
|
|
class UBodySetup;
|
|
class ARecastNavMesh;
|
|
class FNavigationOctree;
|
|
class FNavMeshBuildContext;
|
|
class FRecastNavMeshGenerator;
|
|
struct FTileRasterizationContext;
|
|
struct BuildContext;
|
|
struct FNavigationRelevantData;
|
|
struct dtTileCacheLayer;
|
|
struct FKAggregateGeom;
|
|
struct FTileCacheCompressor;
|
|
struct FTileCacheAllocator;
|
|
struct FTileGenerationContext;
|
|
class dtNavMesh;
|
|
class FNavRegenTimeSliceManager;
|
|
class UNavigationSystemV1;
|
|
|
|
#define MAX_VERTS_PER_POLY 6
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
struct FRecastBuildConfig : public rcConfig
|
|
{
|
|
/** controls whether voxel filterring will be applied (via FRecastTileGenerator::ApplyVoxelFilter) */
|
|
uint32 bPerformVoxelFiltering:1;
|
|
/** generate detailed mesh (additional tessellation to match heights of geometry) */
|
|
uint32 bGenerateDetailedMesh:1;
|
|
/** generate BV tree (space partitioning for queries) */
|
|
uint32 bGenerateBVTree:1;
|
|
/** if set, mark areas with insufficient free height instead of cutting them out */
|
|
uint32 bMarkLowHeightAreas : 1;
|
|
/** Expand the top of the area nav modifier's bounds by one cell height when applying to the navmesh.
|
|
If unset, navmesh on top of surfaces might not be marked by marking bounds flush with top surfaces (since navmesh is generated slightly above collision, depending on cell height). */
|
|
uint32 bUseExtraTopCellWhenMarkingAreas : 1;
|
|
/** if set, only single low height span will be allowed under valid one */
|
|
uint32 bFilterLowSpanSequences : 1;
|
|
/** if set, only low height spans with corresponding area modifier will be stored in tile cache (reduces memory, can't modify without full tile rebuild) */
|
|
uint32 bFilterLowSpanFromTileCache : 1;
|
|
|
|
/** region partitioning method used by tile cache */
|
|
int32 TileCachePartitionType;
|
|
/** chunk size for ChunkyMonotone partitioning */
|
|
int32 TileCacheChunkSize;
|
|
|
|
UE_DEPRECATED(4.24, "FRecastBuildConfig.PolyMaxHeight has been deprecated as it has no use")
|
|
int32 PolyMaxHeight;
|
|
/** indicates what's the limit of navmesh polygons per tile. This value is calculated from other
|
|
* factors - DO NOT SET IT TO ARBITRARY VALUE */
|
|
int32 MaxPolysPerTile;
|
|
|
|
/** Actual agent height (in uu)*/
|
|
float AgentHeight;
|
|
/** Actual agent climb (in uu)*/
|
|
float AgentMaxClimb;
|
|
/** Actual agent radius (in uu)*/
|
|
float AgentRadius;
|
|
/** Agent index for filtering links */
|
|
int32 AgentIndex;
|
|
|
|
FRecastBuildConfig()
|
|
{
|
|
Reset();
|
|
}
|
|
|
|
void Reset()
|
|
{
|
|
FMemory::Memzero(*this);
|
|
bPerformVoxelFiltering = true;
|
|
bGenerateDetailedMesh = true;
|
|
bGenerateBVTree = true;
|
|
bMarkLowHeightAreas = false;
|
|
bUseExtraTopCellWhenMarkingAreas = true;
|
|
bFilterLowSpanSequences = false;
|
|
bFilterLowSpanFromTileCache = false;
|
|
// Still initializing, even though the property is deprecated, to avoid static analysis warnings
|
|
PolyMaxHeight = 10;
|
|
MaxPolysPerTile = -1;
|
|
AgentIndex = 0;
|
|
}
|
|
};
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
struct FRecastVoxelCache
|
|
{
|
|
struct FTileInfo
|
|
{
|
|
int16 TileX;
|
|
int16 TileY;
|
|
int32 NumSpans;
|
|
FTileInfo* NextTile;
|
|
rcSpanCache* SpanData;
|
|
};
|
|
|
|
int32 NumTiles;
|
|
|
|
/** tile info */
|
|
FTileInfo* Tiles;
|
|
|
|
FRecastVoxelCache() {}
|
|
FRecastVoxelCache(const uint8* Memory);
|
|
};
|
|
|
|
struct FRecastGeometryCache
|
|
{
|
|
struct FHeader
|
|
{
|
|
FNavigationRelevantData::FCollisionDataHeader Validation;
|
|
|
|
int32 NumVerts;
|
|
int32 NumFaces;
|
|
struct FWalkableSlopeOverride SlopeOverride;
|
|
|
|
static uint32 StaticMagicNumber;
|
|
};
|
|
|
|
FHeader Header;
|
|
|
|
/** recast coords of vertices (size: NumVerts * 3) */
|
|
FVector::FReal* Verts;
|
|
|
|
/** vert indices for triangles (size: NumFaces * 3) */
|
|
int32* Indices;
|
|
|
|
FRecastGeometryCache() {}
|
|
FRecastGeometryCache(const uint8* Memory);
|
|
|
|
static bool IsValid(const uint8* Memory, int32 MemorySize);
|
|
};
|
|
|
|
struct FRecastRawGeometryElement
|
|
{
|
|
// Instance geometry
|
|
TArray<FVector::FReal> GeomCoords;
|
|
TArray<int32> GeomIndices;
|
|
|
|
// Per instance transformations in unreal coords
|
|
// When empty geometry is in world space
|
|
TArray<FTransform> PerInstanceTransform;
|
|
|
|
rcRasterizationFlags RasterizationFlags;
|
|
};
|
|
|
|
struct FRecastAreaNavModifierElement
|
|
{
|
|
TArray<FAreaNavModifier> Areas;
|
|
|
|
// Per instance transformations in unreal coords
|
|
// When empty areas are in world space
|
|
TArray<FTransform> PerInstanceTransform;
|
|
|
|
bool bMaskFillCollisionUnderneathForNavmesh = false;
|
|
};
|
|
|
|
struct FRcTileBox
|
|
{
|
|
int32 XMin, XMax, YMin, YMax;
|
|
|
|
FRcTileBox(const FBox& UnrealBounds, const FVector& RcNavMeshOrigin, const FVector::FReal TileSizeInWorldUnits)
|
|
{
|
|
check(TileSizeInWorldUnits > 0);
|
|
|
|
const FBox RcAreaBounds = Unreal2RecastBox(UnrealBounds);
|
|
XMin = FMath::FloorToInt((RcAreaBounds.Min.X - RcNavMeshOrigin.X) / TileSizeInWorldUnits);
|
|
XMax = FMath::FloorToInt((RcAreaBounds.Max.X - RcNavMeshOrigin.X) / TileSizeInWorldUnits);
|
|
YMin = FMath::FloorToInt((RcAreaBounds.Min.Z - RcNavMeshOrigin.Z) / TileSizeInWorldUnits);
|
|
YMax = FMath::FloorToInt((RcAreaBounds.Max.Z - RcNavMeshOrigin.Z) / TileSizeInWorldUnits);
|
|
}
|
|
|
|
FORCEINLINE bool Contains(const FIntPoint& Point) const
|
|
{
|
|
return Point.X >= XMin && Point.X <= XMax
|
|
&& Point.Y >= YMin && Point.Y <= YMax;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* TIME SLICING
|
|
* The general idea is that any function that handles time slicing internally will be named XXXXTimeSliced
|
|
* and returns a ETimeSliceWorkResult. These functions also call TestTimeSliceFinished() internally when required,
|
|
* IsTimeSliceFinishedCached() can be called externally after they have finished. Non time sliced functions are
|
|
* managed externally and the calling function should call TestTimeSliceFinished() when necessary.
|
|
*/
|
|
|
|
/** Return state of calling time sliced functions */
|
|
enum class ETimeSliceWorkResult : uint8
|
|
{
|
|
Failed,
|
|
Succeeded,
|
|
CallAgainNextTimeSlice, /** time slice is finished this frame but we need to call this functionality again next frame */
|
|
};
|
|
|
|
/** State representing which area of GenerateCompressedLayersTimeSliced() we are processing */
|
|
enum class EGenerateCompressedLayersTimeSliced : uint8
|
|
{
|
|
Invalid,
|
|
Init,
|
|
CreateHeightField,
|
|
RasterizeTriangles,
|
|
EmptyLayers,
|
|
VoxelFilter,
|
|
RecastFilter,
|
|
CompactHeightField,
|
|
ErodeWalkable,
|
|
BuildLayers,
|
|
BuildTileCache,
|
|
};
|
|
|
|
enum class ERasterizeGeomRecastTimeSlicedState : uint8
|
|
{
|
|
MarkWalkableTriangles,
|
|
RasterizeTriangles,
|
|
};
|
|
|
|
enum class ERasterizeGeomTimeSlicedState : uint8
|
|
{
|
|
RasterizeGeometryTransformCoords,
|
|
RasterizeGeometryRecast,
|
|
};
|
|
|
|
enum class EDoWorkTimeSlicedState : uint8
|
|
{
|
|
Invalid,
|
|
GatherGeometryFromSources,
|
|
GenerateTile,
|
|
};
|
|
|
|
enum class EGenerateTileTimeSlicedState : uint8
|
|
{
|
|
Invalid,
|
|
GenerateCompressedLayers,
|
|
GenerateNavigationData,
|
|
};
|
|
|
|
enum class EGenerateNavDataTimeSlicedState : uint8
|
|
{
|
|
Invalid,
|
|
Init,
|
|
GenerateLayers,
|
|
};
|
|
|
|
/**
|
|
* Class handling generation of a single tile, caching data that can speed up subsequent tile generations
|
|
*/
|
|
class NAVIGATIONSYSTEM_API FRecastTileGenerator : public FNoncopyable, public FGCObject
|
|
{
|
|
friend FRecastNavMeshGenerator;
|
|
|
|
public:
|
|
FRecastTileGenerator(FRecastNavMeshGenerator& ParentGenerator, const FIntPoint& Location);
|
|
virtual ~FRecastTileGenerator();
|
|
|
|
/** Does the work involved with regenerating this tile using time slicing.
|
|
* The return value determines the result of the time slicing
|
|
*/
|
|
ETimeSliceWorkResult DoWorkTimeSliced();
|
|
/** Does the work involved with regenerating this tile */
|
|
bool DoWork();
|
|
|
|
FORCEINLINE int32 GetTileX() const { return TileX; }
|
|
FORCEINLINE int32 GetTileY() const { return TileY; }
|
|
/** Whether specified layer was updated */
|
|
FORCEINLINE bool IsLayerChanged(int32 LayerIdx) const { return DirtyLayers[LayerIdx]; }
|
|
FORCEINLINE const TBitArray<>& GetDirtyLayersMask() const { return DirtyLayers; }
|
|
/** Whether tile data was fully regenerated */
|
|
FORCEINLINE bool IsFullyRegenerated() const { return bRegenerateCompressedLayers; }
|
|
/** Whether tile task has anything to build */
|
|
bool HasDataToBuild() const;
|
|
|
|
const TArray<FNavMeshTileData>& GetCompressedLayers() const { return CompressedLayers; }
|
|
|
|
protected:
|
|
// to be used solely by FRecastNavMeshGenerator
|
|
TArray<FNavMeshTileData>& GetNavigationData() { return NavigationData; }
|
|
|
|
public:
|
|
uint32 GetUsedMemCount() const;
|
|
|
|
// Memory amount used to construct generator
|
|
uint32 UsedMemoryOnStartup;
|
|
|
|
// FGCObject begin
|
|
virtual void AddReferencedObjects(FReferenceCollector& Collector) override;
|
|
virtual FString GetReferencerName() const override;
|
|
// FGCObject end
|
|
|
|
#if RECAST_INTERNAL_DEBUG_DATA
|
|
const FRecastInternalDebugData& GetDebugData() const { return DebugData; }
|
|
FRecastInternalDebugData& GetMutableDebugData() { return DebugData; }
|
|
#endif
|
|
|
|
typedef TArray<int32, TInlineAllocator<4096>> TInlineMaskArray;
|
|
|
|
protected:
|
|
/** Does the actual TimeSliced tile generation.
|
|
* @note always trigger tile generation only via DoWorkTimeSliced(). This is a worker function
|
|
* The return value determines the result of the time slicing
|
|
* @return Suceeded if new tile navigation data has been generated and is ready to be added to navmesh instance,
|
|
* @return Failed if failed or no need to generate (still valid).
|
|
* @return CallAgainNextTimeSlice, time slice is finished this frame but we need to call this function again next frame
|
|
*/
|
|
ETimeSliceWorkResult GenerateTileTimeSliced();
|
|
/** Does the actual tile generation.
|
|
* @note always trigger tile generation only via DoWorkTime(). This is a worker function
|
|
* The return value determines the result of the time slicing
|
|
* @return true if new tile navigation data has been generated and is ready to be added to navmesh instance,
|
|
* @return false if failed or no need to generate (still valid).
|
|
*/
|
|
bool GenerateTile();
|
|
|
|
void Setup(const FRecastNavMeshGenerator& ParentGenerator, const TArray<FBox>& DirtyAreas);
|
|
|
|
/** Gather geometry */
|
|
virtual void GatherGeometry(const FRecastNavMeshGenerator& ParentGenerator, bool bGeometryChanged);
|
|
/** Gather geometry sources to be processed later by the GatherGeometryFromSources */
|
|
virtual void PrepareGeometrySources(const FRecastNavMeshGenerator& ParentGenerator, bool bGeometryChanged);
|
|
/** Gather geometry from the prefetched sources */
|
|
void GatherGeometryFromSources();
|
|
/** Gather geometry from the prefetched sources time sliced version */
|
|
ETimeSliceWorkResult GatherGeometryFromSourcesTimeSliced();
|
|
/** Gather geometry from a specified Navigation Data */
|
|
void GatherNavigationDataGeometry(const TSharedRef<FNavigationRelevantData, ESPMode::ThreadSafe>& ElementData, UNavigationSystemV1& NavSys, const FNavDataConfig& OwnerNavDataConfig, bool bGeometryChanged);
|
|
|
|
/** Start functions used by GenerateCompressedLayersTimeSliced / GenerateCompressedLayers */
|
|
bool CreateHeightField(FNavMeshBuildContext& BuildContext, FTileRasterizationContext& RasterContext);
|
|
ETimeSliceWorkResult RasterizeTrianglesTimeSliced(FNavMeshBuildContext& BuildContext, FTileRasterizationContext& RasterContext);
|
|
void RasterizeTriangles(FNavMeshBuildContext& BuildContext, FTileRasterizationContext& RasterContext);
|
|
ETimeSliceWorkResult RasterizeGeometryRecastTimeSliced(FNavMeshBuildContext& BuildContext, const TArray<FVector::FReal>& Coords, const TArray<int32>& Indices, const rcRasterizationFlags RasterizationFlags, FTileRasterizationContext& RasterContext);
|
|
UE_DEPRECATED(5.0, "Call the version of this function where Coords are now a TArray of FReals!")
|
|
ETimeSliceWorkResult RasterizeGeometryRecastTimeSliced(FNavMeshBuildContext& BuildContext, const TArray<float>& Coords, const TArray<int32>& Indices, const rcRasterizationFlags RasterizationFlags, FTileRasterizationContext& RasterContext);
|
|
void RasterizeGeometryRecast(FNavMeshBuildContext& BuildContext, const TArray<FVector::FReal>& Coords, const TArray<int32>& Indices, const rcRasterizationFlags RasterizationFlags, FTileRasterizationContext& RasterContext);
|
|
UE_DEPRECATED(5.0, "Call the version of this function where Coords are now a TArray of FReals!")
|
|
void RasterizeGeometryRecast(FNavMeshBuildContext& BuildContext, const TArray<float>& Coords, const TArray<int32>& Indices, const rcRasterizationFlags RasterizationFlags, FTileRasterizationContext& RasterContext);
|
|
void RasterizeGeometryTransformCoords(const TArray<FVector::FReal>& Coords, const FTransform& LocalToWorld);
|
|
UE_DEPRECATED(5.0, "Call the version of this function where Coords are now a TArray of FReals!")
|
|
void RasterizeGeometryTransformCoords(const TArray<float>& Coords, const FTransform& LocalToWorld);
|
|
ETimeSliceWorkResult RasterizeGeometryTimeSliced(FNavMeshBuildContext& BuildContext, const TArray<FVector::FReal>& Coords, const TArray<int32>& Indices, const FTransform& LocalToWorld, const rcRasterizationFlags RasterizationFlags, FTileRasterizationContext& RasterContext);
|
|
UE_DEPRECATED(5.0, "Call the version of this function where Coords are now a TArray of FReals!")
|
|
ETimeSliceWorkResult RasterizeGeometryTimeSliced(FNavMeshBuildContext& BuildContext, const TArray<float>& Coords, const TArray<int32>& Indices, const FTransform& LocalToWorld, const rcRasterizationFlags RasterizationFlags, FTileRasterizationContext& RasterContext);
|
|
void RasterizeGeometry(FNavMeshBuildContext& BuildContext, const TArray<FVector::FReal>& Coords, const TArray<int32>& Indices, const FTransform& LocalToWorld, const rcRasterizationFlags RasterizationFlags, FTileRasterizationContext& RasterContext);
|
|
UE_DEPRECATED(5.0, "Call the version of this function where Coords are now a TArray of FReals!")
|
|
void RasterizeGeometry(FNavMeshBuildContext& BuildContext, const TArray<float>& Coords, const TArray<int32>& Indices, const FTransform& LocalToWorld, const rcRasterizationFlags RasterizationFlags, FTileRasterizationContext& RasterContext);
|
|
void GenerateRecastFilter(FNavMeshBuildContext& BuildContext, FTileRasterizationContext& RasterContext);
|
|
bool BuildCompactHeightField(FNavMeshBuildContext& BuildContext, FTileRasterizationContext& RasterContext);
|
|
bool RecastErodeWalkable(FNavMeshBuildContext& BuildContext, FTileRasterizationContext& RasterContext);
|
|
bool RecastBuildLayers(FNavMeshBuildContext& BuildContext, FTileRasterizationContext& RasterContext);
|
|
bool RecastBuildTileCache(FNavMeshBuildContext& BuildContext, FTileRasterizationContext& RasterContext);
|
|
/** End functions used by GenerateCompressedLayersTimeSliced / GenerateCompressedLayers */
|
|
|
|
/** builds CompressedLayers array (geometry + modifiers) time sliced*/
|
|
virtual ETimeSliceWorkResult GenerateCompressedLayersTimeSliced(FNavMeshBuildContext& BuildContext);
|
|
/** builds CompressedLayers array (geometry + modifiers) */
|
|
virtual bool GenerateCompressedLayers(FNavMeshBuildContext& BuildContext);
|
|
|
|
/** Builds a navigation data layer */
|
|
bool GenerateNavigationDataLayer(FNavMeshBuildContext& BuildContext, FTileCacheCompressor& TileCompressor, FTileCacheAllocator& GenNavAllocator, FTileGenerationContext& GenerationContext, int32 LayerIdx);
|
|
|
|
/** builds NavigationData array (layers + obstacles) time sliced */
|
|
ETimeSliceWorkResult GenerateNavigationDataTimeSliced(FNavMeshBuildContext& BuildContext);
|
|
|
|
/** builds NavigationData array (layers + obstacles) */
|
|
bool GenerateNavigationData(FNavMeshBuildContext& BuildContext);
|
|
|
|
virtual void ApplyVoxelFilter(struct rcHeightfield* SolidHF, FVector::FReal WalkableRadius);
|
|
|
|
/** Compute rasterization mask */
|
|
void InitRasterizationMaskArray(const rcHeightfield* SolidHF, TInlineMaskArray& OutRasterizationMasks);
|
|
void ComputeRasterizationMasks(FNavMeshBuildContext& BuildContext, FTileRasterizationContext& RasterContext);
|
|
void MarkRasterizationMask(rcContext* /*BuildContext*/, rcHeightfield* SolidHF,
|
|
const FAreaNavModifier& Modifier, const FTransform& LocalToWorld, const int32 Mask, TInlineMaskArray& OutMaskArray);
|
|
|
|
/** apply areas from DynamicAreas to layer */
|
|
void MarkDynamicAreas(dtTileCacheLayer& Layer);
|
|
void MarkDynamicArea(const FAreaNavModifier& Modifier, const FTransform& LocalToWorld, dtTileCacheLayer& Layer);
|
|
void MarkDynamicArea(const FAreaNavModifier& Modifier, const FTransform& LocalToWorld, dtTileCacheLayer& Layer, const int32 AreaId, const int32* ReplaceAreaIdPtr);
|
|
|
|
void AppendModifier(const FCompositeNavModifier& Modifier, const FNavDataPerInstanceTransformDelegate& InTransformsDelegate);
|
|
/** Appends specified geometry to tile's geometry */
|
|
void ValidateAndAppendGeometry(const TSharedRef<FNavigationRelevantData, ESPMode::ThreadSafe>& ElementData, const FCompositeNavModifier& InModifier);
|
|
void AppendGeometry(const FNavigationRelevantData& DataRef, const FCompositeNavModifier& InModifier, const FNavDataPerInstanceTransformDelegate& InTransformsDelegate);
|
|
void AppendVoxels(rcSpanCache* SpanData, int32 NumSpans);
|
|
|
|
/** prepare voxel cache from collision data */
|
|
void PrepareVoxelCache(const TNavStatArray<uint8>& RawCollisionCache, const FCompositeNavModifier& InModifier, TNavStatArray<rcSpanCache>& SpanData);
|
|
bool HasVoxelCache(const TNavStatArray<uint8>& RawVoxelCache, rcSpanCache*& CachedVoxels, int32& NumCachedVoxels) const;
|
|
void AddVoxelCache(TNavStatArray<uint8>& RawVoxelCache, const rcSpanCache* CachedVoxels, const int32 NumCachedVoxels) const;
|
|
|
|
void DumpAsyncData();
|
|
|
|
#if RECAST_INTERNAL_DEBUG_DATA
|
|
bool IsTileDebugActive();
|
|
#endif
|
|
|
|
protected:
|
|
uint32 bRegenerateCompressedLayers : 1;
|
|
uint32 bFullyEncapsulatedByInclusionBounds : 1;
|
|
uint32 bUpdateGeometry : 1;
|
|
uint32 bHasLowAreaModifiers : 1;
|
|
|
|
/** Start time slicing variables */
|
|
ERasterizeGeomRecastTimeSlicedState RasterizeGeomRecastState;
|
|
ERasterizeGeomTimeSlicedState RasterizeGeomState;
|
|
EDoWorkTimeSlicedState DoWorkTimeSlicedState;
|
|
EGenerateTileTimeSlicedState GenerateTileTimeSlicedState;
|
|
|
|
EGenerateNavDataTimeSlicedState GenerateNavDataTimeSlicedState;
|
|
int32 GenNavDataLayerTimeSlicedIdx;
|
|
EGenerateCompressedLayersTimeSliced GenCompressedLayersTimeSlicedState;
|
|
int32 RasterizeTrianglesTimeSlicedRawGeomIdx;
|
|
int32 RasterizeTrianglesTimeSlicedInstTransformIdx;
|
|
TNavStatArray<uint8> RasterizeGeomRecastTriAreas;
|
|
const FNavRegenTimeSliceManager* TimeSliceManager;
|
|
|
|
TUniquePtr<struct FTileCacheAllocator> GenNavDataTimeSlicedAllocator;
|
|
TUniquePtr<struct FTileGenerationContext> GenNavDataTimeSlicedGenerationContext;
|
|
TUniquePtr<struct FTileRasterizationContext> GenCompressedlayersTimeSlicedRasterContext;
|
|
/** End time slicing variables */
|
|
|
|
int32 TileX;
|
|
int32 TileY;
|
|
uint32 Version;
|
|
/** Tile's bounding box, Unreal coords */
|
|
FBox TileBB;
|
|
|
|
/** Tile's bounding box expanded by Agent's radius *2 + CellSize, Unreal coords */
|
|
FBox TileBBExpandedForAgent;
|
|
|
|
/** Layers dirty flags */
|
|
TBitArray<> DirtyLayers;
|
|
|
|
/** Parameters defining navmesh tiles */
|
|
FRecastBuildConfig TileConfig;
|
|
|
|
/** Bounding geometry definition. */
|
|
TNavStatArray<FBox> InclusionBounds;
|
|
|
|
/** Additional config */
|
|
FRecastNavMeshCachedData AdditionalCachedData;
|
|
|
|
// generated tile data
|
|
TArray<FNavMeshTileData> CompressedLayers;
|
|
TArray<FNavMeshTileData> NavigationData;
|
|
|
|
/** Result of calling RasterizeGeometryInitVars() */
|
|
TArray<FVector::FReal> RasterizeGeometryWorldRecastCoords;
|
|
|
|
// tile's geometry: without voxel cache
|
|
TArray<FRecastRawGeometryElement> RawGeometry;
|
|
// areas used for creating navigation data: obstacles
|
|
TArray<FRecastAreaNavModifierElement> Modifiers;
|
|
// navigation links
|
|
TArray<FSimpleLinkNavModifier> OffmeshLinks;
|
|
|
|
TWeakPtr<FNavDataGenerator, ESPMode::ThreadSafe> ParentGeneratorWeakPtr;
|
|
|
|
TNavStatArray<TSharedRef<FNavigationRelevantData, ESPMode::ThreadSafe> > NavigationRelevantData;
|
|
TWeakObjectPtr<UNavigationSystemV1> NavSystem;
|
|
FNavDataConfig NavDataConfig;
|
|
|
|
FRecastNavMeshTileGenerationDebug TileDebugSettings;
|
|
|
|
#if RECAST_INTERNAL_DEBUG_DATA
|
|
FRecastInternalDebugData DebugData;
|
|
#endif
|
|
};
|
|
|
|
struct NAVIGATIONSYSTEM_API FRecastTileGeneratorWrapper : public FNonAbandonableTask
|
|
{
|
|
TSharedRef<FRecastTileGenerator> TileGenerator;
|
|
|
|
FRecastTileGeneratorWrapper(TSharedRef<FRecastTileGenerator> InTileGenerator)
|
|
: TileGenerator(InTileGenerator)
|
|
{
|
|
}
|
|
|
|
void DoWork()
|
|
{
|
|
TileGenerator->DoWork();
|
|
}
|
|
|
|
FORCEINLINE TStatId GetStatId() const
|
|
{
|
|
RETURN_QUICK_DECLARE_CYCLE_STAT(FRecastTileGenerator, STATGROUP_ThreadPoolAsyncTasks);
|
|
}
|
|
};
|
|
|
|
typedef FAsyncTask<FRecastTileGeneratorWrapper> FRecastTileGeneratorTask;
|
|
//typedef FAsyncTask<FRecastTileGenerator> FRecastTileGeneratorTask;
|
|
|
|
struct FPendingTileElement
|
|
{
|
|
/** tile coordinates on a grid in recast space */
|
|
FIntPoint Coord;
|
|
/** distance to seed, used for sorting pending tiles */
|
|
FVector::FReal SeedDistance;
|
|
/** Whether we need a full rebuild for this tile grid cell */
|
|
bool bRebuildGeometry;
|
|
/** We need to store dirty area bounds to check which cached layers needs to be regenerated
|
|
* In case geometry is changed cached layers data will be fully regenerated without using dirty areas list
|
|
*/
|
|
TArray<FBox> DirtyAreas;
|
|
|
|
FPendingTileElement()
|
|
: Coord(FIntPoint::NoneValue)
|
|
, SeedDistance(TNumericLimits<FVector::FReal>::Max())
|
|
, bRebuildGeometry(false)
|
|
{
|
|
}
|
|
|
|
bool operator == (const FIntPoint& Location) const
|
|
{
|
|
return Coord == Location;
|
|
}
|
|
|
|
bool operator == (const FPendingTileElement& Other) const
|
|
{
|
|
return Coord == Other.Coord;
|
|
}
|
|
|
|
bool operator < (const FPendingTileElement& Other) const
|
|
{
|
|
return Other.SeedDistance < SeedDistance;
|
|
}
|
|
|
|
friend uint32 GetTypeHash(const FPendingTileElement& Element)
|
|
{
|
|
return GetTypeHash(Element.Coord);
|
|
}
|
|
};
|
|
|
|
struct FRunningTileElement
|
|
{
|
|
FRunningTileElement()
|
|
: Coord(FIntPoint::NoneValue)
|
|
, bShouldDiscard(false)
|
|
, AsyncTask(nullptr)
|
|
{
|
|
}
|
|
|
|
FRunningTileElement(FIntPoint InCoord)
|
|
: Coord(InCoord)
|
|
, bShouldDiscard(false)
|
|
, AsyncTask(nullptr)
|
|
{
|
|
}
|
|
|
|
bool operator == (const FRunningTileElement& Other) const
|
|
{
|
|
return Coord == Other.Coord;
|
|
}
|
|
|
|
/** tile coordinates on a grid in recast space */
|
|
FIntPoint Coord;
|
|
/** whether generated results should be discarded */
|
|
bool bShouldDiscard;
|
|
FRecastTileGeneratorTask* AsyncTask;
|
|
};
|
|
|
|
struct FTileTimestamp
|
|
{
|
|
uint32 TileIdx;
|
|
double Timestamp;
|
|
|
|
bool operator == (const FTileTimestamp& Other) const
|
|
{
|
|
return TileIdx == Other.TileIdx;
|
|
}
|
|
};
|
|
|
|
enum class EProcessTileTasksSyncTimeSlicedState : uint8
|
|
{
|
|
Init,
|
|
DoWork,
|
|
AddGeneratedTiles,
|
|
StoreCompessedTileCacheLayers,
|
|
AppendUpdateTiles,
|
|
Finish,
|
|
};
|
|
|
|
enum class EAddGeneratedTilesTimeSlicedState : uint8
|
|
{
|
|
Init,
|
|
AddTiles,
|
|
};
|
|
|
|
/**
|
|
* Class that handles generation of the whole Recast-based navmesh.
|
|
*/
|
|
class NAVIGATIONSYSTEM_API FRecastNavMeshGenerator : public FNavDataGenerator
|
|
{
|
|
public:
|
|
FRecastNavMeshGenerator(ARecastNavMesh& InDestNavMesh);
|
|
virtual ~FRecastNavMeshGenerator();
|
|
|
|
private:
|
|
/** Prevent copying. */
|
|
FRecastNavMeshGenerator(FRecastNavMeshGenerator const& NoCopy) { check(0); };
|
|
FRecastNavMeshGenerator& operator=(FRecastNavMeshGenerator const& NoCopy) { check(0); return *this; }
|
|
|
|
public:
|
|
virtual bool RebuildAll() override;
|
|
virtual void EnsureBuildCompletion() override;
|
|
virtual void CancelBuild() override;
|
|
virtual void TickAsyncBuild(float DeltaSeconds) override;
|
|
virtual void OnNavigationBoundsChanged() override;
|
|
|
|
/** Asks generator to update navigation affected by DirtyAreas */
|
|
virtual void RebuildDirtyAreas(const TArray<FNavigationDirtyArea>& DirtyAreas) override;
|
|
|
|
/** determines whether this generator is performing navigation building actions at the moment, dirty areas are also checked */
|
|
virtual bool IsBuildInProgressCheckDirty() const override;
|
|
|
|
#if !RECAST_ASYNC_REBUILDING
|
|
/** returns true if we are time slicing and the data is valid to use false otherwise*/
|
|
virtual bool GetTimeSliceData(int32& OutNumRemainingBuildTasks, double& OutCurrentBuildTaskDuration) const override;
|
|
#endif
|
|
|
|
int32 GetNumRemaningBuildTasksHelper() const { return RunningDirtyTiles.Num() + PendingDirtyTiles.Num() + static_cast<int32>(SyncTimeSlicedData.TileGeneratorSync.IsValid()); }
|
|
virtual int32 GetNumRemaningBuildTasks() const override;
|
|
virtual int32 GetNumRunningBuildTasks() const override;
|
|
|
|
/** Checks if a given tile is being build or has just finished building */
|
|
bool IsTileChanged(int32 TileIdx) const;
|
|
|
|
FORCEINLINE uint32 GetVersion() const { return Version; }
|
|
|
|
const ARecastNavMesh* GetOwner() const { return DestNavMesh; }
|
|
|
|
/** update area data */
|
|
void OnAreaAdded(const UClass* AreaClass, int32 AreaID);
|
|
|
|
//--- accessors --- //
|
|
FORCEINLINE class UWorld* GetWorld() const { return DestNavMesh->GetWorld(); }
|
|
|
|
const FRecastBuildConfig& GetConfig() const { return Config; }
|
|
|
|
const FRecastNavMeshTileGenerationDebug& GetTileDebugSettings() const { return DestNavMesh->TileGenerationDebug; }
|
|
|
|
const TNavStatArray<FBox>& GetInclusionBounds() const { return InclusionBounds; }
|
|
|
|
FVector GetRcNavMeshOrigin() const { return RcNavMeshOrigin; }
|
|
|
|
/** checks if any on InclusionBounds encapsulates given box.
|
|
* @return index to first item in InclusionBounds that meets expectations */
|
|
int32 FindInclusionBoundEncapsulatingBox(const FBox& Box) const;
|
|
|
|
/** Total navigable area box, sum of all navigation volumes bounding boxes */
|
|
FBox GetTotalBounds() const { return TotalNavBounds; }
|
|
|
|
const FRecastNavMeshCachedData& GetAdditionalCachedData() const { return AdditionalCachedData; }
|
|
|
|
bool HasDirtyTiles() const;
|
|
bool HasDirtyTiles(const FBox& AreaBounds) const;
|
|
int32 GetDirtyTilesCount(const FBox& AreaBounds) const;
|
|
|
|
bool GatherGeometryOnGameThread() const;
|
|
bool IsTimeSliceRegenActive() const;
|
|
|
|
FBox GrowBoundingBox(const FBox& BBox, bool bIncludeAgentHeight) const;
|
|
|
|
/** Returns if the provided Octree Element should generate geometry on the provided NavDataConfig. Can be used to extend the logic to decide what geometry is generated on what Navmesh */
|
|
virtual bool ShouldGenerateGeometryForOctreeElement(const FNavigationOctreeElement& Element, const FNavDataConfig& NavDataConfig) const;
|
|
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) && ENABLE_VISUAL_LOG
|
|
virtual void ExportNavigationData(const FString& FileName) const override;
|
|
virtual void GrabDebugSnapshot(struct FVisualLogEntry* Snapshot, const FBox& BoundingBox, const FName& CategoryName, ELogVerbosity::Type Verbosity) const override;
|
|
#endif
|
|
|
|
/**
|
|
* @param Actor is a reference to make callee responsible for assuring it's valid
|
|
*/
|
|
static void ExportComponentGeometry(UActorComponent* Component, FNavigationRelevantData& Data);
|
|
static void ExportVertexSoupGeometry(const TArray<FVector>& Verts, FNavigationRelevantData& Data);
|
|
|
|
static void ExportRigidBodyGeometry(UBodySetup& BodySetup, TNavStatArray<FVector>& OutVertexBuffer, TNavStatArray<int32>& OutIndexBuffer, const FTransform& LocalToWorld = FTransform::Identity);
|
|
static void ExportRigidBodyGeometry(UBodySetup& BodySetup, TNavStatArray<FVector>& OutTriMeshVertexBuffer, TNavStatArray<int32>& OutTriMeshIndexBuffer, TNavStatArray<FVector>& OutConvexVertexBuffer, TNavStatArray<int32>& OutConvexIndexBuffer, TNavStatArray<int32>& OutShapeBuffer, const FTransform& LocalToWorld = FTransform::Identity);
|
|
static void ExportAggregatedGeometry(const FKAggregateGeom& AggGeom, TNavStatArray<FVector>& OutConvexVertexBuffer, TNavStatArray<int32>& OutConvexIndexBuffer, TNavStatArray<int32>& OutShapeBuffer, const FTransform& LocalToWorld = FTransform::Identity);
|
|
|
|
#if UE_ENABLE_DEBUG_DRAWING
|
|
/** Converts data encoded in EncodedData.CollisionData to FNavDebugMeshData format */
|
|
static void GetDebugGeometry(const FNavigationRelevantData& EncodedData, FNavDebugMeshData& DebugMeshData);
|
|
#endif // UE_ENABLE_DEBUG_DRAWING
|
|
|
|
const FNavRegenTimeSliceManager* GetTimeSliceManager() const { return SyncTimeSlicedData.TimeSliceManager; }
|
|
|
|
void SetNextTimeSliceRegenActive(bool bRegenState) { SyncTimeSlicedData.bNextTimeSliceRegenActive = bRegenState; }
|
|
|
|
protected:
|
|
// Performs initial setup of member variables so that generator is ready to
|
|
// do its thing from this point on. Called just after construction by ARecastNavMesh
|
|
virtual void Init();
|
|
|
|
// Used to configure Config. Override to influence build properties
|
|
virtual void ConfigureBuildProperties(FRecastBuildConfig& OutConfig);
|
|
|
|
// Updates cached list of navigation bounds
|
|
void UpdateNavigationBounds();
|
|
|
|
// Sorts pending build tiles by proximity to player, so tiles closer to player will get generated first
|
|
virtual void SortPendingBuildTiles();
|
|
|
|
// Get seed locations used for sorting pending build tiles. Tiles closer to these locations will be prioritized first.
|
|
virtual void GetSeedLocations(UWorld& World, TArray<FVector2D>& OutSeedLocations) const;
|
|
|
|
/** Instantiates dtNavMesh and configures it for tiles generation. Returns false if failed */
|
|
bool ConstructTiledNavMesh();
|
|
|
|
/** Determine bit masks for poly address */
|
|
void CalcNavMeshProperties(int32& MaxTiles, int32& MaxPolys);
|
|
|
|
/** Marks grid tiles affected by specified areas as dirty */
|
|
virtual void MarkDirtyTiles(const TArray<FNavigationDirtyArea>& DirtyAreas);
|
|
|
|
/** Returns if the provided UObject that requested a navmesh dirtying should dirty this Navmesh. Useful to avoid tiles regeneration from objects that are excluded from the provided NavDataConfig */
|
|
virtual bool ShouldDirtyTilesRequestedByObject(const UNavigationSystemV1& NavSys, const FNavigationOctree& NavOctreeInstance, const UObject& SourceObject, const FNavDataConfig& NavDataConfig) const;
|
|
|
|
/** Marks all tiles overlapping with InclusionBounds dirty (via MarkDirtyTiles). */
|
|
bool MarkNavBoundsDirty();
|
|
|
|
void RemoveLayers(const FIntPoint& Tile, TArray<uint32>& UpdatedTiles);
|
|
|
|
void StoreCompressedTileCacheLayers(const FRecastTileGenerator& TileGenerator, int32 TileX, int32 TileY);
|
|
|
|
#if RECAST_INTERNAL_DEBUG_DATA
|
|
void StoreDebugData(const FRecastTileGenerator& TileGenerator, int32 TileX, int32 TileY);
|
|
#endif
|
|
|
|
#if RECAST_ASYNC_REBUILDING
|
|
/** Processes pending tile generation tasks Async*/
|
|
TArray<uint32> ProcessTileTasksAsync(const int32 NumTasksToProcess);
|
|
#else
|
|
TSharedRef<FRecastTileGenerator> CreateTileGeneratorFromPendingElement(FIntPoint &OutTileLocation);
|
|
/** Processes pending tile generation tasks Sync with option for time slicing currently an experimental feature. */
|
|
TArray<uint32> ProcessTileTasksSyncTimeSliced();
|
|
TArray<uint32> ProcessTileTasksSync(const int32 NumTasksToProcess);
|
|
#endif
|
|
/** Processes pending tile generation tasks */
|
|
TArray<uint32> ProcessTileTasks(const int32 NumTasksToProcess);
|
|
|
|
void ResetTimeSlicedTileGeneratorSync();
|
|
|
|
public:
|
|
/** Adds generated tiles to NavMesh, replacing old ones, uses time slicing returns Failed if any layer failed */
|
|
ETimeSliceWorkResult AddGeneratedTilesTimeSliced(FRecastTileGenerator& TileGenerator, TArray<uint32>& OutResultTileIndices);
|
|
/** Adds generated tiles to NavMesh, replacing old ones */
|
|
TArray<uint32> AddGeneratedTiles(FRecastTileGenerator& TileGenerator);
|
|
|
|
public:
|
|
/** Removes all tiles at specified grid location */
|
|
TArray<uint32> RemoveTileLayers(const int32 TileX, const int32 TileY, TMap<int32, dtPolyRef>* OldLayerTileIdMap = nullptr);
|
|
|
|
void RemoveTiles(const TArray<FIntPoint>& Tiles);
|
|
void ReAddTiles(const TArray<FIntPoint>& Tiles);
|
|
|
|
bool IsBuildingRestrictedToActiveTiles() const { return bRestrictBuildingToActiveTiles; }
|
|
|
|
/** sets a limit to number of asynchronous tile generators running at one time
|
|
* @note if used at runtime will not result in killing tasks above limit count
|
|
* @mote function does not validate the input parameter - it's on caller */
|
|
void SetMaxTileGeneratorTasks(int32 NewLimit) { MaxTileGeneratorTasks = NewLimit; }
|
|
|
|
static void CalcPolyRefBits(ARecastNavMesh* NavMeshOwner, int32& MaxTileBits, int32& MaxPolyBits);
|
|
|
|
protected:
|
|
bool IsInActiveSet(const FIntPoint& Tile) const;
|
|
virtual void RestrictBuildingToActiveTiles(bool InRestrictBuildingToActiveTiles);
|
|
|
|
/** Blocks until build for specified list of tiles is complete and discard results */
|
|
void DiscardCurrentBuildingTasks();
|
|
|
|
virtual TSharedRef<FRecastTileGenerator> CreateTileGenerator(const FIntPoint& Coord, const TArray<FBox>& DirtyAreas);
|
|
|
|
template <typename T>
|
|
TSharedRef<T> ConstuctTileGeneratorImpl(const FIntPoint& Coord, const TArray<FBox>& DirtyAreas)
|
|
{
|
|
TSharedRef<T> TileGenerator = MakeShareable(new T(*this, Coord));
|
|
TileGenerator->Setup(*this, DirtyAreas);
|
|
return TileGenerator;
|
|
}
|
|
|
|
void SetBBoxGrowth(const FVector& InBBox) { BBoxGrowth = InBBox; }
|
|
|
|
//----------------------------------------------------------------------//
|
|
// debug
|
|
//----------------------------------------------------------------------//
|
|
virtual uint32 LogMemUsed() const override;
|
|
|
|
void AddGeneratedTileLayer(int32 LayerIndex, FRecastTileGenerator& TileGenerator, const TMap<int32, dtPolyRef>& OldLayerTileIdMap, TArray<uint32>& OutResultTileIndices);
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
/** Data struct used by 'LogDirtyAreas' that contains all the information regarding the areas that are being dirtied, per dirtied tile. */
|
|
struct FNavigationDirtyAreaPerTileDebugInformation
|
|
{
|
|
FNavigationDirtyArea DirtyArea;
|
|
bool bTileWasAlreadyAdded = false;
|
|
};
|
|
|
|
/** Used internally, when LogNavigationDirtyArea is VeryVerbose, to log the number of tiles a dirty area is requesting. */
|
|
void LogDirtyAreas(const TMap<FPendingTileElement, TArray<FNavigationDirtyAreaPerTileDebugInformation>>& DirtyAreasDebuggingInformation) const;
|
|
#endif
|
|
|
|
protected:
|
|
friend ARecastNavMesh;
|
|
|
|
/** Parameters defining navmesh tiles */
|
|
FRecastBuildConfig Config;
|
|
|
|
/** Used to grow generic element bounds to match this generator's properties
|
|
* (most notably Config.borderSize) */
|
|
FVector BBoxGrowth;
|
|
|
|
int32 NumActiveTiles;
|
|
/** the limit to number of asynchronous tile generators running at one time,
|
|
* this is also used by the sync non time sliced regeneration ProcessTileTasksSync,
|
|
* but not by ProcessTileTasksSyncTimeSliced.
|
|
*/
|
|
int32 MaxTileGeneratorTasks;
|
|
|
|
FVector::FReal AvgLayersPerTile;
|
|
|
|
/** Total bounding box that includes all volumes, in unreal units. */
|
|
FBox TotalNavBounds;
|
|
|
|
/** Bounding geometry definition. */
|
|
TNavStatArray<FBox> InclusionBounds;
|
|
|
|
/** Navigation mesh that owns this generator */
|
|
ARecastNavMesh* DestNavMesh;
|
|
|
|
/** List of dirty tiles that needs to be regenerated */
|
|
TNavStatArray<FPendingTileElement> PendingDirtyTiles;
|
|
|
|
/** List of dirty tiles currently being regenerated */
|
|
TNavStatArray<FRunningTileElement> RunningDirtyTiles;
|
|
|
|
#if WITH_EDITOR
|
|
/** List of tiles that were recently regenerated */
|
|
TNavStatArray<FTileTimestamp> RecentlyBuiltTiles;
|
|
#endif// WITH_EDITOR
|
|
|
|
TArray<FIntPoint> ActiveTiles;
|
|
|
|
/** */
|
|
FRecastNavMeshCachedData AdditionalCachedData;
|
|
|
|
/** Use this if you don't want your tiles to start at (0,0,0) */
|
|
FVector RcNavMeshOrigin;
|
|
|
|
uint32 bInitialized:1;
|
|
|
|
uint32 bRestrictBuildingToActiveTiles:1;
|
|
|
|
uint32 bSortTilesWithSeedLocations:1;
|
|
|
|
/** Runtime generator's version, increased every time all tile generators get invalidated
|
|
* like when navmesh size changes */
|
|
uint32 Version;
|
|
|
|
/** Grouping all the member variables used by the time slicing code together for neatness */
|
|
struct FSyncTimeSlicedData
|
|
{
|
|
FSyncTimeSlicedData();
|
|
|
|
double CurrentTileRegenDuration;
|
|
/** if we are currently using time sliced regen or not - currently an experimental feature.
|
|
* do not manipulate this value directly instead call SetNextTimeSliceRegenState()
|
|
*/
|
|
bool bTimeSliceRegenActive;
|
|
bool bNextTimeSliceRegenActive;
|
|
|
|
/** Used by ProcessTileTasksSyncTimeSliced */
|
|
EProcessTileTasksSyncTimeSlicedState ProcessTileTasksSyncState;
|
|
|
|
/** Used by ProcessTileTasksSyncTimeSliced */
|
|
TArray<uint32> UpdatedTilesCache;
|
|
|
|
/** Used by AddGeneratedTilesTimeSliced */
|
|
TArray<uint32> ResultTileIndicesCached;
|
|
|
|
/** Used by AddGeneratedTilesTimeSliced */
|
|
TMap<int32, dtPolyRef> OldLayerTileIdMapCached;
|
|
|
|
/** Used by AddGeneratedTilesTimeSliced */
|
|
EAddGeneratedTilesTimeSlicedState AddGeneratedTilesState;
|
|
|
|
/** Used by AddGeneratedTilesTimeSliced */
|
|
int32 AddGenTilesLayerIndex;
|
|
|
|
/** Do not null or Reset this directly instead call ResetTimeSlicedTileGeneratorSync(). The only exception currently is in ProcessTileTasksSyncTimeSliced */
|
|
TSharedPtr<FRecastTileGenerator> TileGeneratorSync;
|
|
FNavRegenTimeSliceManager* TimeSliceManager;
|
|
};
|
|
|
|
FSyncTimeSlicedData SyncTimeSlicedData;
|
|
};
|
|
|
|
#endif // WITH_RECAST
|