// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "MassLODLogic.h" #include "MassLODUtils.h" #include "DrawDebugHelpers.h" #include "VisualLogger/VisualLogger.h" /** * Helper struct to calculate LOD for each agent and maximize count per LOD * Requires TViewerInfoFragment fragment collected by the TMassLODCollector. * Stores information in TLODFragment fragment. */ template struct TMassLODCalculator : public FMassLODBaseLogic { TMassLODCalculator() : FMassLODBaseLogic(/*bShouldBuildFrustumData=*/FLODLogic::bDoVisibilityLogic) {} /** * Initializes the LOD calculator, needed to be called once at initialization time * @Param InBaseLODDistance distances used to calculate LOD * @Param InBufferHysteresisOnFOVRatio distance hysteresis used to calculate LOD * @Param InLODMaxCount the maximum count for each LOD - Supports nullptr being passed in now and will put INT_MAX everywhere by default * @Param InLODMaxCountPerViewer the maximum count for each LOD per viewer (Only when FLODLogic::bMaximizeCountPerViewer is enabled) * @Param InVisibleDistanceToFrustum is the distance from the frustum to start considering this entity is visible (Only when FLODLogic::bDoVisibilityLogic is enabled) * @Param InVisibleDistanceToFrustumHysteresis once visible, what extra distance the entity need to be before considered not visible anymore (Only when FLODLogic::bDoVisibilityLogic is enabled) * @Param InVisibleLODDistance the maximum count for each LOD per viewer (Only when FLODLogic::bDoVisibilityLogic is enabled) */ void Initialize(const float InBaseLODDistance[EMassLOD::Max], const float InBufferHysteresisOnDistanceRatio, const int32 InLODMaxCount[EMassLOD::Max], const int32 InLODMaxCountPerViewer[EMassLOD::Max] = nullptr, const float InVisibleDistanceToFrustum = 0.0f, const float InVisibleDistanceToFrustumHysteresis = 0.0f, const float InVisibleLODDistance[EMassLOD::Max] = nullptr); /** * Prepares execution for the current frame, needed to be called before every execution * @Param Viewers is the array of all the known viewers */ void PrepareExecution(TConstArrayView Viewers); /** * Calculate LOD, called for each entity chunks * Use next method when FLODLogic::bStoreInfoPerViewer is enabled * @Param Context of the chunk execution * @Param ViewersInfoList is the source information fragment for LOD calculation * @Param LODList is the fragment where calculation are stored */ template inline void CalculateLOD(FMassExecutionContext& Context, TConstArrayView ViewersInfoList, TArrayView LODList) { CalculateLOD(Context, ViewersInfoList, LODList, TConstArrayView()); } /** * Calculate LOD, called for each entity chunks * Use this version when FLODLogic::bStoreInfoPerViewer is enabled * It calculates a LOD per viewer and needs information per viewer via PerViewerInfoList fragments * @Param Context of the chunk execution * @Param ViewersInfoList is the source information fragment for LOD calculation * @Param LODList is the fragment where calculation are stored * @Param PerViewerInfoList is the Per viewer source information */ template void CalculateLOD(FMassExecutionContext& Context, TConstArrayView ViewersInfoList, TArrayView LODList, TConstArrayView PerViewerInfoList); /** * Adjust LOD distances by clamping them to respect the maximum LOD count * @Return true if any LOD distances clamping was done */ template bool AdjustDistancesFromCount(); /** * Adjust LOD from newly adjusted distances, only needed to be called when AdjustDistancesFromCount return true, called for each entity chunks * Use next method when FLODLogic::bStoreInfoPerViewer is enabled * @Param Context of the chunk execution * @Param ViewersInfoList is the source information fragment for LOD calculation * @Param LODList is the fragment where calculation are stored */ template inline void AdjustLODFromCount(FMassExecutionContext& Context, TConstArrayView ViewersInfoList, TArrayView LODList) { AdjustLODFromCount(Context, ViewersInfoList, LODList, TConstArrayView()); } /** * Adjust LOD from newly adjusted distances, only needed to be called when AdjustDistancesFromCount return true, called for each entity chunks * Use this version when FLODLogic::bStoreInfoPerViewer is enabled * It calculates a LOD per viewer and needs information per viewer via PerViewerInfoList fragments * @Param Context of the chunk execution * @Param ViewersInfoList is the source information fragment for LOD calculation * @Param LODList is the fragment where calculation are stored * @Param PerViewerInfoList is the Per viewer source information */ template void AdjustLODFromCount(FMassExecutionContext& Context, TConstArrayView ViewersInfoList, TArrayView LODList, TConstArrayView PerViewerInfoList); /** * Turn Off all LOD, called for each entity chunks * @Param Context of the chunk execution * @Param LODList is the fragment where calculation are stored */ template void ForceOffLOD(FMassExecutionContext& Context, TArrayView LODList); #if WITH_MASSGAMEPLAY_DEBUG /** * Debug draw the current state of each agent as a color coded square * @Param Context of the chunk execution * @Param LODList is the fragment where calculation are stored * @Param LocationList is the fragment transforms of the entities * @Param World where the debug display should be drawn */ template void DebugDisplayLOD(FMassExecutionContext& Context, TConstArrayView LODList, TConstArrayView LocationList, UWorld* World); /** * Debug draw the current state of each agent as a color coded square, within MaxLODSignificance range * @Param Context of the chunk execution * @Param LODList is the fragment where calculation are stored * @Param LocationList is the fragment transforms of the entities * @Param World where the debug display should be drawn * @Param MaxLODSignificance is the max allowed value of LODList[i].LODSignificance for an agent's state to debug draw */ template void DebugDisplaySignificantLOD(FMassExecutionContext& Context, TConstArrayView LODList, TConstArrayView LocationList, UWorld* World, float MaxLODSignificance); /** * Add Visual Log entries for the current state of each agent as a color coded location * @Param Context of the chunk execution * @Param LODList is the fragment where calculation are stored * @Param LocationList is the fragment transforms of the entities * @Param World where the debug display should be drawn */ template void VisLogLOD(FMassExecutionContext& Context, TConstArrayView LODList, TConstArrayView LocationList, UObject* LogOwner); /** * Add Visual Log entries for the current state of each agent as a color coded location, within MaxLODSignificance range * @Param Context of the chunk execution * @Param LODList is the fragment where calculation are stored * @Param LocationList is the fragment transforms of the entities * @Param World where the debug display should be drawn * @Param MaxLODSignificance is the max allowed value of LODList[i].LODSignificance for an agent's state to vislog */ template void VisLogSignificantLOD(FMassExecutionContext& Context, TConstArrayView LODList, TConstArrayView LocationList, UObject* LogOwner, float MaxLODSignificance); #endif // WITH_MASSGAMEPLAY_DEBUG /** * Return the maximum distance at which the LOD will be turn off */ float GetMaxLODDistance() const { return MaxLODDistance; } protected: struct FMassLODRuntimeData { /** Reset values to default */ void Reset(const TStaticArray& InBaseLODDistance, const TStaticArray& InVisibleLODDistance) { // Reset the AdjustedLODDistances as they might have been changed by the max count calculation previous frame for (int32 LODDistIdx = 0; LODDistIdx < EMassLOD::Max; LODDistIdx++) { AdjustedBaseLODDistance[LODDistIdx] = InBaseLODDistance[LODDistIdx]; AdjustedBaseLODDistanceSq[LODDistIdx] = FMath::Square(AdjustedBaseLODDistance[LODDistIdx]); if (FLODLogic::bDoVisibilityLogic) { AdjustedVisibleLODDistance[LODDistIdx] = InVisibleLODDistance[LODDistIdx]; AdjustedVisibleLODDistanceSq[LODDistIdx] = FMath::Square(AdjustedVisibleLODDistance[LODDistIdx]); } } FMemory::Memzero(BaseBucketCounts); if (FLODLogic::bDoVisibilityLogic) { FMemory::Memzero(VisibleBucketCounts); } } /** Distance where each LOD becomes relevant (Squared and Normal) */ TStaticArray AdjustedBaseLODDistanceSq; TStaticArray AdjustedVisibleLODDistanceSq; TStaticArray AdjustedBaseLODDistance; TStaticArray AdjustedVisibleLODDistance; /** Count of entities in each subdivision */ TStaticArray< TStaticArray, EMassLOD::Max > BaseBucketCounts; TStaticArray< TStaticArray, EMassLOD::Max > VisibleBucketCounts; #if WITH_MASSGAMEPLAY_DEBUG /* Last calculation count per LOD */ TStaticArray LastCalculatedLODCount; #endif // WITH_MASSGAMEPLAY_DEBUG }; template float AccumulateCountInRuntimeData(const EMassLOD::Type LOD, const float ViewerDistanceSq, const bool bIsVisible, FMassLODRuntimeData& Data) const; template bool AdjustDistancesFromCountForRuntimeData(const TStaticArray& MaxCount, FMassLODRuntimeData& RuntimeData) const; template EMassLOD::Type ComputeLODFromSettings(const EMassLOD::Type PrevLOD, const float DistanceToViewerSq, const bool bIsVisible, bool* bIsInAVisibleRange, const FMassLODRuntimeData& Data) const; bool CalculateVisibility(const bool bWasVisible, const float DistanceToFrustum) const; /** LOD distances */ TStaticArray BaseLODDistance; TStaticArray VisibleLODDistance; /** MaxCount total */ TStaticArray LODMaxCount; /** MaxCount total per viewers*/ TStaticArray LODMaxCountPerViewer; /** Ratio for Buffer Distance Hysteresis */ float BufferHysteresisOnDistanceRatio = 0.1f; /** How far away from frustum does this entities are considered visible */ float VisibleDistanceToFrustum = 0.0f; /** Once visible how much further than distance to frustum does the entities need to be before being consider not visible */ float VisibleDistanceToFrustumWithHysteresis = 0.0f; /** The size of each subdivision per LOD (LOD Size/MaxBucketsPerLOD) */ TStaticArray BaseBucketSize; TStaticArray VisibleBucketSize; /** Maximum LOD Distance */ float MaxLODDistance = 0.0f; /** Runtime data for LOD calculation */ FMassLODRuntimeData RuntimeData; /** Runtime data for each viewer specific LOD calculation, used only when bMaximizeCountPerViewer is true */ TArray RuntimeDataPerViewer; }; template void TMassLODCalculator::Initialize(const float InBaseLODDistance[EMassLOD::Max], const float InBufferHysteresisOnDistanceRatio, const int32 InLODMaxCount[EMassLOD::Max], const int32 InLODMaxCountPerViewer[EMassLOD::Max] /*= nullptr*/, const float InVisibleDistanceToFrustum /*= 0.0f*/, const float InVisibleDistanceToFrustumHysteresis /*= 0.0f*/, const float InVisibleLODDistance[EMassLOD::Max] /*= nullptr*/ ) { static_assert(!FLODLogic::bCalculateLODPerViewer || FLODLogic::bStoreInfoPerViewer, "Need to enable store info per viewer to be able to calculate LOD per viewer"); static_assert(!FLODLogic::bMaximizeCountPerViewer || FLODLogic::bCalculateLODPerViewer, "Need to enable CalculatedLODPerviewer in order to maximize count per viewer"); checkf(FLODLogic::bMaximizeCountPerViewer == (InLODMaxCountPerViewer != nullptr), TEXT("Missmatched between expected parameter InLODMaxCountPerViewer and LOD logic trait bMaximizeCountPerViewer.")); checkf(FLODLogic::bDoVisibilityLogic == (InVisibleLODDistance != nullptr), TEXT("Missmatched between expected parameter InVisibleLODDistance and LOD logic trait bDoVisibilityLogic.")); // Make a copy of all the settings for (int x = 0; x < EMassLOD::Max; x++) { BaseLODDistance[x] = InBaseLODDistance[x]; // @todo Treat InLODMaxCount as a possible nullptr by default for this Initialize function, would need to come as an option from FLODLogic as well LODMaxCount[x] = (InLODMaxCount != nullptr) ? InLODMaxCount[x] : INT_MAX; if (FLODLogic::bDoVisibilityLogic && InVisibleLODDistance) { VisibleLODDistance[x] = InVisibleLODDistance[x]; } if (FLODLogic::bMaximizeCountPerViewer && InLODMaxCountPerViewer) { LODMaxCountPerViewer[x] = InLODMaxCountPerViewer[x]; } } // Some values should always be constant BaseLODDistance[EMassLOD::High] = 0.0f; BaseBucketSize[EMassLOD::Off] = FLT_MAX; VisibleLODDistance[EMassLOD::High] = 0.0f; VisibleBucketSize[EMassLOD::Off] = FLT_MAX; LODMaxCount[EMassLOD::Off] = INT_MAX; LODMaxCountPerViewer[EMassLOD::Off] = INT_MAX; BufferHysteresisOnDistanceRatio = InBufferHysteresisOnDistanceRatio; // Calculate the size for each LOD buckets float BasePrevLODDistance = BaseLODDistance[0]; float VisiblePrevLODDistance = VisibleLODDistance[0]; for (int32 LODDistIdx = 1; LODDistIdx < EMassLOD::Max; LODDistIdx++) { BaseBucketSize[LODDistIdx - 1] = (BaseLODDistance[LODDistIdx] - BasePrevLODDistance) / UE::MassLOD::MaxBucketsPerLOD; BasePrevLODDistance = BaseLODDistance[LODDistIdx]; if (FLODLogic::bDoVisibilityLogic) { VisibleBucketSize[LODDistIdx - 1] = (VisibleLODDistance[LODDistIdx] - VisiblePrevLODDistance) / UE::MassLOD::MaxBucketsPerLOD; VisiblePrevLODDistance = VisibleLODDistance[LODDistIdx]; } } // Assuming that off is the farthest distance, calculate the max LOD distance MaxLODDistance = !FLODLogic::bDoVisibilityLogic || BaseLODDistance[EMassLOD::Off] >= VisibleLODDistance[EMassLOD::Off] ? BaseLODDistance[EMassLOD::Off] : VisibleLODDistance[EMassLOD::Off]; // Distance to frustum settings VisibleDistanceToFrustum = InVisibleDistanceToFrustum; VisibleDistanceToFrustumWithHysteresis = InVisibleDistanceToFrustum + InVisibleDistanceToFrustumHysteresis; } template bool TMassLODCalculator::CalculateVisibility(const bool bWasVisible, const float DistanceToFrustum) const { return DistanceToFrustum < (bWasVisible ? VisibleDistanceToFrustumWithHysteresis : VisibleDistanceToFrustum); } template void TMassLODCalculator::PrepareExecution(TConstArrayView ViewersInfo) { CacheViewerInformation(ViewersInfo); if (FLODLogic::bMaximizeCountPerViewer) { RuntimeDataPerViewer.SetNum(Viewers.Num()); for (int ViewerIdx = 0; ViewerIdx < Viewers.Num(); ++ViewerIdx) { // Reset viewer data if (Viewers[ViewerIdx].Handle.IsValid()) { RuntimeDataPerViewer[ViewerIdx].Reset(BaseLODDistance, VisibleLODDistance); } } } RuntimeData.Reset(BaseLODDistance, VisibleLODDistance); } template template float TMassLODCalculator::AccumulateCountInRuntimeData(const EMassLOD::Type LOD, const float ViewerDistanceSq, const bool bIsVisible, FMassLODRuntimeData& Data) const { TStaticArray< TStaticArray, EMassLOD::Max>& BucketCounts = bCalculateVisibility && bIsVisible ? Data.VisibleBucketCounts : Data.BaseBucketCounts; // Cumulate LOD in buckets for Max LOD count calculation if (LOD == EMassLOD::Off) { // A single bucket for Off LOD BucketCounts[EMassLOD::Off][0]++; if (bCalculateLODSignificance) { return float(EMassLOD::Off); } } else { const TStaticArray& BucketSize = bCalculateVisibility && bIsVisible ? VisibleBucketSize : BaseBucketSize; const TStaticArray& AdjustedLODDistance = bCalculateVisibility && bIsVisible ? Data.AdjustedVisibleLODDistance : Data.AdjustedBaseLODDistance; const int32 LODDistIdx = (int32)LOD; // Need to clamp as the Sqrt is not precise enough and always end up with floating calculation errors const int32 BucketIdx = FMath::Clamp((int32)((FMath::Sqrt(ViewerDistanceSq) - AdjustedLODDistance[LODDistIdx]) / BucketSize[LODDistIdx]), 0, UE::MassLOD::MaxBucketsPerLOD - 1); BucketCounts[LODDistIdx][BucketIdx]++; if (bCalculateLODSignificance) { // Derive significance from LODDistIdx combined with BucketIdx const float PartialLODSignificance = float(BucketIdx) / float(UE::MassLOD::MaxBucketsPerLOD); return float(LODDistIdx) + PartialLODSignificance; } } return 0.0f; } template template void TMassLODCalculator::CalculateLOD(FMassExecutionContext& Context, TConstArrayView ViewersInfoList, TArrayView LODList, TConstArrayView PerViewerInfoList) { static_assert(!bCalculateVisibility || FLODLogic::bDoVisibilityLogic, "FLODLogic must have bDoVisibilityLogic enabled to calculate visibility."); static_assert(!bCalculateLODPerViewer || FLODLogic::bCalculateLODPerViewer, "FLODLogic must have bCalculateLODPerViewer enabled to calculate LOD Per viewer."); static_assert(!bCalculateVisibilityPerViewer || (FLODLogic::bDoVisibilityLogic && FLODLogic::bStoreInfoPerViewer), "FLODLogic must have bDoVisibilityLogic and bStoreInfoPerViewer enabled to calculate visibility per viewer."); static_assert(!bMaximizeCountPerViewer || FLODLogic::bMaximizeCountPerViewer, "FLODLogic must have bMaximizeCountPerViewer enabled to maximize count per viewer."); #if WITH_MASSGAMEPLAY_DEBUG if (UE::MassLOD::Debug::bLODCalculationsPaused) { return; } #endif // WITH_MASSGAMEPLAY_DEBUG for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt) { // Calculate the LOD purely upon distances const TViewerInfoFragment& EntityViewersInfo = ViewersInfoList[EntityIt]; TLODFragment& EntityLOD = LODList[EntityIt]; const float ClosestDistanceToFrustum = GetClosestDistanceToFrustum(EntityViewersInfo, FLT_MAX); const EMassVisibility PrevVisibility = GetVisibility(EntityLOD, EMassVisibility::Max); const bool bIsVisibleByAViewer = CalculateVisibility(PrevVisibility == EMassVisibility::CanBeSeen, ClosestDistanceToFrustum); bool bIsInAVisibleRange = false; // Find new LOD EntityLOD.PrevLOD = EntityLOD.LOD; EntityLOD.LOD = ComputeLODFromSettings(EntityLOD.PrevLOD, EntityViewersInfo.ClosestViewerDistanceSq, bIsVisibleByAViewer, &bIsInAVisibleRange, RuntimeData); // Set visibility SetPrevVisibility(EntityLOD, PrevVisibility); SetVisibility(EntityLOD, bIsInAVisibleRange ? (bIsVisibleByAViewer ? EMassVisibility::CanBeSeen : EMassVisibility::CulledByFrustum) : EMassVisibility::CulledByDistance); // Accumulate in buckets const float LODSignificance = AccumulateCountInRuntimeData(EntityLOD.LOD, EntityViewersInfo.ClosestViewerDistanceSq, bIsVisibleByAViewer, RuntimeData); SetLODSignificance(EntityLOD, LODSignificance); // Do per viewer logic if asked for if (bCalculateLODPerViewer || bCalculateVisibilityPerViewer) { const TPerViewerInfoFragment& EntityPerViewerInfo = PerViewerInfoList[EntityIt]; SetLODPerViewerNum(EntityLOD, Viewers.Num()); SetPrevLODPerViewerNum(EntityLOD, Viewers.Num()); SetVisibilityPerViewerNum(EntityLOD, Viewers.Num()); SetPrevVisibilityPerViewerNum(EntityLOD, Viewers.Num()); for (int ViewerIdx = 0; ViewerIdx < Viewers.Num(); ++ViewerIdx) { const FViewerLODInfo& Viewer = Viewers[ViewerIdx]; if (Viewer.bClearData) { SetLODPerViewer(EntityLOD, ViewerIdx, EMassLOD::Max); SetPrevLODPerViewer(EntityLOD, ViewerIdx, EMassLOD::Max); SetPrevVisibilityPerViewer(EntityLOD, ViewerIdx, EMassVisibility::Max); SetVisibilityPerViewer(EntityLOD, ViewerIdx, EMassVisibility::Max); } // Check to see if we want only local viewer only if (bCalculateLocalViewers && !Viewer.bLocal) { continue; } if (Viewer.Handle.IsValid()) { const float DistanceToFrustum = GetDistanceToFrustum(EntityPerViewerInfo, ViewerIdx, FLT_MAX); const EMassVisibility PrevVisibilityPerViewer = GetVisibilityPerViewer(EntityLOD, ViewerIdx, EMassVisibility::Max); const bool bIsVisibleByViewer = CalculateVisibility(PrevVisibilityPerViewer == EMassVisibility::CanBeSeen, DistanceToFrustum); bool bIsInVisibleRange = false; if (bCalculateLODPerViewer) { const float DistanceToViewerSq = GetDistanceToViewerSq(EntityPerViewerInfo, ViewerIdx, FLT_MAX); const EMassLOD::Type PrevLODPerViewer = GetLODPerViewer(EntityLOD, ViewerIdx, EntityLOD.LOD); // Find new LOD const EMassLOD::Type LODPerViewer = ComputeLODFromSettings(PrevLODPerViewer, DistanceToViewerSq, bIsInVisibleRange, &bIsInAVisibleRange, RuntimeData); // Set Per viewer LOD SetPrevLODPerViewer(EntityLOD, ViewerIdx, PrevLODPerViewer); SetLODPerViewer(EntityLOD, ViewerIdx, LODPerViewer); if (bMaximizeCountPerViewer) { // Accumulate in buckets AccumulateCountInRuntimeData(LODPerViewer, DistanceToViewerSq, bIsInVisibleRange, RuntimeDataPerViewer[ViewerIdx]); } } // Set visibility SetPrevVisibilityPerViewer(EntityLOD, ViewerIdx, bIsInAVisibleRange ? (bIsVisibleByAViewer ? EMassVisibility::CanBeSeen : EMassVisibility::CulledByFrustum) : EMassVisibility::CulledByDistance); SetVisibilityPerViewer(EntityLOD, ViewerIdx, PrevVisibilityPerViewer); } } } } } template template bool TMassLODCalculator::AdjustDistancesFromCountForRuntimeData(const TStaticArray& MaxCount, FMassLODRuntimeData& Data) const { int32 Count = 0; int32 ProcessingLODIdx = EMassLOD::High; bool bNeedAdjustments = false; // Go through all LOD can start counting from the high LOD for (int32 BucketLODIdx = 0; BucketLODIdx < EMassLOD::Max - 1; ++BucketLODIdx) { // Switch to next LOD if we have not reach the max if (ProcessingLODIdx < BucketLODIdx) { #if WITH_MASSGAMEPLAY_DEBUG // Save the count of this LOD for this frame Data.LastCalculatedLODCount[ProcessingLODIdx] = Count; #endif // WITH_MASSGAMEPLAY_DEBUG // Switch to next LOD ProcessingLODIdx = BucketLODIdx; // Restart the count Count = 0; } // Count entities through all buckets of this LOD for (int32 BucketIdx = 0; BucketIdx < UE::MassLOD::MaxBucketsPerLOD; ++BucketIdx) { if (bCalculateVisibility) { // Do visible count first to prioritize them over none visible for that bucket idx Count += Data.VisibleBucketCounts[BucketLODIdx][BucketIdx]; while (Count > MaxCount[ProcessingLODIdx]) { #if WITH_MASSGAMEPLAY_DEBUG // Save the count of this LOD for this frame Data.LastCalculatedLODCount[ProcessingLODIdx] = Count - Data.VisibleBucketCounts[BucketLODIdx][BucketIdx]; #endif // WITH_MASSGAMEPLAY_DEBUG // Switch to next LOD ProcessingLODIdx++; // Adjust distance for this LOD Data.AdjustedBaseLODDistance[ProcessingLODIdx] = BaseLODDistance[BucketLODIdx] + (static_cast(BucketIdx) * BaseBucketSize[BucketLODIdx]); Data.AdjustedBaseLODDistanceSq[ProcessingLODIdx] = FMath::Square(Data.AdjustedBaseLODDistance[ProcessingLODIdx]); Data.AdjustedVisibleLODDistance[ProcessingLODIdx] = VisibleLODDistance[BucketLODIdx] + (static_cast(BucketIdx) * VisibleBucketSize[BucketLODIdx]); Data.AdjustedVisibleLODDistanceSq[ProcessingLODIdx] = FMath::Square(Data.AdjustedVisibleLODDistance[ProcessingLODIdx]); // Check if we are done if (ProcessingLODIdx == EMassLOD::Off) { return true; } // Start the next LOD count with the bucket count that made it go over Count = Data.VisibleBucketCounts[BucketLODIdx][BucketIdx]; bNeedAdjustments = true; } } // Add base count Count += Data.BaseBucketCounts[BucketLODIdx][BucketIdx]; // Check if the count is going over max while (Count > MaxCount[ProcessingLODIdx]) { #if WITH_MASSGAMEPLAY_DEBUG // Save the count of this LOD for this frame Data.LastCalculatedLODCount[ProcessingLODIdx] = Count - Data.BaseBucketCounts[BucketLODIdx][BucketIdx]; #endif // WITH_MASSGAMEPLAY_DEBUG // Switch to next LOD ProcessingLODIdx++; // Adjust distance for this LOD Data.AdjustedBaseLODDistance[ProcessingLODIdx] = BaseLODDistance[BucketLODIdx] + (static_cast(BucketIdx) * BaseBucketSize[BucketLODIdx]); Data.AdjustedBaseLODDistanceSq[ProcessingLODIdx] = FMath::Square(Data.AdjustedBaseLODDistance[ProcessingLODIdx]); if (bCalculateVisibility) { Data.AdjustedVisibleLODDistance[ProcessingLODIdx] = VisibleLODDistance[BucketLODIdx] + (static_cast(BucketIdx + 1) * VisibleBucketSize[BucketLODIdx]); Data.AdjustedVisibleLODDistanceSq[ProcessingLODIdx] = FMath::Square(Data.AdjustedVisibleLODDistance[ProcessingLODIdx]); } // Check if we are done if (ProcessingLODIdx == EMassLOD::Off) { return true; } // Start the next LOD count with the bucket count that made it go over Count = Data.BaseBucketCounts[BucketLODIdx][BucketIdx]; bNeedAdjustments = true; } } } #if WITH_MASSGAMEPLAY_DEBUG if (ProcessingLODIdx < EMassLOD::Max - 1) { // Save the count of this LOD for this frame Data.LastCalculatedLODCount[ProcessingLODIdx] = Count; } #endif // WITH_MASSGAMEPLAY_DEBUG return bNeedAdjustments; } template template bool TMassLODCalculator::AdjustDistancesFromCount() { static_assert(!bCalculateVisibility || FLODLogic::bDoVisibilityLogic, "FLODLogic must have bDoVisibilityLogic enabled to calculate visibility."); static_assert(!bCalculateVisibilityPerViewer || (FLODLogic::bDoVisibilityLogic && FLODLogic::bStoreInfoPerViewer), "FLODLogic must have bDoVisibilityLogic and bStoreInfoPerViewer enabled to calculate visibility per viewer."); static_assert(!bMaximizeCountPerViewer || FLODLogic::bMaximizeCountPerViewer, "FLODLogic must have bMaximizeCountPerViewer enabled to maximize count per viewer."); bool bDistanceAdjusted = false; if (bMaximizeCountPerViewer) { for (int ViewerIdx = 0; ViewerIdx < Viewers.Num(); ++ViewerIdx) { if (!Viewers[ViewerIdx].Handle.IsValid()) { continue; } bDistanceAdjusted |= AdjustDistancesFromCountForRuntimeData(LODMaxCountPerViewer, RuntimeDataPerViewer[ViewerIdx]); } } bDistanceAdjusted |= AdjustDistancesFromCountForRuntimeData(LODMaxCount, RuntimeData); return bDistanceAdjusted; } template template void TMassLODCalculator::AdjustLODFromCount(FMassExecutionContext& Context, TConstArrayView ViewersInfoList, TArrayView LODList, TConstArrayView PerViewerInfoList) { static_assert(!bCalculateVisibility || FLODLogic::bDoVisibilityLogic, "FLODLogic must have bDoVisibilityLogic enabled to calculate visibility."); static_assert(!bCalculateLODPerViewer || FLODLogic::bCalculateLODPerViewer, "FLODLogic must have bCalculateLODPerViewer enabled to calculate LOD Per viewer."); static_assert(!bCalculateVisibilityPerViewer || (FLODLogic::bDoVisibilityLogic && FLODLogic::bStoreInfoPerViewer), "FLODLogic must have bDoVisibilityLogic and bStoreInfoPerViewer enabled to calculate visibility per viewer."); static_assert(!bMaximizeCountPerViewer || FLODLogic::bMaximizeCountPerViewer, "FLODLogic must have bMaximizeCountPerViewer enabled to maximize count per viewer."); // Adjust LOD for each viewer and remember the new highest for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt) { const TViewerInfoFragment& EntityViewersInfo = ViewersInfoList[EntityIt]; TLODFragment& EntityLOD = LODList[EntityIt]; EMassLOD::Type HighestViewerLOD = EMassLOD::Off; if (bMaximizeCountPerViewer) { const TPerViewerInfoFragment& EntityPerViewerInfo = PerViewerInfoList[EntityIt]; for (int ViewerIdx = 0; ViewerIdx < Viewers.Num(); ++ViewerIdx) { const FViewerLODInfo& Viewer = Viewers[ViewerIdx]; if (!Viewer.Handle.IsValid()) { continue; } // Check to see if we want only local viewer only if (bCalculateLocalViewers && !Viewer.bLocal) { continue; } const float DistanceToViewerSq = GetDistanceToViewerSq(EntityPerViewerInfo, ViewerIdx, FLT_MAX); // Using the prev visibility here as it was already updated for this frame in the CalculateLOD method and we want the previous one const bool bIsVisibleByViewer = GetPrevVisibilityPerViewer(EntityLOD, ViewerIdx, EMassVisibility::Max) == EMassVisibility::CanBeSeen; EMassLOD::Type LODPerViewer = GetLODPerViewer(EntityLOD, ViewerIdx, EntityLOD.LOD); LODPerViewer = ComputeLODFromSettings(LODPerViewer, DistanceToViewerSq, bIsVisibleByViewer, nullptr, RuntimeDataPerViewer[ViewerIdx]); if (LODPerViewer < HighestViewerLOD) { HighestViewerLOD = LODPerViewer; } SetLODPerViewer(EntityLOD, ViewerIdx, LODPerViewer); } } // Using the prev visibility here as it was already updated for this frame in the CalculateLOD method and we want the previous one const bool bIsVisibleByAViewer = GetPrevVisibility(EntityLOD, EMassVisibility::Max) == EMassVisibility::CanBeSeen; EMassLOD::Type NewLOD = ComputeLODFromSettings(EntityLOD.PrevLOD, EntityViewersInfo.ClosestViewerDistanceSq, bIsVisibleByAViewer, nullptr, RuntimeData); // Maybe the highest of all the viewers is now lower than the global entity LOD, make sure to update it accordingly if (bMaximizeCountPerViewer && NewLOD > HighestViewerLOD) { NewLOD = HighestViewerLOD; } if (EntityLOD.LOD != NewLOD) { EntityLOD.LOD = NewLOD; if (FLODLogic::bCalculateLODSignificance) { float LODSignificance = 0.f; if (NewLOD == EMassLOD::Off) { LODSignificance = float(EMassLOD::Off); } else { const TStaticArray& AdjustedLODDistance = bCalculateVisibility && bIsVisibleByAViewer ? RuntimeData.AdjustedVisibleLODDistance : RuntimeData.AdjustedBaseLODDistance; // Need to clamp as the Sqrt is not precise enough and always end up with floating calculation errors const float DistanceBetweenLODThresholdAndEntity = FMath::Max(FMath::Sqrt(EntityViewersInfo.ClosestViewerDistanceSq) - AdjustedLODDistance[NewLOD], 0.f); // Derive significance from distance between viewer and where the agent stands between both LOD threshold const float AdjustedDistanceBetweenCurrentLODAndNext = AdjustedLODDistance[NewLOD + 1] - AdjustedLODDistance[NewLOD]; const float PartialLODSignificance = DistanceBetweenLODThresholdAndEntity / AdjustedDistanceBetweenCurrentLODAndNext; LODSignificance = float(NewLOD) + PartialLODSignificance; } SetLODSignificance(EntityLOD, LODSignificance); } } } } template template void TMassLODCalculator::ForceOffLOD(FMassExecutionContext& Context, TArrayView LODList) { for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt) { TLODFragment& EntityLOD = LODList[EntityIt]; // Force off LOD EntityLOD.PrevLOD = EntityLOD.LOD; EntityLOD.LOD = EMassLOD::Off; // Set visibility as not calculated SetPrevVisibility(EntityLOD, EMassVisibility::Max); SetVisibility(EntityLOD, EMassVisibility::Max); if (FLODLogic::bStoreInfoPerViewer) { for (int ViewerIdx = 0; ViewerIdx < Viewers.Num(); ++ViewerIdx) { if (!Viewers[ViewerIdx].Handle.IsValid()) { continue; } SetLODPerViewer(EntityLOD, ViewerIdx, EMassLOD::Off); SetPrevLODPerViewer(EntityLOD, ViewerIdx, EMassLOD::Off); SetPrevVisibilityPerViewer(EntityLOD, ViewerIdx, EMassVisibility::Max); SetVisibilityPerViewer(EntityLOD, ViewerIdx, EMassVisibility::Max); } } SetLODSignificance(EntityLOD, float(EMassLOD::Off)); } } #if WITH_MASSGAMEPLAY_DEBUG template template void TMassLODCalculator::DebugDisplayLOD(FMassExecutionContext& Context, TConstArrayView LODList, TConstArrayView LocationList, UWorld* World) { for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt) { const TTransformFragment& EntityLocation = LocationList[EntityIt]; const TLODFragment& EntityLOD = LODList[EntityIt]; int32 LODIdx = (int32)EntityLOD.LOD; DrawDebugSolidBox(World, EntityLocation.GetTransform().GetLocation() + FVector(0.0f, 0.0f, 120.0f), FVector(25.0f), UE::MassLOD::LODColors[LODIdx]); } } template template void TMassLODCalculator::DebugDisplaySignificantLOD(FMassExecutionContext& Context, TConstArrayView LODList, TConstArrayView LocationList, UWorld* World, const float MaxLODSignificance) { for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt) { const TLODFragment& EntityLOD = LODList[EntityIt]; if (EntityLOD.LODSignificance <= MaxLODSignificance) { const TTransformFragment& EntityLocation = LocationList[EntityIt]; int32 LODIdx = (int32)EntityLOD.LOD; DrawDebugSolidBox(World, EntityLocation.GetTransform().GetLocation() + FVector(0.0f, 0.0f, 120.0f), FVector(25.0f), UE::MassLOD::LODColors[LODIdx]); } } } template template void TMassLODCalculator::VisLogLOD(FMassExecutionContext& Context, TConstArrayView LODList, TConstArrayView LocationList, UObject* LogOwner) { #if ENABLE_VISUAL_LOG for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt) { const TTransformFragment& EntityLocation = LocationList[EntityIt]; const TLODFragment& EntityLOD = LODList[EntityIt]; int32 LODIdx = (int32)EntityLOD.LOD; UE_VLOG_LOCATION(LogOwner, LogMassLOD, Verbose, EntityLocation.GetTransform().GetLocation(), 20.0f, UE::MassLOD::LODColors[LODIdx], TEXT("%s %d"), *Context.GetEntity(EntityIt).DebugGetDescription(), LODIdx); } #endif } template template void TMassLODCalculator::VisLogSignificantLOD(FMassExecutionContext& Context, TConstArrayView LODList, TConstArrayView LocationList, UObject* LogOwner, const float MaxLODSignificance) { #if ENABLE_VISUAL_LOG for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt) { const TLODFragment& EntityLOD = LODList[EntityIt]; if (EntityLOD.LODSignificance <= MaxLODSignificance) { const TTransformFragment& EntityLocation = LocationList[EntityIt]; int32 LODIdx = (int32)EntityLOD.LOD; UE_VLOG_LOCATION(LogOwner, LogMassLOD, Verbose, EntityLocation.GetTransform().GetLocation(), 20, UE::MassLOD::LODColors[LODIdx], TEXT("%s %d"), *Context.GetEntity(EntityIt).DebugGetDescription(), LODIdx); } } #endif } #endif // WITH_MASSGAMEPLAY_DEBUG template template EMassLOD::Type TMassLODCalculator::ComputeLODFromSettings(const EMassLOD::Type PrevLOD, const float DistanceToViewerSq, const bool bIsVisible, bool* bIsInAVisibleRange, const FMassLODRuntimeData& Data) const { const TStaticArray& AdjustedLODDistanceSq = bCalculateVisibility && bIsVisible ? Data.AdjustedVisibleLODDistanceSq : Data.AdjustedBaseLODDistanceSq; int32 LODDistIdx = EMassLOD::Max - 1; for (; LODDistIdx > 0; LODDistIdx--) { if (DistanceToViewerSq >= AdjustedLODDistanceSq[LODDistIdx]) { // Validate that we allow going to a single higher LOD level after considering an extended buffer hysteresis on distance for the LOD level we are about to quit to prevent oscillating LOD states if (PrevLOD != EMassLOD::Max && (PrevLOD - (EMassLOD::Type)LODDistIdx) == 1) { const TStaticArray& AdjustedLODDistance = bCalculateVisibility && bIsVisible ? Data.AdjustedVisibleLODDistance : Data.AdjustedBaseLODDistance; float HysteresisDistance = FMath::Lerp(AdjustedLODDistance[LODDistIdx], AdjustedLODDistance[LODDistIdx + 1], 1.f - BufferHysteresisOnDistanceRatio); if (DistanceToViewerSq >= FMath::Square(HysteresisDistance)) { // Keep old LODDistIdx = PrevLOD; } } break; } } EMassLOD::Type NewLOD = (EMassLOD::Type)LODDistIdx; if(bCalculateVisibility && bIsInAVisibleRange) { *bIsInAVisibleRange = bIsVisible ? (NewLOD != EMassLOD::Off) : (DistanceToViewerSq < Data.AdjustedVisibleLODDistanceSq[EMassLOD::Off]); } return NewLOD; }