7205 lines
249 KiB
C++
7205 lines
249 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
Landscape.cpp: Terrain rendering
|
|
=============================================================================*/
|
|
|
|
#include "Landscape.h"
|
|
|
|
#include "Serialization/MemoryWriter.h"
|
|
#include "Serialization/BufferArchive.h"
|
|
#include "Serialization/MemoryReader.h"
|
|
#include "UObject/RenderingObjectVersion.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
#include "UObject/PropertyPortFlags.h"
|
|
#include "UObject/ConstructorHelpers.h"
|
|
#include "UObject/DevObjectVersion.h"
|
|
#include "UObject/LinkerLoad.h"
|
|
#include "UObject/Package.h"
|
|
#include "Components/RuntimeVirtualTextureComponent.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "LandscapePrivate.h"
|
|
#include "LandscapeStreamingProxy.h"
|
|
#include "LandscapeInfo.h"
|
|
#include "LightMap.h"
|
|
#include "Engine/MapBuildDataRegistry.h"
|
|
#include "ShadowMap.h"
|
|
#include "LandscapeComponent.h"
|
|
#include "LandscapeLayerInfoObject.h"
|
|
#include "LandscapeInfoMap.h"
|
|
#include "EditorSupportDelegates.h"
|
|
#include "LandscapeMeshProxyComponent.h"
|
|
#include "LandscapeNaniteComponent.h"
|
|
#include "LandscapeRender.h"
|
|
#include "LandscapePrivate.h"
|
|
#include "Logging/StructuredLog.h"
|
|
#include "Logging/TokenizedMessage.h"
|
|
#include "Logging/MessageLog.h"
|
|
#include "Misc/UObjectToken.h"
|
|
#include "Misc/MapErrors.h"
|
|
#include "Misc/PackageSegment.h"
|
|
#include "DerivedDataCacheInterface.h"
|
|
#include "Interfaces/ITargetPlatform.h"
|
|
#include "LandscapeMeshCollisionComponent.h"
|
|
#include "MaterialDomain.h"
|
|
#include "Materials/Material.h"
|
|
#include "LandscapeMaterialInstanceConstant.h"
|
|
#include "Engine/CollisionProfile.h"
|
|
#include "LandscapeMeshProxyActor.h"
|
|
#include "Materials/MaterialExpressionLandscapeLayerWeight.h"
|
|
#include "Materials/MaterialExpressionLandscapeLayerSwitch.h"
|
|
#include "Materials/MaterialExpressionLandscapeLayerSample.h"
|
|
#include "Materials/MaterialExpressionLandscapeLayerBlend.h"
|
|
#include "Materials/MaterialExpressionLandscapeVisibilityMask.h"
|
|
#include "Materials/MaterialInstance.h"
|
|
#include "Materials/MaterialInstanceDynamic.h"
|
|
#include "ProfilingDebugging/CookStats.h"
|
|
#include "ILandscapeSplineInterface.h"
|
|
#include "LandscapeGrassType.h"
|
|
#include "LandscapeSplineActor.h"
|
|
#include "LandscapeSplinesComponent.h"
|
|
#include "EngineGlobals.h"
|
|
#include "Engine/Engine.h"
|
|
#include "Engine/TextureRenderTarget2D.h"
|
|
#include "Engine/TextureRenderTarget2DArray.h"
|
|
#include "EngineUtils.h"
|
|
#include "ComponentRecreateRenderStateContext.h"
|
|
#include "LandscapeWeightmapUsage.h"
|
|
#include "LandscapeSubsystem.h"
|
|
#include "LandscapeGroup.h"
|
|
#include "LandscapeGrassMapsBuilder.h"
|
|
#include "LandscapeCulling.h"
|
|
#include "ContentStreaming.h"
|
|
#include "UObject/ObjectSaveContext.h"
|
|
#include "GlobalShader.h"
|
|
#include "ShaderParameterStruct.h"
|
|
#include "PixelShaderUtils.h"
|
|
#include "LandscapeEditResources.h"
|
|
#include "Rendering/Texture2DResource.h"
|
|
#include "RenderCaptureInterface.h"
|
|
#include "VisualLogger/VisualLogger.h"
|
|
#include "Components/HierarchicalInstancedStaticMeshComponent.h"
|
|
#include "NaniteSceneProxy.h"
|
|
#include "Misc/ArchiveMD5.h"
|
|
#include "LandscapeEditLayer.h"
|
|
#include "LandscapeTextureStorageProvider.h"
|
|
#include "LandscapeUtils.h"
|
|
#include "LandscapeUtilsPrivate.h"
|
|
#include "LandscapeVersion.h"
|
|
#include "UObject/FortniteMainBranchObjectVersion.h"
|
|
#include "UObject/FortniteReleaseBranchCustomObjectVersion.h"
|
|
#include "UObject/EditorObjectVersion.h"
|
|
#include "UObject/UObjectThreadContext.h"
|
|
#include "LandscapeDataAccess.h"
|
|
#include "LandscapeNotification.h"
|
|
#include "LandscapeHeightfieldCollisionComponent.h"
|
|
#include "SystemTextures.h"
|
|
#include "Algo/BinarySearch.h"
|
|
#include "Algo/Count.h"
|
|
#include "Algo/Transform.h"
|
|
#include "Algo/AllOf.h"
|
|
#include "WorldPartition/WorldPartition.h"
|
|
#include "WorldPartition/WorldPartitionHelpers.h"
|
|
#include "WorldPartition/WorldPartitionHandle.h"
|
|
#include "WorldPartition/Landscape/LandscapeActorDesc.h"
|
|
#include "WorldPartition/WorldPartitionActorDescInstance.h"
|
|
#include "Engine/Texture2DArray.h"
|
|
|
|
#if WITH_EDITOR
|
|
#include "Rendering/StaticLightingSystemInterface.h"
|
|
#include "Misc/ScopedSlowTask.h"
|
|
#include "UnrealEdGlobals.h"
|
|
#include "Editor/UnrealEdEngine.h"
|
|
#include "LandscapeEdit.h"
|
|
#include "LandscapeEditTypes.h"
|
|
#include "MaterialUtilities.h"
|
|
#include "Editor.h"
|
|
#include "Editor/EditorEngine.h"
|
|
#include "Engine/Selection.h"
|
|
#include "Engine/Texture2D.h"
|
|
#include "AssetCompilingManager.h"
|
|
#include "FileHelpers.h"
|
|
#endif
|
|
|
|
/** Landscape stats */
|
|
|
|
DEFINE_STAT(STAT_LandscapeDynamicDrawTime);
|
|
DEFINE_STAT(STAT_LandscapeVFDrawTimeVS);
|
|
DEFINE_STAT(STAT_LandscapeVFDrawTimePS);
|
|
DEFINE_STAT(STAT_LandscapeComponentRenderPasses);
|
|
DEFINE_STAT(STAT_LandscapeDrawCalls);
|
|
DEFINE_STAT(STAT_LandscapeTriangles);
|
|
DEFINE_STAT(STAT_LandscapeLayersRegenerateDrawCalls);
|
|
|
|
#if ENABLE_COOK_STATS
|
|
namespace LandscapeCookStats
|
|
{
|
|
static FCookStats::FDDCResourceUsageStats UsageStats;
|
|
static FCookStatsManager::FAutoRegisterCallback RegisterCookStats([](FCookStatsManager::AddStatFuncRef AddStat)
|
|
{
|
|
UsageStats.LogStats(AddStat, TEXT("Landscape.Usage"), TEXT(""));
|
|
});
|
|
}
|
|
#endif
|
|
|
|
#define LOCTEXT_NAMESPACE "Landscape"
|
|
|
|
static void PrintNumLandscapeShadows()
|
|
{
|
|
int32 NumComponents = 0;
|
|
int32 NumShadowCasters = 0;
|
|
for (TObjectIterator<ULandscapeComponent> It(/*AdditionalExclusionFlags = */RF_ClassDefaultObject, /*bIncludeDerivedClasses = */true, /*InInternalExclusionFlags = */EInternalObjectFlags::Garbage); It; ++It)
|
|
{
|
|
ULandscapeComponent* LC = *It;
|
|
NumComponents++;
|
|
if (LC->CastShadow && LC->bCastDynamicShadow)
|
|
{
|
|
NumShadowCasters++;
|
|
}
|
|
}
|
|
UE_LOG(LogLandscape, Log, TEXT("%d/%d landscape components cast shadows"), NumShadowCasters, NumComponents);
|
|
}
|
|
|
|
FAutoConsoleCommand CmdPrintNumLandscapeShadows(
|
|
TEXT("landscape.PrintNumLandscapeShadows"),
|
|
TEXT("Prints the number of landscape components that cast shadows."),
|
|
FConsoleCommandDelegate::CreateStatic(PrintNumLandscapeShadows)
|
|
);
|
|
|
|
namespace UE::Landscape
|
|
{
|
|
int32 RenderCaptureNextMergeRenders = 0;
|
|
static FAutoConsoleVariableRef CVarRenderCaptureNextMergeRenders(
|
|
TEXT("landscape.RenderCaptureNextMergeRenders"),
|
|
RenderCaptureNextMergeRenders,
|
|
TEXT("Trigger a render capture during the next N RenderHeightmap/RenderWeightmap(s) draws"));
|
|
} // namespace UE::Landscape
|
|
|
|
#if WITH_EDITOR
|
|
|
|
namespace UE::Landscape
|
|
{
|
|
int32 NaniteExportCacheMaxQuadCount = 2048 * 2048;
|
|
static FAutoConsoleVariableRef CVarNaniteExportCacheMaxQuadCount(
|
|
TEXT("landscape.NaniteExportCacheMaxQuadCount"),
|
|
NaniteExportCacheMaxQuadCount,
|
|
TEXT("The maximum number of quads in a landscape proxy that will use the DDC cache when exporting the nanite mesh (any larger landscapes will be uncached). Set to a negative number to always cache."));
|
|
}
|
|
|
|
float LandscapeNaniteAsyncDebugWait = 0.0f;
|
|
static FAutoConsoleVariableRef CVarNaniteAsyncDebugWait(
|
|
TEXT("landscape.Nanite.AsyncDebugWait"),
|
|
LandscapeNaniteAsyncDebugWait,
|
|
TEXT("Time in seconds to pause the async Nanite build. Used for debugging"));
|
|
|
|
float LandscapeNaniteBuildLag = 0.25f;
|
|
static FAutoConsoleVariableRef CVarNaniteUpdateLag(
|
|
TEXT("landscape.Nanite.UpdateLag"),
|
|
LandscapeNaniteBuildLag,
|
|
TEXT("Time to wait in seconds after the last landscape update before triggering a nanite rebuild"));
|
|
|
|
float LandscapeNaniteStallDetectionTimeout = 3.0f * 60.0f; // 3 minutes
|
|
static FAutoConsoleVariableRef CVarNaniteStallDetectionTimeout(
|
|
TEXT("landscape.Nanite.StallDetectionTimeout"),
|
|
LandscapeNaniteStallDetectionTimeout,
|
|
TEXT("Time, in seconds, after which we consider a landscape nanite async build to have stalled or deadlocked."));
|
|
|
|
static FAutoConsoleVariable CVarForceInvalidateNaniteOnLoad(
|
|
TEXT("landscape.ForceInvalidateNaniteOnLoad"),
|
|
false,
|
|
TEXT("Trigger a rebuild of Nanite representation on load (for debugging purposes)"));
|
|
|
|
static FAutoConsoleVariable CVarSilenceSharedPropertyDeprecationFixup(
|
|
TEXT("landscape.SilenceSharedPropertyDeprecationFixup"),
|
|
true,
|
|
TEXT("Silently performs the fixup of discrepancies in shared properties when handling data modified before the enforcement introduction."));
|
|
|
|
static FAutoConsoleVariable CVarLandscapeSilenceMapCheckWarnings_Nanite(
|
|
TEXT("landscape.Nanite.SilenceMapCheckWarnings"),
|
|
false,
|
|
TEXT("Issue MapCheck Info messages instead of warnings if Nanite Data is out of date"));
|
|
static FAutoConsoleVariableDeprecated CVarLandscapeSuppressMapCheckWarnings_Nanite_Deprecated(TEXT("landscape.SupressMapCheckWarnings.Nanite"), TEXT("landscape.Nanite.SilenceMapCheckWarnings"), TEXT("5.6"));
|
|
|
|
FAutoConsoleVariable CVarStripLayerTextureMipsOnLoad(
|
|
TEXT("landscape.StripLayerMipsOnLoad"),
|
|
false,
|
|
TEXT("Remove (on load) the mip chain from textures used in layers which don't require them"));
|
|
|
|
static FAutoConsoleVariable CVarAllowGrassStripping(
|
|
TEXT("landscape.AllowGrassStripping"),
|
|
true,
|
|
TEXT("Enables the conditional stripping of grass data during cook. Disabling this means the bStripGrassWhenCooked* will be ignored."));
|
|
|
|
int32 GLandscapeHeightmapCompressionMode = 1;
|
|
static FAutoConsoleVariableRef CVarLandscapeHeightmapCompressionMode(
|
|
TEXT("landscape.HeightmapCompressionMode"),
|
|
GLandscapeHeightmapCompressionMode,
|
|
TEXT("Defines whether compression is applied to landscapes. Can be defined per platform.\n")
|
|
TEXT(" 0: force disable heightmap compression on all landscapes\n")
|
|
TEXT(" 1: force enable heightmap compression on all landscapes (default)\n"),
|
|
ECVF_Preview | ECVF_ReadOnly);
|
|
|
|
int32 GLandscapeHeightmapCompressionMipThreshold = 32;
|
|
static FAutoConsoleVariableRef CVarLandscapeHeightmapCompressionMipThreshold(
|
|
TEXT("landscape.HeightmapCompressionMipThreshold"),
|
|
GLandscapeHeightmapCompressionMipThreshold,
|
|
TEXT("Sets the minimum size for which heightmap mips are stored in a compressed layout. Can be defined per platform.\n")
|
|
TEXT("Below this size, mips are stored in an uncompressed layout.\n")
|
|
TEXT("Default threshold is 32, though some slower platforms may have a higher default threshold out of the box.\n"),
|
|
ECVF_Preview | ECVF_ReadOnly);
|
|
|
|
bool GLandscapePrioritizeDirtyRVTPages = true;
|
|
static FAutoConsoleVariableRef CVarPrioritizeDirtyRVTPages(
|
|
TEXT("landscape.PrioritizeDirtyRVTPages"),
|
|
GLandscapePrioritizeDirtyRVTPages,
|
|
TEXT("Prioritize RVT pages affected by the landscape tools, so that they get updated prior to others. Improves reactiveness when invalidating large areas of the RVT.")
|
|
);
|
|
#endif // WITH_EDITOR
|
|
|
|
TAutoConsoleVariable<int32> CVarRenderNaniteLandscape(
|
|
TEXT("landscape.RenderNanite"), 1,
|
|
TEXT("Render Landscape using Nanite."),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
extern int32 GGrassEnable;
|
|
extern int32 GGrassMapUseRuntimeGeneration;
|
|
extern FAutoConsoleVariableRef CVarGrassMapUseRuntimeGeneration;
|
|
|
|
struct FCompareULandscapeComponentClosest
|
|
{
|
|
FCompareULandscapeComponentClosest(const FIntPoint& InCenter) : Center(InCenter) {}
|
|
|
|
FORCEINLINE bool operator()(const ULandscapeComponent* A, const ULandscapeComponent* B) const
|
|
{
|
|
const FIntPoint ABase = A->GetSectionBase();
|
|
const FIntPoint BBase = B->GetSectionBase();
|
|
|
|
int32 DistA = (ABase - Center).SizeSquared();
|
|
int32 DistB = (BBase - Center).SizeSquared();
|
|
|
|
return DistA < DistB;
|
|
}
|
|
|
|
FIntPoint Center;
|
|
|
|
};
|
|
|
|
ULandscapeComponent::ULandscapeComponent(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
, bNaniteActive(false)
|
|
#if WITH_EDITORONLY_DATA
|
|
, LayerUpdateFlagPerMode(0)
|
|
, bPendingCollisionDataUpdate(false)
|
|
, bPendingLayerCollisionDataUpdate(false)
|
|
, WeightmapsHash(0)
|
|
, SplineHash(0)
|
|
, PhysicalMaterialHash(0)
|
|
#endif
|
|
, GrassData(MakeShared<FLandscapeComponentGrassData>())
|
|
, ChangeTag(0)
|
|
{
|
|
SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
|
|
SetGenerateOverlapEvents(false);
|
|
|
|
bUseAsOccluder = true;
|
|
bAllowCullDistanceVolume = false;
|
|
CollisionMipLevel = 0;
|
|
StaticLightingResolution = 0.f; // Default value 0 means no overriding
|
|
|
|
MaterialInstances.AddDefaulted(); // make sure we always have a MaterialInstances[0]
|
|
LODIndexToMaterialIndex.AddDefaulted(); // make sure we always have a MaterialInstances[0]
|
|
|
|
HeightmapScaleBias = FVector4(0.0f, 0.0f, 0.0f, 1.0f);
|
|
WeightmapScaleBias = FVector4(0.0f, 0.0f, 0.0f, 1.0f);
|
|
|
|
bBoundsChangeTriggersStreamingDataRebuild = true;
|
|
ForcedLOD = -1;
|
|
LODBias = 0;
|
|
#if WITH_EDITORONLY_DATA
|
|
LightingLODBias = -1; // -1 Means automatic LOD calculation based on ForcedLOD + LODBias
|
|
#endif
|
|
|
|
Mobility = EComponentMobility::Static;
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
EditToolRenderData = FLandscapeEditToolRenderData();
|
|
#endif
|
|
|
|
// We don't want to load this on the server, this component is for graphical purposes only
|
|
AlwaysLoadOnServer = false;
|
|
|
|
// Default sort priority of landscape to -1 so that it will default to the first thing rendered in any runtime virtual texture
|
|
TranslucencySortPriority = -1;
|
|
}
|
|
|
|
int32 ULandscapeComponent::GetMaterialInstanceCount(bool InDynamic) const
|
|
{
|
|
ALandscapeProxy* Actor = GetLandscapeProxy();
|
|
|
|
if (Actor != nullptr && Actor->bUseDynamicMaterialInstance && InDynamic)
|
|
{
|
|
return MaterialInstancesDynamic.Num();
|
|
}
|
|
|
|
return MaterialInstances.Num();
|
|
}
|
|
|
|
UMaterialInstance* ULandscapeComponent::GetMaterialInstance(int32 InIndex, bool InDynamic) const
|
|
{
|
|
ALandscapeProxy* Actor = GetLandscapeProxy();
|
|
|
|
if (Actor != nullptr && Actor->bUseDynamicMaterialInstance && InDynamic)
|
|
{
|
|
check(MaterialInstancesDynamic.IsValidIndex(InIndex));
|
|
return MaterialInstancesDynamic[InIndex];
|
|
}
|
|
|
|
check(MaterialInstances.IsValidIndex(InIndex));
|
|
return MaterialInstances[InIndex];
|
|
}
|
|
|
|
int32 ULandscapeComponent::GetCurrentRuntimeMaterialInstanceCount() const
|
|
{
|
|
ALandscapeProxy* Proxy = GetLandscapeProxy();
|
|
const ERHIFeatureLevel::Type FeatureLevel = Proxy->GetWorld()->GetFeatureLevel();
|
|
if (FeatureLevel == ERHIFeatureLevel::ES3_1)
|
|
{
|
|
return MobileMaterialInterfaces.Num();
|
|
}
|
|
|
|
bool bDynamic = Proxy->bUseDynamicMaterialInstance;
|
|
return GetMaterialInstanceCount(bDynamic);
|
|
}
|
|
|
|
class UMaterialInterface* ULandscapeComponent::GetCurrentRuntimeMaterialInterface(int32 InIndex)
|
|
{
|
|
ALandscapeProxy* Proxy = GetLandscapeProxy();
|
|
const ERHIFeatureLevel::Type FeatureLevel = GetLandscapeProxy()->GetWorld()->GetFeatureLevel();
|
|
|
|
if (FeatureLevel == ERHIFeatureLevel::ES3_1)
|
|
{
|
|
return MobileMaterialInterfaces[InIndex];
|
|
}
|
|
|
|
bool bDynamic = Proxy->bUseDynamicMaterialInstance;
|
|
return GetMaterialInstance(InIndex, bDynamic);
|
|
}
|
|
|
|
UMaterialInstanceDynamic* ULandscapeComponent::GetMaterialInstanceDynamic(int32 InIndex) const
|
|
{
|
|
ALandscapeProxy* Actor = GetLandscapeProxy();
|
|
|
|
if (Actor != nullptr && Actor->bUseDynamicMaterialInstance)
|
|
{
|
|
if (MaterialInstancesDynamic.IsValidIndex(InIndex))
|
|
{
|
|
return MaterialInstancesDynamic[InIndex];
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
|
|
void ULandscapeComponent::BeginCacheForCookedPlatformData(const ITargetPlatform* TargetPlatform)
|
|
{
|
|
Super::BeginCacheForCookedPlatformData(TargetPlatform);
|
|
|
|
if (!HasAnyFlags(RF_ClassDefaultObject))
|
|
{
|
|
if (TargetPlatform->SupportsFeature(ETargetPlatformFeatures::MobileRendering))
|
|
{
|
|
CheckGenerateMobilePlatformData(/*bIsCooking = */ true, TargetPlatform);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::CheckGenerateMobilePlatformData(bool bIsCooking, const ITargetPlatform* TargetPlatform)
|
|
{
|
|
for (ULandscapeComponent* Component : LandscapeComponents)
|
|
{
|
|
Component->CheckGenerateMobilePlatformData(bIsCooking, TargetPlatform);
|
|
}
|
|
}
|
|
|
|
bool ALandscapeProxy::IsNaniteMeshUpToDate() const
|
|
{
|
|
if (IsNaniteEnabled() && !HasAnyFlags(RF_ClassDefaultObject) && LandscapeComponents.Num() > 0)
|
|
{
|
|
const FGuid NaniteContentId = GetNaniteContentId();
|
|
return AreNaniteComponentsValid(NaniteContentId);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
FGraphEventRef ALandscapeProxy::UpdateNaniteRepresentationAsync(const ITargetPlatform* InTargetPlatform)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(ALandscapeProxy::UpdateNaniteRepresentationAsync);
|
|
FGraphEventRef BatchBuildEvent;
|
|
|
|
if (IsNaniteEnabled() && !HasAnyFlags(RF_ClassDefaultObject) && LandscapeComponents.Num() > 0)
|
|
{
|
|
const FGuid NaniteContentId = GetNaniteContentId();
|
|
|
|
int32 NumNewNaniteComponents = NumNaniteRequiredComponents();
|
|
if (NumNewNaniteComponents != NaniteComponents.Num())
|
|
{
|
|
RemoveNaniteComponents();
|
|
CreateNaniteComponents(NumNewNaniteComponents);
|
|
}
|
|
|
|
const FGuid ComponentNaniteContentId = GetNaniteComponentContentId();
|
|
const bool bNaniteContentDirty = ComponentNaniteContentId != NaniteContentId;
|
|
|
|
if (bNaniteContentDirty && IsRunningCookCommandlet())
|
|
{
|
|
UE_LOG(LogLandscape, Display, TEXT("Landscape Nanite out of date. Map requires resaving. Actor: '%s' Package: '%s'"), *GetActorNameOrLabel(), *GetPackage()->GetName());
|
|
}
|
|
|
|
FGraphEventArray UpdateDependencies;
|
|
for (int32 i = 0; i < NumNewNaniteComponents; ++i)
|
|
{
|
|
FGraphEventArray SingleProxyDependencies;
|
|
|
|
if (bNaniteContentDirty)
|
|
{
|
|
FGraphEventRef ComponentProcessTask = NaniteComponents[i]->InitializeForLandscapeAsync(this, NaniteContentId, GatherSourceComponentsForNaniteComponent(i), i);
|
|
SingleProxyDependencies.Add(ComponentProcessTask);
|
|
}
|
|
|
|
// TODO: Add a flag that only initializes the platform if we called InitializeForLandscape during the PreSave for this or a previous platform
|
|
TWeakObjectPtr<ULandscapeNaniteComponent> WeakComponent = NaniteComponents[i];
|
|
TWeakObjectPtr<ALandscapeProxy> WeakProxy = this;
|
|
FGraphEventRef FinalizeEvent = FFunctionGraphTask::CreateAndDispatchWhenReady([WeakComponent, WeakProxy, Name = GetActorNameOrLabel(), InTargetPlatform]() {
|
|
if (!WeakComponent.IsValid() || !WeakProxy.IsValid())
|
|
{
|
|
UE_LOG(LogLandscape, Log, TEXT("UpdateNaniteRepresentationAsync Component on: '%s' Is Invalid"), *Name);
|
|
return;
|
|
}
|
|
WeakComponent->InitializePlatformForLandscape(WeakProxy.Get(), InTargetPlatform);
|
|
WeakComponent->UpdatedSharedPropertiesFromActor();
|
|
},
|
|
TStatId(),
|
|
&SingleProxyDependencies,
|
|
ENamedThreads::GameThread);
|
|
|
|
UpdateDependencies.Add(FinalizeEvent);
|
|
}
|
|
|
|
BatchBuildEvent = FFunctionGraphTask::CreateAndDispatchWhenReady([] {}, TStatId(), &UpdateDependencies, ENamedThreads::GameThread);
|
|
|
|
// Register the finalize build event so that it can be tracked globally by :
|
|
ULandscapeSubsystem* LandscapeSubsystem = GetWorld()->GetSubsystem<ULandscapeSubsystem>();
|
|
LandscapeSubsystem->AddNaniteFinalizeBuildEvent(BatchBuildEvent);
|
|
}
|
|
else
|
|
{
|
|
InvalidateNaniteRepresentation(/* bInCheckContentId = */false);
|
|
}
|
|
|
|
return BatchBuildEvent;
|
|
}
|
|
|
|
void ALandscapeProxy::UpdateNaniteRepresentation(const ITargetPlatform* InTargetPlatform)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(ALandscapeProxy::UpdateNaniteRepresentation);
|
|
check(IsInGameThread());
|
|
|
|
FGraphEventRef GraphEvent = UpdateNaniteRepresentationAsync(InTargetPlatform);
|
|
ULandscapeSubsystem* LandscapeSubsystem = GetWorld()->GetSubsystem<ULandscapeSubsystem>();
|
|
|
|
if (!GraphEvent.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!LandscapeSubsystem->IsMultithreadedNaniteBuildEnabled() || IsRunningCookCommandlet())
|
|
{
|
|
const bool bAllNaniteBuildsDone = LandscapeSubsystem->FinishAllNaniteBuildsInFlightNow(ULandscapeSubsystem::EFinishAllNaniteBuildsInFlightFlags::Default);
|
|
// Not passing ULandscapeSubsystem::EFinishAllNaniteBuildsInFlightFlags::AllowCancel, so there should be no way that FinishAllNaniteBuildsInFlightNow returns false :
|
|
check(bAllNaniteBuildsDone);
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::InvalidateNaniteRepresentation(bool bInCheckContentId)
|
|
{
|
|
if (HasNaniteComponents())
|
|
{
|
|
if (!bInCheckContentId || GetNaniteComponentContentId() != GetNaniteContentId())
|
|
{
|
|
RemoveNaniteComponents();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::InvalidateOrUpdateNaniteRepresentation(bool bInCheckContentId, const ITargetPlatform* InTargetPlatform)
|
|
{
|
|
ULandscapeSubsystem* Subsystem = GetWorld()->GetSubsystem<ULandscapeSubsystem>();
|
|
if (Subsystem->IsLiveNaniteRebuildEnabled())
|
|
{
|
|
UpdateNaniteRepresentation(InTargetPlatform);
|
|
}
|
|
else
|
|
{
|
|
InvalidateNaniteRepresentation(bInCheckContentId);
|
|
}
|
|
}
|
|
|
|
FGuid ALandscapeProxy::GetNaniteContentId() const
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(ALandscapeProxy::GetNaniteContentId);
|
|
if (!IsNaniteEnabled())
|
|
{
|
|
return FGuid();
|
|
}
|
|
|
|
FBufferArchive ContentStateAr;
|
|
|
|
int32 LocalNaniteLODIndex = GetNaniteLODIndex();
|
|
ContentStateAr << LocalNaniteLODIndex;
|
|
|
|
struct FCompareULandscapeComponentBySectionBase
|
|
{
|
|
FORCEINLINE bool operator()(const ULandscapeComponent* A, const ULandscapeComponent* B) const
|
|
{
|
|
if (!A)
|
|
{
|
|
return true;
|
|
}
|
|
if (!B)
|
|
{
|
|
return false;
|
|
}
|
|
// Sort components based on their SectionBase (i.e. 2D index relative to the entire landscape) to ensure stable ID generation
|
|
return (A->GetSectionBase().X == B->GetSectionBase().X) ? (A->GetSectionBase().Y < B->GetSectionBase().Y) : (A->GetSectionBase().X < B->GetSectionBase().X);
|
|
}
|
|
};
|
|
TArray<ULandscapeComponent*> StableOrderComponents(LandscapeComponents);
|
|
Algo::Sort(StableOrderComponents, FCompareULandscapeComponentBySectionBase());
|
|
|
|
for (ULandscapeComponent* Component : StableOrderComponents)
|
|
{
|
|
if (Component == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Bump if changes to ULandscapeNaniteComponent::InitializeForLandscape() need to be enforced.
|
|
static FGuid ExportRawMeshGuid("36208D9A475B4D93B33BF84FFEDA1536");
|
|
ContentStateAr << ExportRawMeshGuid;
|
|
|
|
FGuid HeightmapGuid = ULandscapeTextureHash::GetHash(Component->GetHeightmap());
|
|
ContentStateAr << HeightmapGuid;
|
|
|
|
// Take into account the Heightmap offset per component
|
|
ContentStateAr << Component->HeightmapScaleBias.Z;
|
|
ContentStateAr << Component->HeightmapScaleBias.W;
|
|
|
|
// Visibility affects the generated Nanite mesh so it has to be taken into account :
|
|
// Note : visibility might be different at runtime if using a masked material (per-pixel visibility) but we obviously cannot take that into account
|
|
// when baking the visibility into the mesh like we do with Nanite landscape
|
|
if (Component->ComponentHasVisibilityPainted())
|
|
{
|
|
const TArray<UTexture2D*>& WeightmapTextures = Component->GetWeightmapTextures();
|
|
const TArray<FWeightmapLayerAllocationInfo>& AllocInfos = Component->GetWeightmapLayerAllocations();
|
|
for (const FWeightmapLayerAllocationInfo& AllocInfo : AllocInfos)
|
|
{
|
|
if (AllocInfo.IsAllocated() && AllocInfo.LayerInfo == ALandscapeProxy::VisibilityLayer)
|
|
{
|
|
UTexture2D* VisibilityWeightmap = WeightmapTextures[AllocInfo.WeightmapTextureIndex];
|
|
check(VisibilityWeightmap != nullptr);
|
|
|
|
// TODO [jonathan.bard] : technically, this is not good, we would need to only check the hash of AllocInfo.WeightmapTextureChannel. We'll leave it as is, though, for
|
|
// as long as we don't store the source weightmaps individually, so that this function stays fast :
|
|
FGuid VisibilityWeightmapGuid = VisibilityWeightmap->Source.GetId();
|
|
ContentStateAr << VisibilityWeightmapGuid;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// landscape nanite settings which might affect the resultant Nanite Static Mesh.
|
|
int32 NaniteSkirtEnabled = bNaniteSkirtEnabled;
|
|
float NaniteSkirtDepthTest = bNaniteSkirtEnabled ? NaniteSkirtDepth : 0.0f; // The hash should only change if Skirts are enabled.
|
|
ContentStateAr << NaniteSkirtEnabled;
|
|
ContentStateAr << NaniteSkirtDepthTest;
|
|
int32 NanitePositionPrecisionCopy = NanitePositionPrecision;
|
|
ContentStateAr << NanitePositionPrecisionCopy;
|
|
float NaniteMaxEdgeLengthFactorCopy = NaniteMaxEdgeLengthFactor;
|
|
ContentStateAr << NaniteMaxEdgeLengthFactorCopy;
|
|
|
|
uint32 Hash[5];
|
|
FSHA1::HashBuffer(ContentStateAr.GetData(), ContentStateAr.Num(), (uint8*)Hash);
|
|
return FGuid(Hash[0] ^ Hash[4], Hash[1], Hash[2], Hash[3]);
|
|
}
|
|
|
|
void ULandscapeComponent::CheckGenerateMobilePlatformData(bool bIsCooking, const ITargetPlatform* TargetPlatform)
|
|
{
|
|
// Regenerate platform data only when it's missing or there is a valid hash-mismatch.
|
|
FBufferArchive ComponentStateAr;
|
|
SerializeStateHashes(ComponentStateAr);
|
|
|
|
// Serialize the version guid as part of the hash so we can invalidate DDC data if needed
|
|
FString MobileVersion = FDevSystemGuids::GetSystemGuid(FDevSystemGuids::Get().LANDSCAPE_MOBILE_COOK_VERSION).ToString();
|
|
ComponentStateAr << MobileVersion;
|
|
|
|
bool IsTextureArrayEnabled = UE::Landscape::Private::IsMobileWeightmapTextureArrayEnabled();
|
|
ComponentStateAr << IsTextureArrayEnabled;
|
|
|
|
uint32 Hash[5];
|
|
FSHA1::HashBuffer(ComponentStateAr.GetData(), ComponentStateAr.Num(), (uint8*)Hash);
|
|
FGuid NewSourceHash = FGuid(Hash[0] ^ Hash[4], Hash[1], Hash[2], Hash[3]);
|
|
|
|
bool bHashMismatch = MobileDataSourceHash != NewSourceHash;
|
|
bool bMissingPixelData = MobileMaterialInterfaces.Num() == 0 || MaterialPerLOD.Num() == 0;
|
|
bool bRegeneratePixelData = bMissingPixelData || bHashMismatch;
|
|
|
|
if (bRegeneratePixelData)
|
|
{
|
|
GenerateMobilePlatformPixelData(bIsCooking, TargetPlatform);
|
|
}
|
|
|
|
MobileDataSourceHash = NewSourceHash;
|
|
}
|
|
|
|
#endif // WITH_EDITOR
|
|
|
|
void ULandscapeComponent::SetForcedLOD(int32 InForcedLOD)
|
|
{
|
|
SetLOD(/*bForced = */true, InForcedLOD);
|
|
}
|
|
|
|
void ULandscapeComponent::SetLODBias(int32 InLODBias)
|
|
{
|
|
SetLOD(/*bForced = */false, InLODBias);
|
|
}
|
|
|
|
void ULandscapeComponent::SetLOD(bool bForcedLODChanged, int32 InLODValue)
|
|
{
|
|
if (bForcedLODChanged)
|
|
{
|
|
ForcedLOD = InLODValue;
|
|
if (ForcedLOD >= 0)
|
|
{
|
|
ForcedLOD = FMath::Clamp<int32>(ForcedLOD, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
|
|
}
|
|
else
|
|
{
|
|
ForcedLOD = -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int32 MaxLOD = FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1;
|
|
LODBias = FMath::Clamp<int32>(InLODValue, -MaxLOD, MaxLOD);
|
|
}
|
|
|
|
InvalidateLightingCache();
|
|
MarkRenderStateDirty();
|
|
|
|
#if WITH_EDITOR
|
|
// Update neighbor components for lighting cache (only relevant in the editor ATM) :
|
|
ULandscapeInfo* Info = GetLandscapeInfo();
|
|
if (Info)
|
|
{
|
|
FIntPoint ComponentBase = GetSectionBase() / ComponentSizeQuads;
|
|
FIntPoint LandscapeKey[8] =
|
|
{
|
|
ComponentBase + FIntPoint(-1, -1),
|
|
ComponentBase + FIntPoint(+0, -1),
|
|
ComponentBase + FIntPoint(+1, -1),
|
|
ComponentBase + FIntPoint(-1, +0),
|
|
ComponentBase + FIntPoint(+1, +0),
|
|
ComponentBase + FIntPoint(-1, +1),
|
|
ComponentBase + FIntPoint(+0, +1),
|
|
ComponentBase + FIntPoint(+1, +1)
|
|
};
|
|
|
|
for (int32 Idx = 0; Idx < 8; ++Idx)
|
|
{
|
|
ULandscapeComponent* Comp = Info->XYtoComponentMap.FindRef(LandscapeKey[Idx]);
|
|
if (Comp)
|
|
{
|
|
Comp->Modify();
|
|
Comp->InvalidateLightingCache();
|
|
Comp->MarkRenderStateDirty();
|
|
}
|
|
}
|
|
}
|
|
#endif // WITH_EDITOR
|
|
}
|
|
|
|
void ULandscapeComponent::SetNaniteActive(bool bValue)
|
|
{
|
|
if (bNaniteActive != bValue)
|
|
{
|
|
bNaniteActive = bValue;
|
|
MarkRenderStateDirty();
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::Serialize(FArchive& Ar)
|
|
{
|
|
LLM_SCOPE(ELLMTag::Landscape);
|
|
Ar.UsingCustomVersion(FRenderingObjectVersion::GUID);
|
|
Ar.UsingCustomVersion(FFortniteMainBranchObjectVersion::GUID);
|
|
Ar.UsingCustomVersion(FEditorObjectVersion::GUID);
|
|
|
|
bool bStripGrassData = false;
|
|
#if WITH_EDITOR
|
|
if (Ar.IsCooking() && !HasAnyFlags(RF_ClassDefaultObject))
|
|
{
|
|
const ITargetPlatform* TargetPlatform = Ar.CookingTarget();
|
|
|
|
// for -oldcook:
|
|
// the old cooker calls BeginCacheForCookedPlatformData after the package export set is tagged, so the mobile material doesn't get saved, so we have to do CheckGenerateMobilePlatformData in serialize
|
|
// the new cooker clears the texture source data before calling serialize, causing GeneratePlatformVertexData to crash, so we have to do CheckGenerateMobilePlatformData in BeginCacheForCookedPlatformData
|
|
if (TargetPlatform->SupportsFeature(ETargetPlatformFeatures::MobileRendering))
|
|
{
|
|
CheckGenerateMobilePlatformData(/*bIsCooking = */ true, TargetPlatform);
|
|
}
|
|
|
|
// determine whether our target platform is going to need serialized grass data
|
|
TSharedPtr<IConsoleVariable> TargetPlatformUseRuntimeGeneration =
|
|
CVarGrassMapUseRuntimeGeneration->GetPlatformValueVariable(*TargetPlatform->IniPlatformName());
|
|
check(TargetPlatformUseRuntimeGeneration.IsValid());
|
|
bStripGrassData = TargetPlatformUseRuntimeGeneration->GetBool();
|
|
|
|
if (ALandscapeProxy* Proxy = GetLandscapeProxy())
|
|
{
|
|
// Also strip grass data according to Proxy flags (when not cooking for editor)
|
|
if (!TargetPlatform->AllowsEditorObjects())
|
|
{
|
|
if (CVarAllowGrassStripping->GetBool() &&
|
|
((Proxy->bStripGrassWhenCookedClient && Proxy->bStripGrassWhenCookedServer) ||
|
|
(Proxy->bStripGrassWhenCookedClient && TargetPlatform->IsClientOnly()) ||
|
|
(Proxy->bStripGrassWhenCookedServer && TargetPlatform->IsServerOnly())))
|
|
{
|
|
bStripGrassData = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
// double check we never save an invalid cached local box to a cooked package (should always be recalculated in ALandscapeProxy::PreSave)
|
|
if (Ar.IsSaving() && Ar.IsCooking() && !Ar.IsSerializingDefaults())
|
|
{
|
|
check(CachedLocalBox.GetVolume() > 0);
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
// Avoid the archiver in the PIE duplicate writer case because we want to share landscape textures & materials
|
|
if (Ar.GetPortFlags() & PPF_DuplicateForPIE)
|
|
{
|
|
if (Ar.IsLoading())
|
|
{
|
|
Super::Serialize(Ar);
|
|
}
|
|
|
|
TArray<UObject**> TexturesAndMaterials;
|
|
TexturesAndMaterials.Add((UObject**)&HeightmapTexture);
|
|
TexturesAndMaterials.Add((UObject**)&XYOffsetmapTexture);
|
|
for (TObjectPtr<UTexture2D>& WeightmapTexture : WeightmapTextures)
|
|
{
|
|
TexturesAndMaterials.Add((UObject**)&static_cast<UTexture2D*&>(WeightmapTexture));
|
|
}
|
|
for (TObjectPtr<UTexture2D>& MobileWeightmapTexture : MobileWeightmapTextures)
|
|
{
|
|
TexturesAndMaterials.Add((UObject**)&static_cast<UTexture2D*&>(MobileWeightmapTexture));
|
|
}
|
|
|
|
TexturesAndMaterials.Add((UObject**)&static_cast<UTexture2DArray*&>(MobileWeightmapTextureArray));
|
|
|
|
for (auto& ItPair : LayersData)
|
|
{
|
|
FLandscapeLayerComponentData& LayerComponentData = ItPair.Value;
|
|
TexturesAndMaterials.Add((UObject**)&LayerComponentData.HeightmapData.Texture);
|
|
for (TObjectPtr<UTexture2D>& WeightmapTexture : LayerComponentData.WeightmapData.Textures)
|
|
{
|
|
TexturesAndMaterials.Add((UObject**)&static_cast<UTexture2D*&>(WeightmapTexture));
|
|
}
|
|
}
|
|
for (TObjectPtr<UMaterialInstanceConstant>& MaterialInstance : MaterialInstances)
|
|
{
|
|
TexturesAndMaterials.Add((UObject**)&static_cast<UMaterialInstanceConstant*&>(MaterialInstance));
|
|
}
|
|
for (TObjectPtr<UMaterialInterface>& MobileMaterialInterface : MobileMaterialInterfaces)
|
|
{
|
|
TexturesAndMaterials.Add((UObject**)(&static_cast<UMaterialInterface*&>(MobileMaterialInterface)));
|
|
}
|
|
for (TObjectPtr<UMaterialInstanceConstant>& MobileCombinationMaterialInstance : MobileCombinationMaterialInstances)
|
|
{
|
|
TexturesAndMaterials.Add((UObject**)&static_cast<UMaterialInstanceConstant*&>(MobileCombinationMaterialInstance));
|
|
}
|
|
|
|
if (Ar.IsSaving())
|
|
{
|
|
TArray<UObject*> BackupTexturesAndMaterials;
|
|
BackupTexturesAndMaterials.AddZeroed(TexturesAndMaterials.Num());
|
|
for (int i = 0; i < TexturesAndMaterials.Num(); ++i)
|
|
{
|
|
Exchange(*TexturesAndMaterials[i], BackupTexturesAndMaterials[i]);
|
|
}
|
|
|
|
Super::Serialize(Ar);
|
|
|
|
for (int i = 0; i < TexturesAndMaterials.Num(); ++i)
|
|
{
|
|
Exchange(*TexturesAndMaterials[i], BackupTexturesAndMaterials[i]);
|
|
}
|
|
}
|
|
// Manually serialize pointers
|
|
for (UObject** Object : TexturesAndMaterials)
|
|
{
|
|
Ar.Serialize(Object, sizeof(UObject*));
|
|
}
|
|
}
|
|
else if (Ar.IsCooking() && !HasAnyFlags(RF_ClassDefaultObject))
|
|
{
|
|
if (!Ar.CookingTarget()->SupportsFeature(ETargetPlatformFeatures::DeferredRendering))
|
|
{
|
|
// These are used for SM5 rendering
|
|
UTexture2D* BackupXYOffsetmapTexture = nullptr;
|
|
TArray<TObjectPtr<UMaterialInstanceConstant>> BackupMaterialInstances;
|
|
TArray<TObjectPtr<UTexture2D>> BackupWeightmapTextures;
|
|
|
|
Exchange(BackupXYOffsetmapTexture, XYOffsetmapTexture);
|
|
Exchange(BackupMaterialInstances, MaterialInstances);
|
|
Exchange(BackupWeightmapTextures, WeightmapTextures);
|
|
|
|
Super::Serialize(Ar);
|
|
|
|
Exchange(BackupXYOffsetmapTexture, XYOffsetmapTexture);
|
|
Exchange(BackupMaterialInstances, MaterialInstances);
|
|
Exchange(BackupWeightmapTextures, WeightmapTextures);
|
|
}
|
|
else if (!Ar.CookingTarget()->SupportsFeature(ETargetPlatformFeatures::MobileRendering))
|
|
{
|
|
// These properties are only for Mobile
|
|
TArray<TObjectPtr<UMaterialInterface>> BackupMobileMaterialInterfaces;
|
|
TArray<TObjectPtr<UTexture2D>> BackupMobileWeightmapTextures;
|
|
|
|
Exchange(MobileMaterialInterfaces, BackupMobileMaterialInterfaces);
|
|
Exchange(MobileWeightmapTextures, BackupMobileWeightmapTextures);
|
|
MobileWeightmapTextureArray = TObjectPtr<UTexture2DArray>(nullptr);
|
|
Super::Serialize(Ar);
|
|
|
|
Exchange(MobileMaterialInterfaces, BackupMobileMaterialInterfaces);
|
|
Exchange(MobileWeightmapTextures, BackupMobileWeightmapTextures);
|
|
}
|
|
else
|
|
{
|
|
// Serialize both mobile and SM5 properties
|
|
Super::Serialize(Ar);
|
|
}
|
|
}
|
|
else
|
|
#endif // WITH_EDITOR
|
|
{
|
|
Super::Serialize(Ar);
|
|
}
|
|
|
|
// this is a sanity check, as ALandscapeProxy::PreSave() for cook should have ensured that the cached local box has non-zero volume
|
|
if (Ar.IsLoadingFromCookedPackage() && (CachedLocalBox.GetVolume() <= 0))
|
|
{
|
|
// we must set a conservative bounds as a last resort here -- if not we risk strobing flicker of landscape visibility
|
|
FVector MinBox(0, 0, LandscapeDataAccess::GetLocalHeight(0));
|
|
FVector MaxBox(ComponentSizeQuads + 1, ComponentSizeQuads + 1, LandscapeDataAccess::GetLocalHeight(UINT16_MAX));
|
|
CachedLocalBox = FBox(MinBox, MaxBox);
|
|
UE_LOG(LogLandscape, Error, TEXT("The component %s has an invalid CachedLocalBox. It has been set to a conservative bounds, that may result in reduced visibility culling performance"), *GetName());
|
|
}
|
|
|
|
if (Ar.IsLoading() && Ar.CustomVer(FRenderingObjectVersion::GUID) < FRenderingObjectVersion::MapBuildDataSeparatePackage)
|
|
{
|
|
FMeshMapBuildData* LegacyMapBuildData = new FMeshMapBuildData();
|
|
Ar << LegacyMapBuildData->LightMap;
|
|
Ar << LegacyMapBuildData->ShadowMap;
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
LegacyMapBuildData->IrrelevantLights = IrrelevantLights_DEPRECATED;
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
FMeshMapBuildLegacyData LegacyComponentData;
|
|
LegacyComponentData.Data.Emplace(MapBuildDataId, LegacyMapBuildData);
|
|
GComponentsWithLegacyLightmaps.AddAnnotation(this, MoveTemp(LegacyComponentData));
|
|
}
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
if (Ar.IsLoading() && Ar.CustomVer(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::NewLandscapeMaterialPerLOD)
|
|
{
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
if (MobileMaterialInterface_DEPRECATED != nullptr)
|
|
{
|
|
MobileMaterialInterfaces.AddUnique(MobileMaterialInterface_DEPRECATED);
|
|
}
|
|
|
|
if (MobileCombinationMaterialInstance_DEPRECATED != nullptr)
|
|
{
|
|
MobileCombinationMaterialInstances.AddUnique(MobileCombinationMaterialInstance_DEPRECATED);
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
}
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
if (Ar.UEVer() >= VER_UE4_SERIALIZE_LANDSCAPE_GRASS_DATA)
|
|
{
|
|
// Share the shared ref so PIE can share this data
|
|
if (Ar.GetPortFlags() & PPF_DuplicateForPIE)
|
|
{
|
|
if (Ar.IsSaving())
|
|
{
|
|
PTRINT GrassDataPointer = (PTRINT)&GrassData;
|
|
Ar << GrassDataPointer;
|
|
}
|
|
else
|
|
{
|
|
PTRINT GrassDataPointer;
|
|
Ar << GrassDataPointer;
|
|
// Duplicate shared reference
|
|
GrassData = *(TSharedRef<FLandscapeComponentGrassData, ESPMode::ThreadSafe>*)GrassDataPointer;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bStripGrassData)
|
|
{
|
|
FLandscapeComponentGrassData EmptyGrassData;
|
|
EmptyGrassData.NumElements = 0;
|
|
Ar << EmptyGrassData;
|
|
}
|
|
else
|
|
{
|
|
// technically on load this is doing a thread-unsafe operation by stomping the data in the existing ref
|
|
// but we're assuming there are no async threads using this pointer yet at load...
|
|
Ar << GrassData.Get();
|
|
}
|
|
}
|
|
|
|
// When loading or saving a component, validate that grass data is valid :
|
|
checkf(IsTemplate() || !Ar.IsLoading() || !Ar.IsSaving() || GrassData->HasValidData(), TEXT("If this asserts, then serialization occurred on grass data that wasn't properly loaded/computed. It's a problem"));
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (Ar.IsTransacting())
|
|
{
|
|
Ar << EditToolRenderData.SelectedType;
|
|
}
|
|
#endif
|
|
|
|
bool bCooked = false;
|
|
|
|
if (Ar.UEVer() >= VER_UE4_LANDSCAPE_PLATFORMDATA_COOKING && !HasAnyFlags(RF_ClassDefaultObject))
|
|
{
|
|
bCooked = Ar.IsCooking() || (FPlatformProperties::RequiresCookedData() && Ar.IsSaving());
|
|
// This is needed when loading cooked data, to know to serialize differently
|
|
Ar << bCooked;
|
|
}
|
|
|
|
if (FPlatformProperties::RequiresCookedData() && !bCooked && Ar.IsLoading())
|
|
{
|
|
UE_LOG(LogLandscape, Fatal, TEXT("This platform requires cooked packages, and this landscape does not contain cooked data %s."), *GetName());
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (Ar.IsSaving() && Ar.IsPersistent())
|
|
{
|
|
//Update the last saved Hash for physical material
|
|
LastSavedPhysicalMaterialHash = PhysicalMaterialHash;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void ULandscapeComponent::GetResourceSizeEx(FResourceSizeEx& CumulativeResourceSize)
|
|
{
|
|
Super::GetResourceSizeEx(CumulativeResourceSize);
|
|
|
|
CumulativeResourceSize.AddDedicatedSystemMemoryBytes(GrassData->GetAllocatedSize());
|
|
}
|
|
|
|
UMaterialInterface* ULandscapeComponent::GetLandscapeMaterial(int8 InLODIndex) const
|
|
{
|
|
if (InLODIndex != INDEX_NONE)
|
|
{
|
|
UWorld* World = GetWorld();
|
|
|
|
if (World != nullptr)
|
|
{
|
|
if (const FLandscapePerLODMaterialOverride* LocalMaterialOverride = PerLODOverrideMaterials.FindByPredicate(
|
|
[InLODIndex](const FLandscapePerLODMaterialOverride& InOverride) { return (InOverride.LODIndex == InLODIndex) && (InOverride.Material != nullptr); }))
|
|
{
|
|
return LocalMaterialOverride->Material;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (OverrideMaterial != nullptr)
|
|
{
|
|
return OverrideMaterial;
|
|
}
|
|
|
|
ALandscapeProxy* Proxy = GetLandscapeProxy();
|
|
if (Proxy)
|
|
{
|
|
return Proxy->GetLandscapeMaterial(InLODIndex);
|
|
}
|
|
|
|
return UMaterial::GetDefaultMaterial(MD_Surface);
|
|
}
|
|
|
|
UMaterialInterface* ULandscapeComponent::GetLandscapeHoleMaterial() const
|
|
{
|
|
if (OverrideHoleMaterial)
|
|
{
|
|
return OverrideHoleMaterial;
|
|
}
|
|
ALandscapeProxy* Proxy = GetLandscapeProxy();
|
|
if (Proxy)
|
|
{
|
|
return Proxy->GetLandscapeHoleMaterial();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
bool ULandscapeComponent::IsLandscapeHoleMaterialValid() const
|
|
{
|
|
UMaterialInterface* HoleMaterial = GetLandscapeHoleMaterial();
|
|
if (!HoleMaterial)
|
|
{
|
|
HoleMaterial = GetLandscapeMaterial();
|
|
}
|
|
|
|
return HoleMaterial ? HoleMaterial->GetMaterial()->HasAnyExpressionsInMaterialAndFunctionsOfType<UMaterialExpressionLandscapeVisibilityMask>() : false;
|
|
}
|
|
|
|
bool ULandscapeComponent::ComponentHasVisibilityPainted() const
|
|
{
|
|
for (const FWeightmapLayerAllocationInfo& Allocation : WeightmapLayerAllocations)
|
|
{
|
|
if (Allocation.LayerInfo == ALandscapeProxy::VisibilityLayer)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
ULandscapeLayerInfoObject* ULandscapeComponent::GetVisibilityLayer() const
|
|
{
|
|
for (const FWeightmapLayerAllocationInfo& Allocation : WeightmapLayerAllocations)
|
|
{
|
|
if (Allocation.LayerInfo == ALandscapeProxy::VisibilityLayer)
|
|
{
|
|
return Allocation.LayerInfo;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void ULandscapeComponent::GetLayerDebugColorKey(int32& R, int32& G, int32& B) const
|
|
{
|
|
ULandscapeInfo* Info = GetLandscapeInfo();
|
|
if (Info != nullptr)
|
|
{
|
|
R = INDEX_NONE, G = INDEX_NONE, B = INDEX_NONE;
|
|
|
|
for (auto It = Info->Layers.CreateConstIterator(); It; It++)
|
|
{
|
|
const FLandscapeInfoLayerSettings& LayerSettings = *It;
|
|
if (LayerSettings.DebugColorChannel > 0
|
|
&& LayerSettings.LayerInfoObj)
|
|
{
|
|
const TArray<FWeightmapLayerAllocationInfo>& ComponentWeightmapLayerAllocations = GetWeightmapLayerAllocations();
|
|
|
|
for (int32 LayerIdx = 0; LayerIdx < ComponentWeightmapLayerAllocations.Num(); LayerIdx++)
|
|
{
|
|
if (ComponentWeightmapLayerAllocations[LayerIdx].LayerInfo == LayerSettings.LayerInfoObj)
|
|
{
|
|
if (LayerSettings.DebugColorChannel & 1) // R
|
|
{
|
|
R = (ComponentWeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex * 4 + ComponentWeightmapLayerAllocations[LayerIdx].WeightmapTextureChannel);
|
|
}
|
|
if (LayerSettings.DebugColorChannel & 2) // G
|
|
{
|
|
G = (ComponentWeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex * 4 + ComponentWeightmapLayerAllocations[LayerIdx].WeightmapTextureChannel);
|
|
}
|
|
if (LayerSettings.DebugColorChannel & 4) // B
|
|
{
|
|
B = (ComponentWeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex * 4 + ComponentWeightmapLayerAllocations[LayerIdx].WeightmapTextureChannel);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif //WITH_EDITOR
|
|
|
|
ULandscapeInfo::ULandscapeInfo(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
#if WITH_EDITORONLY_DATA
|
|
, bDirtyOnlyInMode(false)
|
|
#endif
|
|
, XYComponentBounds(MAX_int32, MAX_int32, MIN_int32, MIN_int32)
|
|
{
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
void ULandscapeInfo::UpdateDebugColorMaterial()
|
|
{
|
|
FlushRenderingCommands();
|
|
//GWarn->BeginSlowTask( *FString::Printf(TEXT("Compiling layer color combinations for %s"), *GetName()), true);
|
|
|
|
for (auto It = XYtoComponentMap.CreateIterator(); It; ++It)
|
|
{
|
|
ULandscapeComponent* Comp = It.Value();
|
|
if (Comp)
|
|
{
|
|
Comp->EditToolRenderData.UpdateDebugColorMaterial(Comp);
|
|
Comp->UpdateEditToolRenderData();
|
|
}
|
|
}
|
|
FlushRenderingCommands();
|
|
//GWarn->EndSlowTask();
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
void ULandscapeComponent::UpdatedSharedPropertiesFromActor()
|
|
{
|
|
ALandscapeProxy* LandscapeProxy = GetLandscapeProxy();
|
|
|
|
CastShadow = LandscapeProxy->CastShadow;
|
|
bCastDynamicShadow = LandscapeProxy->bCastDynamicShadow;
|
|
bCastStaticShadow = LandscapeProxy->bCastStaticShadow;
|
|
bCastContactShadow = LandscapeProxy->bCastContactShadow;
|
|
bCastFarShadow = LandscapeProxy->bCastFarShadow;
|
|
bCastHiddenShadow = LandscapeProxy->bCastHiddenShadow;
|
|
bCastShadowAsTwoSided = LandscapeProxy->bCastShadowAsTwoSided;
|
|
bAffectDistanceFieldLighting = LandscapeProxy->bAffectDistanceFieldLighting;
|
|
bAffectDynamicIndirectLighting = LandscapeProxy->bAffectDynamicIndirectLighting;
|
|
bAffectIndirectLightingWhileHidden = LandscapeProxy->bAffectIndirectLightingWhileHidden;
|
|
bRenderCustomDepth = LandscapeProxy->bRenderCustomDepth;
|
|
CustomDepthStencilWriteMask = LandscapeProxy->CustomDepthStencilWriteMask;
|
|
CustomDepthStencilValue = LandscapeProxy->CustomDepthStencilValue;
|
|
SetCullDistance(LandscapeProxy->LDMaxDrawDistance);
|
|
LightingChannels = LandscapeProxy->LightingChannels;
|
|
ShadowCacheInvalidationBehavior = LandscapeProxy->ShadowCacheInvalidationBehavior;
|
|
bHoldout = LandscapeProxy->bHoldout;
|
|
|
|
UpdateNavigationRelevance();
|
|
UpdateRejectNavmeshUnderneath();
|
|
}
|
|
|
|
void ULandscapeComponent::PostLoad()
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(ULandscapeComponent::PostLoad);
|
|
|
|
using namespace UE::Landscape;
|
|
|
|
Super::PostLoad();
|
|
|
|
if (IsComponentPSOPrecachingEnabled())
|
|
{
|
|
TArray<UMaterialInterface*> Materials;
|
|
bool bGetDebugMaterials = false;
|
|
GetUsedMaterials(Materials, bGetDebugMaterials);
|
|
|
|
FPSOPrecacheParams PrecachePSOParams;
|
|
SetupPrecachePSOParams(PrecachePSOParams);
|
|
|
|
FPSOPrecacheVertexFactoryDataList VertexFactoryDataList;
|
|
if (!XYOffsetmapTexture)
|
|
{
|
|
VertexFactoryDataList.Add(FPSOPrecacheVertexFactoryData(&FLandscapeVertexFactory::StaticType));
|
|
}
|
|
else
|
|
{
|
|
VertexFactoryDataList.Add(FPSOPrecacheVertexFactoryData(&FLandscapeXYOffsetVertexFactory::StaticType));
|
|
}
|
|
|
|
// we need the fixed grid vertex factory for both virtual texturing and grass
|
|
if (NeedsFixedGridVertexFactory(GMaxRHIShaderPlatform))
|
|
{
|
|
VertexFactoryDataList.Add(FPSOPrecacheVertexFactoryData(&FLandscapeFixedGridVertexFactory::StaticType));
|
|
}
|
|
|
|
if (Culling::UseCulling(GMaxRHIShaderPlatform))
|
|
{
|
|
VertexFactoryDataList.Add(FPSOPrecacheVertexFactoryData(Culling::GetTileVertexFactoryType()));
|
|
}
|
|
|
|
TArray<FMaterialPSOPrecacheRequestID> MaterialPrecacheRequestIDs;
|
|
for (UMaterialInterface* MaterialInterface : Materials)
|
|
{
|
|
if (MaterialInterface)
|
|
{
|
|
if (IsComponentPSOPrecachingEnabled())
|
|
{
|
|
MaterialInterface->PrecachePSOs(VertexFactoryDataList, PrecachePSOParams, EPSOPrecachePriority::High, MaterialPrecacheRequestIDs);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
ALandscapeProxy* LandscapeProxy = GetLandscapeProxy();
|
|
if (ensure(LandscapeProxy))
|
|
{
|
|
// Ensure that the component's lighting settings matches the actor's.
|
|
UpdatedSharedPropertiesFromActor();
|
|
|
|
// check SectionBaseX/Y are correct
|
|
const FVector LocalRelativeLocation = GetRelativeLocation();
|
|
int32 CheckSectionBaseX = FMath::RoundToInt32(LocalRelativeLocation.X) + LandscapeProxy->LandscapeSectionOffset.X;
|
|
int32 CheckSectionBaseY = FMath::RoundToInt32(LocalRelativeLocation.Y) + LandscapeProxy->LandscapeSectionOffset.Y;
|
|
if (CheckSectionBaseX != SectionBaseX ||
|
|
CheckSectionBaseY != SectionBaseY)
|
|
{
|
|
UE_LOG(LogLandscape, Warning, TEXT("LandscapeComponent SectionBaseX disagrees with its location, attempted automated fix: '%s', %d,%d vs %d,%d."),
|
|
*GetFullName(), SectionBaseX, SectionBaseY, CheckSectionBaseX, CheckSectionBaseY);
|
|
SectionBaseX = CheckSectionBaseX;
|
|
SectionBaseY = CheckSectionBaseY;
|
|
}
|
|
}
|
|
|
|
if (GIsEditor && !HasAnyFlags(RF_ClassDefaultObject))
|
|
{
|
|
// This is to ensure that component relative location is exact section base offset value
|
|
FVector LocalRelativeLocation = GetRelativeLocation();
|
|
float CheckRelativeLocationX = float(SectionBaseX - LandscapeProxy->LandscapeSectionOffset.X);
|
|
float CheckRelativeLocationY = float(SectionBaseY - LandscapeProxy->LandscapeSectionOffset.Y);
|
|
if (!FMath::IsNearlyEqual(CheckRelativeLocationX, LocalRelativeLocation.X, UE_DOUBLE_KINDA_SMALL_NUMBER) ||
|
|
!FMath::IsNearlyEqual(CheckRelativeLocationY, LocalRelativeLocation.Y, UE_DOUBLE_KINDA_SMALL_NUMBER))
|
|
{
|
|
UE_LOG(LogLandscape, Warning, TEXT("LandscapeComponent RelativeLocation disagrees with its section base, attempted automated fix: '%s', %f,%f vs %f,%f."),
|
|
*GetFullName(), LocalRelativeLocation.X, LocalRelativeLocation.Y, CheckRelativeLocationX, CheckRelativeLocationY);
|
|
LocalRelativeLocation.X = CheckRelativeLocationX;
|
|
LocalRelativeLocation.Y = CheckRelativeLocationY;
|
|
|
|
SetRelativeLocation_Direct(LocalRelativeLocation);
|
|
}
|
|
|
|
// Remove standalone flags from data textures to ensure data is unloaded in the editor when reverting an unsaved level.
|
|
// Previous version of landscape set these flags on creation.
|
|
if (HeightmapTexture)
|
|
{
|
|
ULandscapeTextureHash::SetInitialStateOnPostLoad(HeightmapTexture, ELandscapeTextureUsage::FinalData, ELandscapeTextureType::Heightmap);
|
|
if (HeightmapTexture->HasAnyFlags(RF_Standalone))
|
|
{
|
|
HeightmapTexture->ClearFlags(RF_Standalone);
|
|
}
|
|
}
|
|
for (int32 Idx = 0; Idx < WeightmapTextures.Num(); Idx++)
|
|
{
|
|
ULandscapeTextureHash::SetInitialStateOnPostLoad(WeightmapTextures[Idx], ELandscapeTextureUsage::FinalData, ELandscapeTextureType::Weightmap);
|
|
if (WeightmapTextures[Idx])
|
|
{
|
|
if (WeightmapTextures[Idx]->HasAnyFlags(RF_Standalone))
|
|
{
|
|
WeightmapTextures[Idx]->ClearFlags(RF_Standalone);
|
|
}
|
|
}
|
|
}
|
|
|
|
LastSavedPhysicalMaterialHash = PhysicalMaterialHash;
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS;
|
|
if (!OverrideMaterials_DEPRECATED.IsEmpty())
|
|
{
|
|
PerLODOverrideMaterials.Reserve(OverrideMaterials_DEPRECATED.Num());
|
|
for (const FLandscapeComponentMaterialOverride& LocalMaterialOverride : OverrideMaterials_DEPRECATED)
|
|
{
|
|
PerLODOverrideMaterials.Add({ LocalMaterialOverride.LODIndex.Default, LocalMaterialOverride.Material });
|
|
}
|
|
OverrideMaterials_DEPRECATED.Reset();
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS;
|
|
}
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
// Handle old MaterialInstance
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
if (MaterialInstance_DEPRECATED)
|
|
{
|
|
MaterialInstances.Empty(1);
|
|
MaterialInstances.Add(MaterialInstance_DEPRECATED);
|
|
MaterialInstance_DEPRECATED = nullptr;
|
|
|
|
if (GIsEditor && MaterialInstances.Num() > 0 && MaterialInstances[0] != nullptr)
|
|
{
|
|
MaterialInstances[0]->ConditionalPostLoad();
|
|
UpdateMaterialInstances();
|
|
}
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
if (CVarStripLayerTextureMipsOnLoad->GetBool())
|
|
{
|
|
auto DropMipChain = [](UTexture2D* InTexture)
|
|
{
|
|
if (InTexture->Source.GetNumMips() <= 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TArray64<uint8> TopMipData;
|
|
InTexture->Source.GetMipData(TopMipData, 0);
|
|
|
|
InTexture->PreEditChange(nullptr);
|
|
InTexture->Source.Init(InTexture->Source.GetSizeX(), InTexture->Source.GetSizeY(), 1, 1, InTexture->Source.GetFormat(), TopMipData.GetData());
|
|
InTexture->UpdateResource();
|
|
|
|
InTexture->PostEditChange();
|
|
};
|
|
|
|
// Remove Non zero mip levels found in layer textures
|
|
for (auto& LayerIt : LayersData)
|
|
{
|
|
DropMipChain(LayerIt.Value.HeightmapData.Texture);
|
|
for (int32 i = 0; i < LayerIt.Value.WeightmapData.Textures.Num(); ++i)
|
|
{
|
|
DropMipChain(LayerIt.Value.WeightmapData.Textures[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
auto ReparentObject = [this](UObject* Object)
|
|
{
|
|
if (Object && !Object->HasAllFlags(RF_Public | RF_Standalone) && (Object->GetOuter() != GetOuter()) && (Object->GetOutermost() == GetOutermost()))
|
|
{
|
|
Object->Rename(nullptr, GetOuter());
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
ReparentObject(HeightmapTexture);
|
|
ReparentObject(XYOffsetmapTexture);
|
|
|
|
for (UTexture2D* WeightmapTexture : WeightmapTextures)
|
|
{
|
|
ReparentObject(WeightmapTexture);
|
|
}
|
|
|
|
for (UTexture2D* MobileWeightmapTexture : MobileWeightmapTextures)
|
|
{
|
|
ReparentObject(MobileWeightmapTexture);
|
|
}
|
|
|
|
if (MobileWeightmapTextureArray)
|
|
{
|
|
ReparentObject(MobileWeightmapTextureArray.Get());
|
|
}
|
|
|
|
for (auto& ItPair : LayersData)
|
|
{
|
|
FLandscapeLayerComponentData& LayerComponentData = ItPair.Value;
|
|
ReparentObject(LayerComponentData.HeightmapData.Texture);
|
|
for (UTexture2D* WeightmapTexture : LayerComponentData.WeightmapData.Textures)
|
|
{
|
|
ReparentObject(WeightmapTexture);
|
|
}
|
|
|
|
// Fixup missing/mismatching edit layer names :
|
|
if (const ULandscapeEditLayerBase* LandscapeEditLayer = GetLandscapeActor() ? GetLandscapeActor()->GetEditLayerConst(ItPair.Key) : nullptr)
|
|
{
|
|
if (LayerComponentData.DebugName != LandscapeEditLayer->GetName())
|
|
{
|
|
LayerComponentData.DebugName = LandscapeEditLayer->GetName();
|
|
}
|
|
}
|
|
}
|
|
|
|
for (UMaterialInstance* MaterialInstance : MaterialInstances)
|
|
{
|
|
ULandscapeMaterialInstanceConstant* CurrentMIC = Cast<ULandscapeMaterialInstanceConstant>(MaterialInstance);
|
|
while (ReparentObject(CurrentMIC))
|
|
{
|
|
CurrentMIC = Cast<ULandscapeMaterialInstanceConstant>(MaterialInstance->Parent);
|
|
}
|
|
}
|
|
|
|
for (UMaterialInterface* MobileMaterialInterface : MobileMaterialInterfaces)
|
|
{
|
|
while (ReparentObject(MobileMaterialInterface))
|
|
{
|
|
MobileMaterialInterface = Cast<UMaterialInstance>(MobileMaterialInterface) ? Cast<UMaterialInstance>(((UMaterialInstance*)MobileMaterialInterface)->Parent) : nullptr;
|
|
}
|
|
}
|
|
|
|
for (UMaterialInstance* MobileCombinationMaterialInstance : MobileCombinationMaterialInstances)
|
|
{
|
|
while (ReparentObject(MobileCombinationMaterialInstance))
|
|
{
|
|
MobileCombinationMaterialInstance = Cast<UMaterialInstance>(MobileCombinationMaterialInstance->Parent);
|
|
}
|
|
}
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
// This will fix the data in case there is mismatch between save of asset/maps
|
|
const int8 MaxLOD = static_cast<int8>(FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
|
|
|
|
TArray<ULandscapeMaterialInstanceConstant*> ResolvedMaterials;
|
|
|
|
if (LODIndexToMaterialIndex.Num() != MaxLOD+1)
|
|
{
|
|
if (GIsEditor)
|
|
{
|
|
UpdateMaterialInstances();
|
|
}
|
|
else
|
|
{
|
|
// Correct in-place differences by applying the highest LOD value we have to the newly added items as most case will be missing items added at the end
|
|
LODIndexToMaterialIndex.SetNumZeroed(MaxLOD + 1);
|
|
|
|
int8 LastLODIndex = 0;
|
|
|
|
for (int32 i = 0; i < LODIndexToMaterialIndex.Num(); ++i)
|
|
{
|
|
if (LODIndexToMaterialIndex[i] > LastLODIndex)
|
|
{
|
|
LastLODIndex = LODIndexToMaterialIndex[i];
|
|
}
|
|
|
|
if (LODIndexToMaterialIndex[i] == 0 && LastLODIndex != 0)
|
|
{
|
|
LODIndexToMaterialIndex[i] = LastLODIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // UE_BUILD_SHIPPING
|
|
|
|
if (GIsEditor && !HasAnyFlags(RF_ClassDefaultObject))
|
|
{
|
|
// Move the MICs and Textures back to the Package if they're currently in the level
|
|
// Moving them into the level caused them to be duplicated when running PIE, which is *very very slow*, so we've reverted that change
|
|
// Also clear the public flag to avoid various issues, e.g. generating and saving thumbnails that can never be seen
|
|
if (ULevel* Level = GetLevel())
|
|
{
|
|
TArray<UObject*> ObjectsToMoveFromLevelToPackage;
|
|
GetGeneratedTexturesAndMaterialInstances(ObjectsToMoveFromLevelToPackage);
|
|
|
|
UPackage* MyPackage = GetOutermost();
|
|
for (auto* Obj : ObjectsToMoveFromLevelToPackage)
|
|
{
|
|
Obj->ClearFlags(RF_Public);
|
|
if (Obj->GetOuter() == Level)
|
|
{
|
|
Obj->Rename(nullptr, MyPackage, REN_DoNotDirty | REN_DontCreateRedirectors | REN_NonTransactional);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GIsEditor && GetLinkerCustomVersion(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::LandscapeSupportPerComponentGrassTypes)
|
|
{
|
|
UpdateGrassTypes();
|
|
}
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
if (MobileCombinationMaterialInstances.Num() == 0)
|
|
{
|
|
if (GIsEditor)
|
|
{
|
|
UpdateMaterialInstances();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogLandscape, Error, TEXT("Landscape component (%d, %d) Does not have a valid mobile combination material. To correct this issue, open the map in the editor and resave the map."), SectionBaseX, SectionBaseY);
|
|
}
|
|
}
|
|
#endif // UE_BUILD_SHIPPING
|
|
|
|
|
|
// May have been saved without mobile layer allocations, but those are serialized now
|
|
if (MobileWeightmapLayerAllocations.Num() == 0)
|
|
{
|
|
GenerateMobileWeightmapLayerAllocations();
|
|
}
|
|
|
|
if (!HasAnyFlags(RF_ClassDefaultObject))
|
|
{
|
|
FSceneInterface* SceneInterface = GetScene();
|
|
ERHIFeatureLevel::Type FeatureLevel = ((GEngine->GetDefaultWorldFeatureLevel() == ERHIFeatureLevel::ES3_1) || (SceneInterface && (SceneInterface->GetFeatureLevel() <= ERHIFeatureLevel::ES3_1)))
|
|
? ERHIFeatureLevel::ES3_1 : GMaxRHIFeatureLevel;
|
|
|
|
// If we're loading on a platform that doesn't require cooked data, but defaults to a mobile feature level, generate or preload data from the DDC
|
|
if (!FPlatformProperties::RequiresCookedData() && FeatureLevel == ERHIFeatureLevel::ES3_1)
|
|
{
|
|
CheckGenerateMobilePlatformData(/*bIsCooking = */ false, /*TargetPlatform = */ nullptr);
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
// If the Collision Component is not set yet and we're transferring the property from the lazy object pointer it was previously stored as to the soft object ptr it is now stored as :
|
|
if (!CollisionComponentRef && CollisionComponent_DEPRECATED.IsValid())
|
|
{
|
|
CollisionComponentRef = CollisionComponent_DEPRECATED.Get();
|
|
CollisionComponent_DEPRECATED = nullptr;
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
// If mip-to-mip info is missing, recompute them (they were introduced later) :
|
|
if (MipToMipMaxDeltas.IsEmpty())
|
|
{
|
|
UpdateCachedBounds();
|
|
}
|
|
#endif // !WITH_EDITORONLY_DATA
|
|
|
|
#endif // WITH_EDITOR
|
|
|
|
GrassData->ConditionalDiscardDataOnLoad();
|
|
}
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
void ULandscapeComponent::DeclareConstructClasses(TArray<FTopLevelAssetPath>& OutConstructClasses, const UClass* SpecificSubclass)
|
|
{
|
|
Super::DeclareConstructClasses(OutConstructClasses, SpecificSubclass);
|
|
OutConstructClasses.Add(FTopLevelAssetPath(ULandscapeMaterialInstanceConstant::StaticClass()));
|
|
}
|
|
#endif
|
|
|
|
void ULandscapeComponent::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector)
|
|
{
|
|
Super::AddReferencedObjects(InThis, Collector);
|
|
|
|
ThisClass* const TypedThis = Cast<ThisClass>(InThis);
|
|
Collector.AddReferencedObjects(TypedThis->GrassData->WeightOffsets, TypedThis);
|
|
}
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
TArray<ALandscapeProxy*> ALandscapeProxy::LandscapeProxies;
|
|
#endif
|
|
|
|
ALandscapeProxy::ALandscapeProxy(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
bReplicates = false;
|
|
SetNetUpdateFrequency(10.0f);
|
|
SetHidden(false);
|
|
SetReplicatingMovement(false);
|
|
SetCanBeDamaged(false);
|
|
|
|
CastShadow = true;
|
|
bCastDynamicShadow = true;
|
|
bCastStaticShadow = true;
|
|
bCastContactShadow = true;
|
|
bCastFarShadow = true;
|
|
bCastHiddenShadow = false;
|
|
bCastShadowAsTwoSided = false;
|
|
bAffectDistanceFieldLighting = true;
|
|
bAffectDynamicIndirectLighting = true;
|
|
bAffectIndirectLightingWhileHidden = false;
|
|
bHoldout = false;
|
|
|
|
RootComponent->SetRelativeScale3D(FVector(128.0f, 128.0f, 256.0f)); // Old default scale, preserved for compatibility. See ULandscapeEditorObject::NewLandscape_Scale
|
|
RootComponent->Mobility = EComponentMobility::Static;
|
|
LandscapeSectionOffset = FIntPoint::ZeroValue;
|
|
|
|
StaticLightingResolution = 1.0f;
|
|
StreamingDistanceMultiplier = 1.0f;
|
|
MaxLODLevel = -1;
|
|
bUseDynamicMaterialInstance = false;
|
|
#if WITH_EDITORONLY_DATA
|
|
bLockLocation = true;
|
|
#endif // WITH_EDITORONLY_DATA
|
|
bCastStaticShadow = true;
|
|
ShadowCacheInvalidationBehavior = EShadowCacheInvalidationBehavior::Auto;
|
|
bUsedForNavigation = true;
|
|
bFillCollisionUnderLandscapeForNavmesh = false;
|
|
BodyInstance.SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName);
|
|
bGenerateOverlapEvents = false;
|
|
#if WITH_EDITORONLY_DATA
|
|
MaxPaintedLayersPerComponent = 0;
|
|
bHasLayersContent = false;
|
|
HLODTextureSizePolicy = ELandscapeHLODTextureSizePolicy::SpecificSize;
|
|
HLODTextureSize = 256;
|
|
HLODMeshSourceLODPolicy = ELandscapeHLODMeshSourceLODPolicy::LowestDetailLOD;
|
|
HLODMeshSourceLOD = 0;
|
|
#endif
|
|
|
|
#if WITH_EDITOR
|
|
if (VisibilityLayer == nullptr)
|
|
{
|
|
// Structure to hold one-time initialization
|
|
struct FConstructorStatics
|
|
{
|
|
ConstructorHelpers::FObjectFinderOptional<ULandscapeLayerInfoObject> LandscapeVisibilityLayerInfoFinder;
|
|
FConstructorStatics()
|
|
: LandscapeVisibilityLayerInfoFinder(TEXT("LandscapeLayerInfoObject'/Engine/EngineResources/LandscapeVisibilityLayerInfo.LandscapeVisibilityLayerInfo'"))
|
|
{
|
|
}
|
|
};
|
|
static FConstructorStatics ConstructorStatics;
|
|
|
|
VisibilityLayer = ConstructorStatics.LandscapeVisibilityLayerInfoFinder.Get();
|
|
check(VisibilityLayer);
|
|
#if WITH_EDITORONLY_DATA
|
|
// This layer should be no weight blending
|
|
VisibilityLayer->bNoWeightBlend = true;
|
|
#endif
|
|
VisibilityLayer->LayerName = UMaterialExpressionLandscapeVisibilityMask::ParameterName;
|
|
VisibilityLayer->LayerUsageDebugColor = FLinearColor(0, 0, 0, 0);
|
|
VisibilityLayer->AddToRoot();
|
|
}
|
|
|
|
if (!HasAnyFlags(RF_ArchetypeObject | RF_ClassDefaultObject) && GetWorld() != nullptr)
|
|
{
|
|
FOnFeatureLevelChanged::FDelegate FeatureLevelChangedDelegate = FOnFeatureLevelChanged::FDelegate::CreateUObject(this, &ALandscapeProxy::OnFeatureLevelChanged);
|
|
FeatureLevelChangedDelegateHandle = GetWorld()->AddOnFeatureLevelChangedHandler(FeatureLevelChangedDelegate);
|
|
}
|
|
#endif
|
|
|
|
static uint32 FrameOffsetForTickIntervalInc = 0;
|
|
FrameOffsetForTickInterval = FrameOffsetForTickIntervalInc++;
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
LandscapeProxies.Add(this);
|
|
#endif
|
|
}
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
ALandscape::FLandscapeEdModeInfo::FLandscapeEdModeInfo()
|
|
: ViewMode(ELandscapeViewMode::Invalid)
|
|
, ToolTarget(ELandscapeToolTargetType::Invalid)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
ALandscape::ALandscape(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
bLockLocation = false;
|
|
WasCompilingShaders = false;
|
|
LayerContentUpdateModes = 0;
|
|
bSplineLayerUpdateRequested = false;
|
|
CombinedLayersWeightmapAllMaterialLayersResource = nullptr;
|
|
CurrentLayersWeightmapAllMaterialLayersResource = nullptr;
|
|
WeightmapScratchExtractLayerTextureResource = nullptr;
|
|
WeightmapScratchPackLayerTextureResource = nullptr;
|
|
bLandscapeLayersAreInitialized = false;
|
|
bLandscapeLayersForceResourceReset = true;
|
|
LandscapeEdMode = nullptr;
|
|
bGrassUpdateEnabled = true;
|
|
bIsSpatiallyLoaded = false;
|
|
bDefaultOutlinerExpansionState = false;
|
|
#endif // WITH_EDITORONLY_DATA
|
|
}
|
|
|
|
ALandscapeStreamingProxy::ALandscapeStreamingProxy(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
bLockLocation = true;
|
|
#endif // WITH_EDITORONLY_DATA
|
|
}
|
|
|
|
const ALandscape* ALandscape::GetLandscapeActor() const
|
|
{
|
|
return this;
|
|
}
|
|
|
|
ALandscape* ALandscape::GetLandscapeActor()
|
|
{
|
|
return this;
|
|
}
|
|
|
|
const ALandscape* ALandscapeStreamingProxy::GetLandscapeActor() const
|
|
{
|
|
return LandscapeActorRef.Get();
|
|
}
|
|
|
|
ALandscape* ALandscapeStreamingProxy::GetLandscapeActor()
|
|
{
|
|
return LandscapeActorRef.Get();
|
|
}
|
|
|
|
void ALandscapeStreamingProxy::SetLandscapeActor(ALandscape* InLandscape)
|
|
{
|
|
LandscapeActorRef = InLandscape;
|
|
}
|
|
|
|
void ALandscape::SetLODGroupKey(uint32 InLODGroupKey)
|
|
{
|
|
SetLODGroupKeyInternal(InLODGroupKey);
|
|
|
|
// change LODGroupKey on any proxies that are currently registered
|
|
// (any proxies that get registered later will copy the value on registration)
|
|
if (ULandscapeInfo* Info = GetLandscapeInfo())
|
|
{
|
|
Info->ForEachLandscapeProxy([InLODGroupKey](ALandscapeProxy* Proxy)
|
|
{
|
|
Proxy->SetLODGroupKeyInternal(InLODGroupKey);
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::SetLODGroupKeyInternal(uint32 InLODGroupKey)
|
|
{
|
|
if (LODGroupKey != InLODGroupKey)
|
|
{
|
|
LODGroupKey = InLODGroupKey;
|
|
MarkComponentsRenderStateDirty();
|
|
}
|
|
}
|
|
|
|
uint32 ALandscape::GetLODGroupKey()
|
|
{
|
|
return LODGroupKey;
|
|
}
|
|
|
|
void ALandscape::MarkAllLandscapeRenderStateDirty()
|
|
{
|
|
if (ULandscapeInfo* Info = GetLandscapeInfo())
|
|
{
|
|
Info->ForEachLandscapeProxy([](ALandscapeProxy* Proxy)
|
|
{
|
|
Proxy->MarkComponentsRenderStateDirty();
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
|
|
ULandscapeInfo* ALandscapeProxy::CreateLandscapeInfo(bool bMapCheck, bool bUpdateAllAddCollisions)
|
|
{
|
|
ULandscapeInfo* LandscapeInfo = ULandscapeInfo::FindOrCreate(GetWorld(), LandscapeGuid);
|
|
LandscapeInfo->RegisterActor(this, bMapCheck, bUpdateAllAddCollisions);
|
|
return LandscapeInfo;
|
|
}
|
|
|
|
ULandscapeInfo* ALandscapeProxy::GetLandscapeInfo() const
|
|
{
|
|
return ULandscapeInfo::Find(GetWorld(), LandscapeGuid);
|
|
}
|
|
|
|
FTransform ALandscapeProxy::LandscapeActorToWorld() const
|
|
{
|
|
FTransform TM = ActorToWorld();
|
|
// Add this proxy landscape section offset to obtain landscape actor transform
|
|
TM.AddToTranslation(TM.TransformVector(-FVector(LandscapeSectionOffset)));
|
|
return TM;
|
|
}
|
|
|
|
void ALandscapeProxy::UpdateSharedProperties(ULandscapeInfo* InLandscapeInfo)
|
|
{
|
|
check(LandscapeGuid == InLandscapeInfo->LandscapeGuid);
|
|
}
|
|
|
|
static TArray<float> GetLODScreenSizeArray(const ALandscapeProxy* InLandscapeProxy, const int32 InNumLODLevels)
|
|
{
|
|
float LOD0ScreenSize;
|
|
float LOD0Distribution;
|
|
if (InLandscapeProxy->bUseScalableLODSettings)
|
|
{
|
|
const int32 LandscapeQuality = Scalability::GetQualityLevels().LandscapeQuality;
|
|
|
|
LOD0ScreenSize = InLandscapeProxy->ScalableLOD0ScreenSize.GetValue(LandscapeQuality);
|
|
LOD0Distribution = InLandscapeProxy->ScalableLOD0DistributionSetting.GetValue(LandscapeQuality);
|
|
}
|
|
else
|
|
{
|
|
static IConsoleVariable* CVarLSLOD0DistributionScale = IConsoleManager::Get().FindConsoleVariable(TEXT("r.LandscapeLOD0DistributionScale"));
|
|
|
|
LOD0ScreenSize = InLandscapeProxy->LOD0ScreenSize;
|
|
LOD0Distribution = InLandscapeProxy->LOD0DistributionSetting * CVarLSLOD0DistributionScale->GetFloat();
|
|
}
|
|
|
|
static TConsoleVariableData<float>* CVarSMLODDistanceScale = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.StaticMeshLODDistanceScale"));
|
|
|
|
float CurrentScreenSize = LOD0ScreenSize / CVarSMLODDistanceScale->GetValueOnGameThread();
|
|
const float ScreenSizeMult = 1.f / FMath::Max(LOD0Distribution , 1.01f);
|
|
|
|
TArray<float> Result;
|
|
Result.Empty(InNumLODLevels);
|
|
for (int32 Idx = 0; Idx < InNumLODLevels; ++Idx)
|
|
{
|
|
Result.Add(CurrentScreenSize);
|
|
CurrentScreenSize *= ScreenSizeMult;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
TArray<float> ALandscapeProxy::GetLODScreenSizeArray() const
|
|
{
|
|
const int32 MaxPossibleLOD = FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1;
|
|
const int32 MaxLOD = MaxLODLevel != INDEX_NONE ? FMath::Min<int32>(MaxLODLevel, MaxPossibleLOD) : MaxPossibleLOD;
|
|
|
|
const int32 NumLODLevels = MaxLOD + 1;
|
|
return ::GetLODScreenSizeArray(this, NumLODLevels);
|
|
}
|
|
|
|
|
|
ALandscape* ULandscapeComponent::GetLandscapeActor() const
|
|
{
|
|
ALandscapeProxy* Landscape = GetLandscapeProxy();
|
|
if (Landscape)
|
|
{
|
|
return Landscape->GetLandscapeActor();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
ULevel* ULandscapeComponent::GetLevel() const
|
|
{
|
|
AActor* MyOwner = GetOwner();
|
|
return MyOwner ? MyOwner->GetLevel() : nullptr;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
TArray<UTexture*> ULandscapeComponent::GetGeneratedTextures() const
|
|
{
|
|
TArray<UTexture*> OutTextures;
|
|
if (HeightmapTexture)
|
|
{
|
|
OutTextures.Add(HeightmapTexture);
|
|
}
|
|
|
|
for (const auto& ItPair : LayersData)
|
|
{
|
|
const FLandscapeLayerComponentData& LayerComponentData = ItPair.Value;
|
|
|
|
OutTextures.Add(LayerComponentData.HeightmapData.Texture);
|
|
OutTextures.Append(LayerComponentData.WeightmapData.Textures);
|
|
}
|
|
|
|
OutTextures.Append(WeightmapTextures);
|
|
|
|
if (XYOffsetmapTexture)
|
|
{
|
|
OutTextures.Add(XYOffsetmapTexture);
|
|
}
|
|
|
|
TArray<UMaterialInstance*> OutMaterials;
|
|
for (UMaterialInstance* MaterialInstance : MaterialInstances)
|
|
{
|
|
for (ULandscapeMaterialInstanceConstant* CurrentMIC = Cast<ULandscapeMaterialInstanceConstant>(MaterialInstance); CurrentMIC; CurrentMIC = Cast<ULandscapeMaterialInstanceConstant>(CurrentMIC->Parent))
|
|
{
|
|
// Sometimes weight map is not registered in the WeightmapTextures, so
|
|
// we need to get it from here.
|
|
FTextureParameterValue* WeightmapPtr = CurrentMIC->TextureParameterValues.FindByPredicate(
|
|
[](const FTextureParameterValue& ParamValue)
|
|
{
|
|
static const FName WeightmapParamName("Weightmap0");
|
|
return ParamValue.ParameterInfo.Name == WeightmapParamName;
|
|
});
|
|
|
|
if (WeightmapPtr != nullptr)
|
|
{
|
|
OutTextures.AddUnique(WeightmapPtr->ParameterValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
OutTextures.Remove(nullptr);
|
|
|
|
return OutTextures;
|
|
}
|
|
|
|
TArray<UMaterialInstance*> ULandscapeComponent::GetGeneratedMaterialInstances() const
|
|
{
|
|
TArray<UMaterialInstance*> OutMaterials;
|
|
for (UMaterialInstance* MaterialInstance : MaterialInstances)
|
|
{
|
|
for (ULandscapeMaterialInstanceConstant* CurrentMIC = Cast<ULandscapeMaterialInstanceConstant>(MaterialInstance); CurrentMIC; CurrentMIC = Cast<ULandscapeMaterialInstanceConstant>(CurrentMIC->Parent))
|
|
{
|
|
OutMaterials.Add(CurrentMIC);
|
|
}
|
|
}
|
|
|
|
for (UMaterialInstanceConstant* MaterialInstance : MobileCombinationMaterialInstances)
|
|
{
|
|
for (ULandscapeMaterialInstanceConstant* CurrentMIC = Cast<ULandscapeMaterialInstanceConstant>(MaterialInstance); CurrentMIC; CurrentMIC = Cast<ULandscapeMaterialInstanceConstant>(CurrentMIC->Parent))
|
|
{
|
|
OutMaterials.Add(CurrentMIC);
|
|
}
|
|
}
|
|
|
|
return OutMaterials;
|
|
}
|
|
|
|
void ULandscapeComponent::GetGeneratedTexturesAndMaterialInstances(TArray<UObject*>& OutTexturesAndMaterials) const
|
|
{
|
|
TArray<UTexture*> LocalTextures = GetGeneratedTextures();
|
|
TArray<UMaterialInstance*> LocalMaterialInstances = GetGeneratedMaterialInstances();
|
|
OutTexturesAndMaterials.Reserve(LocalTextures.Num() + LocalMaterialInstances.Num());
|
|
OutTexturesAndMaterials.Append(LocalTextures);
|
|
OutTexturesAndMaterials.Append(LocalMaterialInstances);
|
|
}
|
|
#endif
|
|
|
|
ALandscapeProxy* ULandscapeComponent::GetLandscapeProxy() const
|
|
{
|
|
return CastChecked<ALandscapeProxy>(GetOuter());
|
|
}
|
|
|
|
int32 ULandscapeComponent::GetNumRelevantMips() const
|
|
{
|
|
const int32 TextureSize = (SubsectionSizeQuads + 1) * NumSubsections;
|
|
const int32 NumTextureMips = FMath::FloorLog2(TextureSize) + 1;
|
|
// We actually only don't care about the last texture mip, since a 1 vertex landscape is meaningless. When using 2x2 subsections, we can even drop an additional mip
|
|
// as the 4 texels of the penultimate mip will be identical (i.e. 4 sub-sections of 1 vertex are equally meaningless) :
|
|
const int32 NumRelevantMips = (NumSubsections > 1) ? (NumTextureMips - 2) : (NumTextureMips - 1);
|
|
check(NumRelevantMips > 0);
|
|
return NumRelevantMips;
|
|
}
|
|
|
|
const FMeshMapBuildData* ULandscapeComponent::GetMeshMapBuildData() const
|
|
{
|
|
AActor* Owner = GetOwner();
|
|
|
|
if (Owner)
|
|
{
|
|
ULevel* OwnerLevel = Owner->GetLevel();
|
|
|
|
#if WITH_EDITOR
|
|
if (FStaticLightingSystemInterface::GetPrimitiveMeshMapBuildData(this))
|
|
{
|
|
return FStaticLightingSystemInterface::GetPrimitiveMeshMapBuildData(this);
|
|
}
|
|
#endif
|
|
|
|
if (OwnerLevel && OwnerLevel->OwningWorld)
|
|
{
|
|
UMapBuildDataRegistry* MapBuildData = UMapBuildDataRegistry::Get(this);
|
|
|
|
if (MapBuildData)
|
|
{
|
|
return MapBuildData->GetMeshBuildData(MapBuildDataId);
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
bool ULandscapeComponent::IsPrecomputedLightingValid() const
|
|
{
|
|
return GetMeshMapBuildData() != NULL;
|
|
}
|
|
|
|
void ULandscapeComponent::PropagateLightingScenarioChange()
|
|
{
|
|
FComponentRecreateRenderStateContext Context(this);
|
|
}
|
|
|
|
bool ULandscapeComponent::IsHLODRelevant() const
|
|
{
|
|
if (!CanBeHLODRelevant(this))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
return bEnableAutoLODGeneration;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
TArray<URuntimeVirtualTexture*> const& ULandscapeComponent::GetRuntimeVirtualTextures() const
|
|
{
|
|
return GetLandscapeProxy()->RuntimeVirtualTextures;
|
|
}
|
|
|
|
ERuntimeVirtualTextureMainPassType ULandscapeComponent::GetVirtualTextureRenderPassType() const
|
|
{
|
|
return GetLandscapeProxy()->VirtualTextureRenderPassType;
|
|
}
|
|
|
|
ULandscapeInfo* ULandscapeComponent::GetLandscapeInfo() const
|
|
{
|
|
return GetLandscapeProxy()->GetLandscapeInfo();
|
|
}
|
|
|
|
void ULandscapeComponent::BeginDestroy()
|
|
{
|
|
Super::BeginDestroy();
|
|
|
|
#if WITH_EDITOR
|
|
// Ask render thread to destroy EditToolRenderData
|
|
EditToolRenderData = FLandscapeEditToolRenderData();
|
|
UpdateEditToolRenderData();
|
|
|
|
if (GIsEditor && !HasAnyFlags(RF_ClassDefaultObject))
|
|
{
|
|
ALandscapeProxy* Proxy = GetLandscapeProxy();
|
|
|
|
// Remove any weightmap allocations from the Landscape Actor's map
|
|
for (int32 LayerIdx = 0; LayerIdx < WeightmapLayerAllocations.Num(); LayerIdx++)
|
|
{
|
|
int32 WeightmapIndex = WeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex;
|
|
if (WeightmapTextures.IsValidIndex(WeightmapIndex))
|
|
{
|
|
UTexture2D* WeightmapTexture = WeightmapTextures[WeightmapIndex];
|
|
TObjectPtr<ULandscapeWeightmapUsage>* Usage = Proxy->WeightmapUsageMap.Find(WeightmapTexture);
|
|
if (Usage != nullptr && (*Usage) != nullptr)
|
|
{
|
|
(*Usage)->ChannelUsage[WeightmapLayerAllocations[LayerIdx].WeightmapTextureChannel] = nullptr;
|
|
|
|
if ((*Usage)->IsEmpty())
|
|
{
|
|
Proxy->WeightmapUsageMap.Remove(WeightmapTexture);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
WeightmapTexturesUsage.Reset();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
FPrimitiveSceneProxy* ULandscapeComponent::CreateSceneProxy()
|
|
{
|
|
return new FLandscapeComponentSceneProxy(this);
|
|
}
|
|
|
|
bool ULandscapeComponent::IsShown(const FEngineShowFlags& ShowFlags) const
|
|
{
|
|
return ShowFlags.Landscape;
|
|
}
|
|
|
|
void ULandscapeComponent::DestroyComponent(bool bPromoteChildren/*= false*/)
|
|
{
|
|
ALandscapeProxy* Proxy = GetLandscapeProxy();
|
|
if (Proxy)
|
|
{
|
|
Proxy->LandscapeComponents.Remove(this);
|
|
}
|
|
|
|
Super::DestroyComponent(bPromoteChildren);
|
|
}
|
|
|
|
FBoxSphereBounds ULandscapeComponent::CalcBounds(const FTransform& LocalToWorld) const
|
|
{
|
|
FBox MyBounds = CachedLocalBox.TransformBy(LocalToWorld);
|
|
MyBounds = MyBounds.ExpandBy({ 0, 0, NegativeZBoundsExtension }, { 0, 0, PositiveZBoundsExtension });
|
|
|
|
ALandscapeProxy* Proxy = GetLandscapeProxy();
|
|
if (Proxy)
|
|
{
|
|
MyBounds = MyBounds.ExpandBy({ 0, 0, Proxy->NegativeZBoundsExtension }, { 0, 0, Proxy->PositiveZBoundsExtension });
|
|
}
|
|
|
|
return FBoxSphereBounds(MyBounds);
|
|
}
|
|
|
|
static void OnStaticMeshLODDistanceScaleChanged()
|
|
{
|
|
extern RENDERER_API TAutoConsoleVariable<float> CVarStaticMeshLODDistanceScale;
|
|
|
|
static float LastValue = 1.0f;
|
|
|
|
if (LastValue != CVarStaticMeshLODDistanceScale.GetValueOnAnyThread())
|
|
{
|
|
LastValue = CVarStaticMeshLODDistanceScale.GetValueOnAnyThread();
|
|
|
|
for (auto* LandscapeComponent : TObjectRange<ULandscapeComponent>(RF_ClassDefaultObject | RF_ArchetypeObject, true, EInternalObjectFlags::Garbage))
|
|
{
|
|
LandscapeComponent->MarkRenderStateDirty();
|
|
}
|
|
}
|
|
}
|
|
|
|
FAutoConsoleVariableSink OnStaticMeshLODDistanceScaleChangedSink(FConsoleCommandDelegate::CreateStatic(&OnStaticMeshLODDistanceScaleChanged));
|
|
|
|
ULandscapeHeightmapTextureEdgeFixup* ULandscapeComponent::InstallOrUpdateTextureUserDatas(bool bUseEdgeFixup, bool bUseCompression, bool bUpdateSnapshotNow, int32 HeightmapCompressionMipThreshold)
|
|
{
|
|
if (HeightmapTexture == nullptr)
|
|
{
|
|
UE_LOG(LogLandscape, Warning, TEXT("Tried to install EdgeFixup on component %s (proxy %s), but it had NO heightmap"), *GetPathName(), *GetLandscapeProxy()->GetPathName());
|
|
return nullptr;
|
|
}
|
|
|
|
// grid scale must be updated to get proper normal calculations on the edges
|
|
FVector LandscapeGridScale = GetLandscapeProxy()->GetRootComponent()->GetRelativeScale3D();
|
|
|
|
// first update or install the heightmap texture edge fixup
|
|
ULandscapeHeightmapTextureEdgeFixup* EdgeFixup = nullptr;
|
|
if (bUseEdgeFixup && UE::Landscape::ShouldInstallEdgeFixup())
|
|
{
|
|
// find or create edge fixup
|
|
EdgeFixup = ULandscapeHeightmapTextureEdgeFixup::FindOrCreateFor(HeightmapTexture);
|
|
#if WITH_EDITOR
|
|
if (bUpdateSnapshotNow)
|
|
{
|
|
const bool bForceUpdate = true;
|
|
EdgeFixup->UpdateEdgeSnapshotFromHeightmapSource(LandscapeGridScale, bForceUpdate);
|
|
}
|
|
#endif // WITH_EDITOR
|
|
}
|
|
else
|
|
{
|
|
// remove any existing edge fixup (we will remove the factory references to the EdgeFixup below)
|
|
HeightmapTexture->RemoveUserDataOfClass(ULandscapeHeightmapTextureEdgeFixup::StaticClass());
|
|
EdgeFixup = nullptr;
|
|
}
|
|
|
|
// check if the heightmap has a ULandscapeTextureMipEdgeOverrideFactory or a ULandscapeTextureStorageProviderFactory
|
|
ULandscapeTextureMipEdgeOverrideFactory* OverrideFactory = (ULandscapeTextureMipEdgeOverrideFactory*) HeightmapTexture->GetAssetUserDataOfClass(ULandscapeTextureMipEdgeOverrideFactory::StaticClass());
|
|
ULandscapeTextureStorageProviderFactory* StorageFactory = (ULandscapeTextureStorageProviderFactory*) HeightmapTexture->GetAssetUserDataOfClass(ULandscapeTextureStorageProviderFactory::StaticClass());
|
|
|
|
// we should never have both
|
|
check(!OverrideFactory || !StorageFactory);
|
|
|
|
#if WITH_EDITOR
|
|
if (bUseCompression || StorageFactory)
|
|
#else // !WITH_EDITOR
|
|
check(bUseCompression == false); // cannot install compression in non-editor builds
|
|
if (StorageFactory)
|
|
#endif // !WITH_EDITOR
|
|
{
|
|
// Remove any existing mip edge override factory
|
|
if (OverrideFactory)
|
|
{
|
|
OverrideFactory->SetupEdgeFixup(nullptr);
|
|
HeightmapTexture->RemoveUserDataOfClass(ULandscapeTextureMipEdgeOverrideFactory::StaticClass());
|
|
OverrideFactory = nullptr;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
// Install or Update the Storage Factory [editor only]
|
|
if (StorageFactory == nullptr)
|
|
{
|
|
StorageFactory = ULandscapeTextureStorageProviderFactory::ApplyTo(HeightmapTexture, LandscapeGridScale, HeightmapCompressionMipThreshold);
|
|
}
|
|
else
|
|
{
|
|
// since different platforms may change compression settings / thresholds, we should update the compression each time
|
|
StorageFactory->UpdateCompressedDataFromSource(HeightmapTexture, LandscapeGridScale, HeightmapCompressionMipThreshold);
|
|
}
|
|
#endif // WITH_EDITOR
|
|
StorageFactory->SetupEdgeFixup(EdgeFixup);
|
|
}
|
|
|
|
if (EdgeFixup != nullptr)
|
|
{
|
|
// an edge fixup requires at least one factory -- if storage factory doesn't exist, create an override factory
|
|
if (StorageFactory == nullptr)
|
|
{
|
|
if (OverrideFactory == nullptr)
|
|
{
|
|
OverrideFactory = ULandscapeTextureMipEdgeOverrideFactory::AddTo(HeightmapTexture);
|
|
}
|
|
OverrideFactory->SetupEdgeFixup(EdgeFixup);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// no edge fixup, override factory not needed -- remove any existing one
|
|
if (OverrideFactory)
|
|
{
|
|
OverrideFactory->SetupEdgeFixup(nullptr);
|
|
HeightmapTexture->RemoveUserDataOfClass(ULandscapeTextureMipEdgeOverrideFactory::StaticClass());
|
|
OverrideFactory = nullptr;
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
// The EdgeFixup will always require linear texture data and should not apply per platform offline processing
|
|
HeightmapTexture->bNotOfflineProcessed = (EdgeFixup != nullptr);
|
|
#endif // WITH_EDITOR
|
|
|
|
// double check we've achieved the desired relationships..
|
|
if (EdgeFixup)
|
|
{
|
|
// EdgeFixup must have exactly one factory
|
|
check(!!OverrideFactory == !StorageFactory);
|
|
}
|
|
else
|
|
{
|
|
// No EdgeFixup means No override factory
|
|
// (storage factory is optional, it can exist without an EdgeFixup)
|
|
check(!OverrideFactory);
|
|
}
|
|
|
|
return EdgeFixup;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
void ALandscapeProxy::InstallOrUpdateTextureUserDatas(const ITargetPlatform* TargetPlatform)
|
|
{
|
|
FName IniPlatformName = *TargetPlatform->IniPlatformName();
|
|
|
|
int32 HeightmapCompressionMode = 0;
|
|
if (IConsoleVariable* PlatformCVar = CVarLandscapeHeightmapCompressionMode->GetPlatformValueVariable(IniPlatformName).Get())
|
|
{
|
|
PlatformCVar->GetValue(HeightmapCompressionMode);
|
|
}
|
|
else
|
|
{
|
|
CVarLandscapeHeightmapCompressionMode->GetValue(HeightmapCompressionMode);
|
|
}
|
|
|
|
int32 HeightmapCompressionMipThreshold = 32;
|
|
if (IConsoleVariable* PlatformCVar = CVarLandscapeHeightmapCompressionMipThreshold->GetPlatformValueVariable(IniPlatformName).Get())
|
|
{
|
|
PlatformCVar->GetValue(HeightmapCompressionMipThreshold);
|
|
}
|
|
else
|
|
{
|
|
CVarLandscapeHeightmapCompressionMipThreshold->GetValue(HeightmapCompressionMipThreshold);
|
|
}
|
|
|
|
const bool bShouldCompressHeightmap = (HeightmapCompressionMode > 0);
|
|
const bool bIsStreamingProxy = this->IsA<ALandscapeStreamingProxy>();
|
|
const bool bUseEdgeFixups = bIsStreamingProxy;
|
|
const bool bUpdateSnapshotNow = true;
|
|
for (ULandscapeComponent* LandscapeComponent : LandscapeComponents)
|
|
{
|
|
LandscapeComponent->InstallOrUpdateTextureUserDatas(bUseEdgeFixups, bShouldCompressHeightmap, bUpdateSnapshotNow, HeightmapCompressionMipThreshold);
|
|
}
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
void ULandscapeComponent::OnRegister()
|
|
{
|
|
Super::OnRegister();
|
|
|
|
if (ALandscapeProxy* Proxy = GetLandscapeProxy())
|
|
{
|
|
// Generate MID representing the MIC
|
|
if (Proxy->bUseDynamicMaterialInstance)
|
|
{
|
|
MaterialInstancesDynamic.Reserve(MaterialInstances.Num());
|
|
|
|
for (int32 i = 0; i < MaterialInstances.Num(); ++i)
|
|
{
|
|
MaterialInstancesDynamic.Add(UMaterialInstanceDynamic::Create(MaterialInstances[i], this));
|
|
}
|
|
}
|
|
|
|
// AActor::GetWorld checks for Unreachable and BeginDestroyed
|
|
UWorld* World = Proxy->GetWorld();
|
|
if (World)
|
|
{
|
|
if (ULandscapeInfo* Info = GetLandscapeInfo())
|
|
{
|
|
Info->RegisterActorComponent(this);
|
|
}
|
|
if (ULandscapeSubsystem *Subsystem = World->GetSubsystem<ULandscapeSubsystem>())
|
|
{
|
|
Subsystem->RegisterComponent(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::OnUnregister()
|
|
{
|
|
Super::OnUnregister();
|
|
|
|
#if WITH_EDITOR
|
|
PhysicalMaterialTask.Release();
|
|
#endif
|
|
|
|
if (ALandscapeProxy* Proxy = GetLandscapeProxy())
|
|
{
|
|
// Generate MID representing the MIC
|
|
if (Proxy->bUseDynamicMaterialInstance)
|
|
{
|
|
MaterialInstancesDynamic.Empty();
|
|
}
|
|
|
|
// AActor::GetWorld checks for Unreachable and BeginDestroyed
|
|
UWorld* World = Proxy->GetWorld();
|
|
|
|
if (World)
|
|
{
|
|
if (ULandscapeInfo* Info = GetLandscapeInfo())
|
|
{
|
|
Info->UnregisterActorComponent(this);
|
|
}
|
|
if (ULandscapeSubsystem* Subsystem = World->GetSubsystem<ULandscapeSubsystem>())
|
|
{
|
|
Subsystem->UnregisterComponent(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
UTexture2D* ULandscapeComponent::GetHeightmap(bool InReturnEditingHeightmap) const
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
if (InReturnEditingHeightmap)
|
|
{
|
|
if (const FLandscapeLayerComponentData* EditingLayer = GetEditingLayer())
|
|
{
|
|
return EditingLayer->HeightmapData.Texture;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return HeightmapTexture;
|
|
}
|
|
|
|
UTexture2D* ULandscapeComponent::GetHeightmap(const FGuid& InLayerGuid) const
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
if (InLayerGuid.IsValid())
|
|
{
|
|
if (const FLandscapeLayerComponentData* LayerData = GetLayerData(InLayerGuid))
|
|
{
|
|
return LayerData->HeightmapData.Texture;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return HeightmapTexture;
|
|
}
|
|
|
|
const TArray<UTexture2D*>& ULandscapeComponent::GetWeightmapTextures(bool InReturnEditingWeightmap) const
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
if (InReturnEditingWeightmap)
|
|
{
|
|
if (const FLandscapeLayerComponentData* EditingLayer = GetEditingLayer())
|
|
{
|
|
return EditingLayer->WeightmapData.Textures;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return WeightmapTextures;
|
|
}
|
|
|
|
TArray<TObjectPtr<UTexture2D>>& ULandscapeComponent::GetWeightmapTextures(bool InReturnEditingWeightmap)
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
if (InReturnEditingWeightmap)
|
|
{
|
|
if (FLandscapeLayerComponentData* EditingLayer = GetEditingLayer())
|
|
{
|
|
return EditingLayer->WeightmapData.Textures;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return WeightmapTextures;
|
|
}
|
|
|
|
const TArray<UTexture2D*>& ULandscapeComponent::GetWeightmapTextures(const FGuid& InLayerGuid) const
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
if (InLayerGuid.IsValid())
|
|
{
|
|
if (const FLandscapeLayerComponentData* LayerData = GetLayerData(InLayerGuid))
|
|
{
|
|
return LayerData->WeightmapData.Textures;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return WeightmapTextures;
|
|
}
|
|
|
|
TArray<TObjectPtr<UTexture2D>>& ULandscapeComponent::GetWeightmapTextures(const FGuid& InLayerGuid)
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
if (InLayerGuid.IsValid())
|
|
{
|
|
if (FLandscapeLayerComponentData* LayerData = GetLayerData(InLayerGuid))
|
|
{
|
|
return LayerData->WeightmapData.Textures;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return WeightmapTextures;
|
|
}
|
|
|
|
const TArray<UTexture2D*>& ULandscapeComponent::GetRenderedWeightmapTexturesForFeatureLevel(ERHIFeatureLevel::Type FeatureLevel) const
|
|
{
|
|
if (FeatureLevel == ERHIFeatureLevel::ES3_1)
|
|
{
|
|
return MobileWeightmapTextures;
|
|
}
|
|
else
|
|
{
|
|
return WeightmapTextures;
|
|
}
|
|
}
|
|
|
|
const TArray<FWeightmapLayerAllocationInfo>& ULandscapeComponent::GetWeightmapLayerAllocations(bool InReturnEditingWeightmap) const
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
if (InReturnEditingWeightmap)
|
|
{
|
|
if (const FLandscapeLayerComponentData* EditingLayer = GetEditingLayer())
|
|
{
|
|
return EditingLayer->WeightmapData.LayerAllocations;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return WeightmapLayerAllocations;
|
|
}
|
|
|
|
TArray<FWeightmapLayerAllocationInfo>& ULandscapeComponent::GetWeightmapLayerAllocations(const FGuid& InLayerGuid)
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
if (InLayerGuid.IsValid())
|
|
{
|
|
if (FLandscapeLayerComponentData* LayerData = GetLayerData(InLayerGuid))
|
|
{
|
|
return LayerData->WeightmapData.LayerAllocations;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return WeightmapLayerAllocations;
|
|
}
|
|
|
|
const TArray<FWeightmapLayerAllocationInfo>& ULandscapeComponent::GetWeightmapLayerAllocations(const FGuid& InLayerGuid) const
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
if (InLayerGuid.IsValid())
|
|
{
|
|
if (const FLandscapeLayerComponentData* LayerData = GetLayerData(InLayerGuid))
|
|
{
|
|
return LayerData->WeightmapData.LayerAllocations;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return WeightmapLayerAllocations;
|
|
}
|
|
|
|
TArray<FWeightmapLayerAllocationInfo>& ULandscapeComponent::GetWeightmapLayerAllocations(bool InReturnEditingWeightmap)
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
if (InReturnEditingWeightmap)
|
|
{
|
|
if (FLandscapeLayerComponentData* EditingLayer = GetEditingLayer())
|
|
{
|
|
return EditingLayer->WeightmapData.LayerAllocations;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return WeightmapLayerAllocations;
|
|
}
|
|
|
|
const TArray<FWeightmapLayerAllocationInfo>& ULandscapeComponent::GetCurrentRuntimeWeightmapLayerAllocations() const
|
|
{
|
|
|
|
bool bIsMobile = GetWorld()->GetFeatureLevel() == ERHIFeatureLevel::ES3_1;
|
|
return bIsMobile ? MobileWeightmapLayerAllocations : WeightmapLayerAllocations;
|
|
}
|
|
|
|
TArray<FWeightmapLayerAllocationInfo>& ULandscapeComponent::GetCurrentRuntimeWeightmapLayerAllocations()
|
|
{
|
|
bool bIsMobile = GetWorld()->GetFeatureLevel() == ERHIFeatureLevel::ES3_1;
|
|
return bIsMobile ? MobileWeightmapLayerAllocations : WeightmapLayerAllocations;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
|
|
FLandscapeLayerComponentData* ULandscapeComponent::GetEditingLayer()
|
|
{
|
|
if (ALandscape* LandscapeActor = GetLandscapeActor())
|
|
{
|
|
const FGuid& EditingLayerGuid = LandscapeActor->GetEditingLayer();
|
|
return EditingLayerGuid.IsValid() ? LayersData.Find(EditingLayerGuid) : nullptr;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const FLandscapeLayerComponentData* ULandscapeComponent::GetEditingLayer() const
|
|
{
|
|
if (ALandscape* LandscapeActor = GetLandscapeActor())
|
|
{
|
|
const FGuid& EditingLayerGuid = LandscapeActor->GetEditingLayer();
|
|
return EditingLayerGuid.IsValid() ? LayersData.Find(EditingLayerGuid) : nullptr;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void ULandscapeComponent::CopyFinalLayerIntoEditingLayer(FLandscapeEditDataInterface& DataInterface, TSet<UTexture2D*>& ProcessedHeightmaps)
|
|
{
|
|
Modify();
|
|
GetLandscapeProxy()->Modify();
|
|
|
|
// Heightmap
|
|
UTexture2D* EditingTexture = GetHeightmap(true);
|
|
if (!ProcessedHeightmaps.Contains(EditingTexture))
|
|
{
|
|
DataInterface.CopyTextureFromHeightmap(EditingTexture, this, 0);
|
|
ProcessedHeightmaps.Add(EditingTexture);
|
|
}
|
|
|
|
// Weightmap
|
|
const TArray<FWeightmapLayerAllocationInfo>& FinalWeightmapLayerAllocations = GetWeightmapLayerAllocations();
|
|
TArray<FWeightmapLayerAllocationInfo>& EditingLayerWeightmapLayerAllocations = GetWeightmapLayerAllocations(GetEditingLayerGUID());
|
|
|
|
// Add missing Alloc Infos
|
|
for (const FWeightmapLayerAllocationInfo& FinalAllocInfo : FinalWeightmapLayerAllocations)
|
|
{
|
|
int32 Index = EditingLayerWeightmapLayerAllocations.IndexOfByPredicate([&FinalAllocInfo](const FWeightmapLayerAllocationInfo& EditingAllocInfo) { return EditingAllocInfo.LayerInfo == FinalAllocInfo.LayerInfo; });
|
|
if (Index == INDEX_NONE)
|
|
{
|
|
new (EditingLayerWeightmapLayerAllocations) FWeightmapLayerAllocationInfo(FinalAllocInfo.LayerInfo);
|
|
}
|
|
}
|
|
|
|
ReallocateWeightmaps(&DataInterface, GetEditingLayerGUID(), /*bInSaveToTransactionBuffer = */true, /*bool bInForceReallocate = */false, /*InTargetProxy = */nullptr, /*InRestrictSharingToComponents = */nullptr);
|
|
|
|
const TArray<TObjectPtr<UTexture2D>>& EditingWeightmapTextures = GetWeightmapTextures(true);
|
|
for (const FWeightmapLayerAllocationInfo& AllocInfo : EditingLayerWeightmapLayerAllocations)
|
|
{
|
|
DataInterface.CopyTextureFromWeightmap(EditingWeightmapTextures[AllocInfo.WeightmapTextureIndex], AllocInfo.WeightmapTextureChannel, this, AllocInfo.LayerInfo, 0);
|
|
}
|
|
}
|
|
|
|
FGuid ULandscapeComponent::GetEditingLayerGUID() const
|
|
{
|
|
ALandscape* Landscape = GetLandscapeActor();
|
|
return Landscape != nullptr ? Landscape->GetEditingLayer() : FGuid();
|
|
}
|
|
|
|
bool ULandscapeComponent::HasLayersData() const
|
|
{
|
|
return LayersData.Num() > 0;
|
|
}
|
|
|
|
const FLandscapeLayerComponentData* ULandscapeComponent::GetLayerData(const FGuid& InLayerGuid) const
|
|
{
|
|
return LayersData.Find(InLayerGuid);
|
|
}
|
|
|
|
FLandscapeLayerComponentData* ULandscapeComponent::GetLayerData(const FGuid& InLayerGuid)
|
|
{
|
|
return LayersData.Find(InLayerGuid);
|
|
}
|
|
|
|
void ULandscapeComponent::ForEachLayer(TFunctionRef<void(const FGuid&, struct FLandscapeLayerComponentData&)> Fn)
|
|
{
|
|
for (auto& Pair : LayersData)
|
|
{
|
|
Fn(Pair.Key, Pair.Value);
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::AddLayerData(const FGuid& InLayerGuid, const FLandscapeLayerComponentData& InData)
|
|
{
|
|
Modify();
|
|
FLandscapeLayerComponentData& Data = LayersData.FindOrAdd(InLayerGuid);
|
|
Data = InData;
|
|
}
|
|
|
|
void ULandscapeComponent::AddDefaultLayerData(const FGuid& InLayerGuid, const TArray<ULandscapeComponent*>& InComponentsUsingHeightmap, TMap<UTexture2D*, UTexture2D*>& InOutCreatedHeightmapTextures)
|
|
{
|
|
Modify();
|
|
|
|
UTexture2D* ComponentHeightmap = GetHeightmap();
|
|
|
|
// Compute per layer data
|
|
FLandscapeLayerComponentData* LayerData = GetLayerData(InLayerGuid);
|
|
|
|
if (LayerData == nullptr || !LayerData->IsInitialized())
|
|
{
|
|
const ULandscapeEditLayerBase* LandscapeEditLayer = GetLandscapeActor() ? GetLandscapeActor()->GetEditLayerConst(InLayerGuid) : nullptr;
|
|
FLandscapeLayerComponentData NewData(LandscapeEditLayer ? LandscapeEditLayer->GetName() : FName());
|
|
|
|
// Setup Heightmap data
|
|
UTexture2D** LayerHeightmap = InOutCreatedHeightmapTextures.Find(ComponentHeightmap);
|
|
|
|
if (LayerHeightmap == nullptr)
|
|
{
|
|
// No mipchain required as these layer weight maps are used in layer compositing to generate a final set of weight maps to be used for rendering
|
|
UTexture2D* NewLayerHeightmap = GetLandscapeProxy()->CreateLandscapeTexture(ComponentHeightmap->Source.GetSizeX(), ComponentHeightmap->Source.GetSizeY(), TEXTUREGROUP_Terrain_Heightmap, ComponentHeightmap->Source.GetFormat(), /* OptionalOverrideOuter = */ nullptr, /* bCompress = */ false, /* bMipChain = */ false);
|
|
LayerHeightmap = &InOutCreatedHeightmapTextures.Add(ComponentHeightmap, NewLayerHeightmap);
|
|
|
|
// Init Mip0 to be at 32768 which is equal to "0"
|
|
TArrayView<FColor> Mip0Data((FColor*)NewLayerHeightmap->Source.LockMip(0), NewLayerHeightmap->Source.GetSizeX() * NewLayerHeightmap->Source.GetSizeY());
|
|
|
|
for (ULandscapeComponent* ComponentUsingHeightmap : InComponentsUsingHeightmap)
|
|
{
|
|
int32 HeightmapComponentOffsetX = FMath::RoundToInt32(NewLayerHeightmap->Source.GetSizeX() * ComponentUsingHeightmap->HeightmapScaleBias.Z);
|
|
int32 HeightmapComponentOffsetY = FMath::RoundToInt32(NewLayerHeightmap->Source.GetSizeY() * ComponentUsingHeightmap->HeightmapScaleBias.W);
|
|
|
|
for (int32 SubsectionY = 0; SubsectionY < NumSubsections; SubsectionY++)
|
|
{
|
|
for (int32 SubsectionX = 0; SubsectionX < NumSubsections; SubsectionX++)
|
|
{
|
|
for (int32 SubY = 0; SubY <= SubsectionSizeQuads; SubY++)
|
|
{
|
|
for (int32 SubX = 0; SubX <= SubsectionSizeQuads; SubX++)
|
|
{
|
|
// X/Y of the vertex we're looking at in component's coordinates.
|
|
const int32 CompX = SubsectionSizeQuads * SubsectionX + SubX;
|
|
const int32 CompY = SubsectionSizeQuads * SubsectionY + SubY;
|
|
|
|
// X/Y of the vertex we're looking indexed into the texture data
|
|
const int32 TexX = (SubsectionSizeQuads + 1) * SubsectionX + SubX;
|
|
const int32 TexY = (SubsectionSizeQuads + 1) * SubsectionY + SubY;
|
|
|
|
const int32 HeightTexDataIdx = (HeightmapComponentOffsetX + TexX) + (HeightmapComponentOffsetY + TexY) * NewLayerHeightmap->Source.GetSizeX();
|
|
|
|
// copy height and normal data
|
|
const uint16 HeightValue = LandscapeDataAccess::GetTexHeight(0.f);
|
|
|
|
Mip0Data[HeightTexDataIdx].R = HeightValue >> 8;
|
|
Mip0Data[HeightTexDataIdx].G = HeightValue & 255;
|
|
|
|
// Normal with get calculated later
|
|
Mip0Data[HeightTexDataIdx].B = 0;
|
|
Mip0Data[HeightTexDataIdx].A = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
NewLayerHeightmap->Source.UnlockMip(0);
|
|
ULandscapeTextureHash::UpdateHash(NewLayerHeightmap, ELandscapeTextureUsage::EditLayerData, ELandscapeTextureType::Heightmap);
|
|
NewLayerHeightmap->UpdateResource();
|
|
}
|
|
|
|
NewData.HeightmapData.Texture = *LayerHeightmap;
|
|
|
|
// Nothing to do for Weightmap by default
|
|
|
|
AddLayerData(InLayerGuid, MoveTemp(NewData));
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::RemoveLayerData(const FGuid& InLayerGuid)
|
|
{
|
|
Modify();
|
|
LayersData.Remove(InLayerGuid);
|
|
}
|
|
|
|
#endif // WITH_EDITOR
|
|
|
|
void ULandscapeComponent::SetHeightmap(UTexture2D* NewHeightmap)
|
|
{
|
|
check(NewHeightmap != nullptr);
|
|
HeightmapTexture = NewHeightmap;
|
|
}
|
|
|
|
void ULandscapeComponent::SetWeightmapTextures(const TArray<UTexture2D*>& InNewWeightmapTextures, bool InApplyToEditingWeightmap)
|
|
{
|
|
#if WITH_EDITOR
|
|
FLandscapeLayerComponentData* EditingLayer = GetEditingLayer();
|
|
|
|
if (InApplyToEditingWeightmap && EditingLayer != nullptr)
|
|
{
|
|
EditingLayer->WeightmapData.Textures.Reset(InNewWeightmapTextures.Num());
|
|
EditingLayer->WeightmapData.Textures.Append(InNewWeightmapTextures);
|
|
}
|
|
else
|
|
#endif // WITH_EDITOR
|
|
{
|
|
WeightmapTextures = InNewWeightmapTextures;
|
|
}
|
|
}
|
|
|
|
// Note that there is a slight difference in behavior with the Internal function:
|
|
// unlike SetWeightmapTextures, this function will never set the runtime WeightmapTextures when you intended to set an edit layer's WeightmapData.Textures
|
|
void ULandscapeComponent::SetWeightmapTexturesInternal(const TArray<UTexture2D*>& InNewWeightmapTextures, const FGuid& InEditLayerGuid)
|
|
{
|
|
if (InEditLayerGuid.IsValid())
|
|
{
|
|
#if WITH_EDITOR
|
|
FLandscapeLayerComponentData* EditingLayer = GetLayerData(InEditLayerGuid);
|
|
if (ensure(EditingLayer))
|
|
{
|
|
EditingLayer->WeightmapData.Textures.Reset(InNewWeightmapTextures.Num());
|
|
EditingLayer->WeightmapData.Textures.Append(InNewWeightmapTextures);
|
|
}
|
|
#endif // WITH_EDITOR
|
|
}
|
|
else
|
|
{
|
|
WeightmapTextures = InNewWeightmapTextures;
|
|
}
|
|
}
|
|
|
|
|
|
#if WITH_EDITOR
|
|
void ULandscapeComponent::SetWeightmapLayerAllocations(const TArray<FWeightmapLayerAllocationInfo>& InNewWeightmapLayerAllocations)
|
|
{
|
|
WeightmapLayerAllocations = InNewWeightmapLayerAllocations;
|
|
}
|
|
|
|
TArray<TObjectPtr<ULandscapeWeightmapUsage>>& ULandscapeComponent::GetWeightmapTexturesUsage(bool InReturnEditingWeightmap)
|
|
{
|
|
if (InReturnEditingWeightmap)
|
|
{
|
|
if (FLandscapeLayerComponentData* EditingLayer = GetEditingLayer())
|
|
{
|
|
return EditingLayer->WeightmapData.TextureUsages;
|
|
}
|
|
}
|
|
|
|
return WeightmapTexturesUsage;
|
|
}
|
|
|
|
const TArray<ULandscapeWeightmapUsage*>& ULandscapeComponent::GetWeightmapTexturesUsage(bool InReturnEditingWeightmap) const
|
|
{
|
|
if (InReturnEditingWeightmap)
|
|
{
|
|
if (const FLandscapeLayerComponentData* EditingLayer = GetEditingLayer())
|
|
{
|
|
return EditingLayer->WeightmapData.TextureUsages;
|
|
}
|
|
}
|
|
|
|
return WeightmapTexturesUsage;
|
|
}
|
|
|
|
TArray<TObjectPtr<ULandscapeWeightmapUsage>>& ULandscapeComponent::GetWeightmapTexturesUsage(const FGuid& InLayerGuid)
|
|
{
|
|
if (InLayerGuid.IsValid())
|
|
{
|
|
if (FLandscapeLayerComponentData* LayerData = GetLayerData(InLayerGuid))
|
|
{
|
|
return LayerData->WeightmapData.TextureUsages;
|
|
}
|
|
}
|
|
|
|
return WeightmapTexturesUsage;
|
|
}
|
|
|
|
const TArray<ULandscapeWeightmapUsage*>& ULandscapeComponent::GetWeightmapTexturesUsage(const FGuid& InLayerGuid) const
|
|
{
|
|
if (InLayerGuid.IsValid())
|
|
{
|
|
if (const FLandscapeLayerComponentData* LayerData = GetLayerData(InLayerGuid))
|
|
{
|
|
return LayerData->WeightmapData.TextureUsages;
|
|
}
|
|
}
|
|
|
|
return WeightmapTexturesUsage;
|
|
}
|
|
|
|
void ULandscapeComponent::SetWeightmapTexturesUsage(const TArray<ULandscapeWeightmapUsage*>& InNewWeightmapTexturesUsage, bool InApplyToEditingWeightmap)
|
|
{
|
|
FLandscapeLayerComponentData* EditingLayer = GetEditingLayer();
|
|
|
|
if (InApplyToEditingWeightmap && EditingLayer != nullptr)
|
|
{
|
|
EditingLayer->WeightmapData.TextureUsages.Reset(InNewWeightmapTexturesUsage.Num());
|
|
EditingLayer->WeightmapData.TextureUsages.Append(InNewWeightmapTexturesUsage);
|
|
}
|
|
else
|
|
{
|
|
WeightmapTexturesUsage = InNewWeightmapTexturesUsage;
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::SetWeightmapTexturesUsageInternal(const TArray<ULandscapeWeightmapUsage*>& InNewWeightmapTexturesUsage, const FGuid& InEditLayerGuid)
|
|
{
|
|
if (InEditLayerGuid.IsValid())
|
|
{
|
|
#if WITH_EDITOR
|
|
FLandscapeLayerComponentData* EditingLayer = GetLayerData(InEditLayerGuid);
|
|
if (ensure(EditingLayer))
|
|
{
|
|
EditingLayer->WeightmapData.TextureUsages.Reset(InNewWeightmapTexturesUsage.Num());
|
|
EditingLayer->WeightmapData.TextureUsages.Append(InNewWeightmapTexturesUsage);
|
|
}
|
|
#endif // WITH_EDITOR
|
|
}
|
|
else
|
|
{
|
|
WeightmapTexturesUsage = InNewWeightmapTexturesUsage;
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::DeleteLayerAllocation(const FGuid& InEditLayerGuid, int32 InLayerAllocationIdx, bool bInShouldDirtyPackage)
|
|
{
|
|
TArray<FWeightmapLayerAllocationInfo>& ComponentWeightmapLayerAllocations = GetWeightmapLayerAllocations(InEditLayerGuid);
|
|
TArray<TObjectPtr<UTexture2D>>& ComponentWeightmapTextures = GetWeightmapTextures(InEditLayerGuid);
|
|
TArray<TObjectPtr<ULandscapeWeightmapUsage>>& ComponentWeightmapTexturesUsage = GetWeightmapTexturesUsage(InEditLayerGuid);
|
|
|
|
const FWeightmapLayerAllocationInfo& LayerAllocation = ComponentWeightmapLayerAllocations[InLayerAllocationIdx];
|
|
const int32 DeleteLayerWeightmapTextureIndex = LayerAllocation.WeightmapTextureIndex;
|
|
|
|
ALandscapeProxy* Proxy = GetLandscapeProxy();
|
|
Modify(bInShouldDirtyPackage);
|
|
Proxy->Modify(bInShouldDirtyPackage);
|
|
|
|
// Mark the weightmap channel as unallocated, so we can reuse it later
|
|
ULandscapeWeightmapUsage* Usage = ComponentWeightmapTexturesUsage.IsValidIndex(DeleteLayerWeightmapTextureIndex) ? ComponentWeightmapTexturesUsage[DeleteLayerWeightmapTextureIndex] : nullptr;
|
|
if (Usage) // can be null if WeightmapUsageMap hasn't been built yet
|
|
{
|
|
Usage->ChannelUsage[LayerAllocation.WeightmapTextureChannel] = nullptr;
|
|
}
|
|
|
|
// Remove the layer:
|
|
ComponentWeightmapLayerAllocations.RemoveAt(InLayerAllocationIdx);
|
|
|
|
// Check if the weightmap texture used by the material layer we just removed is used by any other material layer -- if not then we can remove the texture from the local list (as it's not used locally)
|
|
bool bCanRemoveLayerTexture = !ComponentWeightmapLayerAllocations.ContainsByPredicate([DeleteLayerWeightmapTextureIndex](const FWeightmapLayerAllocationInfo& Allocation) { return Allocation.WeightmapTextureIndex == DeleteLayerWeightmapTextureIndex; });
|
|
if (bCanRemoveLayerTexture)
|
|
{
|
|
// Make sure the texture can be garbage collected, if necessary
|
|
ComponentWeightmapTextures[DeleteLayerWeightmapTextureIndex]->ClearFlags(RF_Standalone);
|
|
|
|
// Remove from our local list of textures and usages
|
|
ComponentWeightmapTextures.RemoveAt(DeleteLayerWeightmapTextureIndex);
|
|
if (Usage)
|
|
{
|
|
ComponentWeightmapTexturesUsage.RemoveAt(DeleteLayerWeightmapTextureIndex);
|
|
}
|
|
|
|
// Adjust WeightmapTextureIndex for other allocations (as we just reordered the Weightmap list with the deletions above)
|
|
for (FWeightmapLayerAllocationInfo& Allocation : ComponentWeightmapLayerAllocations)
|
|
{
|
|
if (Allocation.WeightmapTextureIndex > DeleteLayerWeightmapTextureIndex)
|
|
{
|
|
Allocation.WeightmapTextureIndex--;
|
|
}
|
|
check(Allocation.WeightmapTextureIndex < ComponentWeightmapTextures.Num());
|
|
}
|
|
}
|
|
|
|
Proxy->ValidateProxyLayersWeightmapUsage();
|
|
}
|
|
|
|
#endif // WITH_EDITOR
|
|
|
|
void ALandscapeProxy::PostRegisterAllComponents()
|
|
{
|
|
Super::PostRegisterAllComponents();
|
|
|
|
ULandscapeInfo* LandscapeInfo = nullptr;
|
|
if (!IsPendingKillPending())
|
|
{
|
|
// Duplicated or newly spawned Landscapes don't have a valid guid until PostEditImport is called, we'll register then
|
|
if (LandscapeGuid.IsValid())
|
|
{
|
|
LandscapeInfo = GetLandscapeInfo();
|
|
|
|
// Depending what action triggered this callback, we may have already registered. If not register now with LandscapeInfo.
|
|
if ((LandscapeInfo == nullptr) || !LandscapeInfo->IsRegistered(this))
|
|
{
|
|
LandscapeInfo = CreateLandscapeInfo(true);
|
|
}
|
|
}
|
|
|
|
if (UWorld* OwningWorld = GetWorld())
|
|
{
|
|
if (ULandscapeSubsystem* LandscapeSubsystem = OwningWorld->GetSubsystem<ULandscapeSubsystem>())
|
|
{
|
|
LandscapeSubsystem->RegisterActor(this);
|
|
}
|
|
}
|
|
|
|
UpdateRenderingMethod();
|
|
}
|
|
#if WITH_EDITOR
|
|
if ((LandscapeInfo != nullptr) && !IsPendingKillPending() && LandscapeGuid.IsValid())
|
|
{
|
|
LandscapeInfo->FixupProxiesTransform();
|
|
}
|
|
#endif // WITH_EDITOR
|
|
}
|
|
|
|
void ALandscapeProxy::UnregisterAllComponents(const bool bForReregister)
|
|
{
|
|
// On shutdown the world will be unreachable
|
|
if (GetWorld() && IsValidChecked(GetWorld()) && !GetWorld()->IsUnreachable() &&
|
|
// When redoing the creation of a landscape we may get UnregisterAllComponents called when
|
|
// we are in a "pre-initialized" state (empty guid, etc)
|
|
LandscapeGuid.IsValid())
|
|
{
|
|
ULandscapeInfo* LandscapeInfo = GetLandscapeInfo();
|
|
if (LandscapeInfo)
|
|
{
|
|
LandscapeInfo->UnregisterActor(this);
|
|
}
|
|
|
|
if (ULandscapeSubsystem* LandscapeSubsystem = GetWorld()->GetSubsystem<ULandscapeSubsystem>())
|
|
{
|
|
LandscapeSubsystem->UnregisterActor(this);
|
|
}
|
|
}
|
|
|
|
Super::UnregisterAllComponents(bForReregister);
|
|
}
|
|
|
|
|
|
FArchive& operator<<(FArchive& Ar, FWeightmapLayerAllocationInfo& U)
|
|
{
|
|
return Ar << U.LayerInfo << U.WeightmapTextureChannel << U.WeightmapTextureIndex;
|
|
}
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
FArchive& operator<<(FArchive& Ar, FLandscapeAddCollision& U)
|
|
{
|
|
return Ar << U.Corners[0] << U.Corners[1] << U.Corners[2] << U.Corners[3];
|
|
}
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
void ULandscapeInfo::Serialize(FArchive& Ar)
|
|
{
|
|
Super::Serialize(Ar);
|
|
|
|
if (Ar.IsTransacting())
|
|
{
|
|
Ar << XYtoComponentMap;
|
|
#if WITH_EDITORONLY_DATA
|
|
Ar << XYtoAddCollisionMap;
|
|
#endif
|
|
Ar << SelectedComponents;
|
|
Ar << SelectedRegion;
|
|
Ar << SelectedRegionComponents;
|
|
}
|
|
}
|
|
|
|
void ALandscape::PostLoad()
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(ALandscape::PostLoad);
|
|
|
|
if (!LandscapeGuid.IsValid())
|
|
{
|
|
LandscapeGuid = FGuid::NewGuid();
|
|
}
|
|
else
|
|
{
|
|
#if WITH_EDITOR
|
|
UWorld* CurrentWorld = GetWorld();
|
|
for (ALandscape* Landscape : TObjectRange<ALandscape>(RF_ClassDefaultObject | RF_BeginDestroyed))
|
|
{
|
|
if (Landscape && Landscape != this && Landscape->LandscapeGuid == LandscapeGuid && Landscape->GetWorld() == CurrentWorld)
|
|
{
|
|
// Duplicated landscape level, need to generate new GUID. This can happen during PIE or gameplay when streaming the same landscape actor.
|
|
Modify();
|
|
LandscapeGuid = FGuid::NewGuid();
|
|
break;
|
|
}
|
|
}
|
|
#endif // WITH_EDITOR
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (GetLinkerCustomVersion(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::IntroduceLandscapeEditLayerClass)
|
|
{
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
LandscapeEditLayers = LandscapeLayers_DEPRECATED;
|
|
|
|
for (FLandscapeLayer& Layer : LandscapeEditLayers)
|
|
{
|
|
UClass* EditLayerClass = ULandscapeEditLayer::StaticClass();
|
|
|
|
if (Layer.Guid_DEPRECATED == LandscapeSplinesTargetLayerGuid_DEPRECATED)
|
|
{
|
|
EditLayerClass = ULandscapeEditLayerSplines::StaticClass();
|
|
}
|
|
check(Layer.EditLayer == nullptr);
|
|
Layer.EditLayer = NewObject<ULandscapeEditLayerBase>(this, EditLayerClass, MakeUniqueObjectName(this, EditLayerClass), RF_Transactional);
|
|
}
|
|
|
|
// Empty the old property now that we've moved them over, else we'll accidentally keep references to brushes etc.
|
|
LandscapeLayers_DEPRECATED.Empty();
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
}
|
|
|
|
for (int32 LayerIndex = 0; LayerIndex < LandscapeEditLayers.Num(); ++LayerIndex)
|
|
{
|
|
FLandscapeLayer& Layer = LandscapeEditLayers[LayerIndex];
|
|
if (Layer.EditLayer != nullptr)
|
|
{
|
|
if (GetLinkerCustomVersion(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::MigrateLandscapeEditLayerProperties)
|
|
{
|
|
// Set Owning Landscape before other fields so Setter checks succeed
|
|
Layer.EditLayer->SetBackPointer(this);
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
Layer.EditLayer->SetFlags(RF_Transactional); // Bug fix, this flag was forgotten in the initial version
|
|
Layer.EditLayer->SetGuid(Layer.Guid_DEPRECATED, /*bInModify = */false);
|
|
Layer.EditLayer->SetName(Layer.Name_DEPRECATED, /*bInModify = */false);
|
|
Layer.EditLayer->SetVisible(Layer.bVisible_DEPRECATED, /*bInModify = */false);
|
|
Layer.EditLayer->SetLocked(Layer.bLocked_DEPRECATED, /*bInModify = */false);
|
|
Layer.EditLayer->SetAlphaForTargetType(ELandscapeToolTargetType::Heightmap, Layer.HeightmapAlpha_DEPRECATED, /*bInModify = */false, EPropertyChangeType::ValueSet);
|
|
Layer.EditLayer->SetAlphaForTargetType(ELandscapeToolTargetType::Weightmap, Layer.WeightmapAlpha_DEPRECATED, /*bInModify = */false, EPropertyChangeType::ValueSet);
|
|
Layer.EditLayer->SetWeightmapLayerAllocationBlend(Layer.WeightmapLayerAllocationBlend_DEPRECATED, /*bInModify = */false);
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
}
|
|
|
|
// Register to data change events on the edit layer so that we can update the landscape accordingly :
|
|
Layer.EditLayer->OnLayerDataChanged().AddUObject(this, &ALandscape::OnEditLayerDataChanged);
|
|
|
|
for (FLandscapeLayerBrush& Brush : Layer.Brushes)
|
|
{
|
|
Brush.SetOwner(this);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogLandscape, Error, TEXT("Couldn't load edit layer object associated with layer at index %i for landscape %s. This may happen when the edit layer class cannot be found "
|
|
"(for example, when a plugin is removed from the project). The layer will be deleted."),
|
|
LayerIndex, *GetFullName());
|
|
ensure(DeleteLayer(LayerIndex));
|
|
}
|
|
}
|
|
|
|
// In case we're a landscape with edit layers but we actually lack a layer (e.g. it was removed by the test above, because its edit layer class is unknown), let's create one all the same
|
|
// because we're always supposed to have at least 1 :
|
|
if (CanHaveLayersContent())
|
|
{
|
|
if (LandscapeEditLayers.IsEmpty())
|
|
{
|
|
// Create a default layer
|
|
CreateDefaultLayer();
|
|
ensure(!LandscapeEditLayers.IsEmpty());
|
|
}
|
|
|
|
// Ensure a valid edit layer is selected post load
|
|
if (!LandscapeEditLayers.IsValidIndex(SelectedEditLayerIndex))
|
|
{
|
|
SetSelectedEditLayerIndex(0);
|
|
}
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
Super::PostLoad();
|
|
}
|
|
|
|
FBox ALandscape::GetLoadedBounds() const
|
|
{
|
|
return GetLandscapeInfo()->GetLoadedBounds();
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------------
|
|
|
|
// This shader allows to render parts of the heightmaps/weightmaps (all pixels except the redundant ones on the right/bottom edges) in an atlas render target (uncompressed height for heightmaps)
|
|
class FLandscapeMergeTexturesPS : public FGlobalShader
|
|
{
|
|
DECLARE_GLOBAL_SHADER(FLandscapeMergeTexturesPS);
|
|
SHADER_USE_PARAMETER_STRUCT(FLandscapeMergeTexturesPS, FGlobalShader);
|
|
|
|
public:
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
|
|
SHADER_PARAMETER(FUintVector4, InAtlasSubregion)
|
|
SHADER_PARAMETER(FUintVector4, InSourceTextureSubregion)
|
|
SHADER_PARAMETER(int32, InSourceTextureChannel)
|
|
SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D<float4>, InSourceTexture)
|
|
RENDER_TARGET_BINDING_SLOTS()
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
class FIsHeightmap : SHADER_PERMUTATION_BOOL("IS_HEIGHTMAP");
|
|
|
|
using FPermutationDomain = TShaderPermutationDomain<FIsHeightmap>;
|
|
|
|
static FPermutationDomain GetPermutationVector(bool bInIsHeighmap)
|
|
{
|
|
FPermutationDomain PermutationVector;
|
|
PermutationVector.Set<FIsHeightmap>(bInIsHeighmap);
|
|
return PermutationVector;
|
|
}
|
|
|
|
|
|
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& InParameters)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& InParameters, FShaderCompilerEnvironment& OutEnvironment)
|
|
{
|
|
OutEnvironment.SetDefine(TEXT("MERGE_TEXTURE"), 1);
|
|
}
|
|
|
|
static void MergeTexture(FRDGBuilder& GraphBuilder, FParameters* InParameters, const FIntRect& InRenderTargetArea, bool bInIsHeightmap)
|
|
{
|
|
FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(GMaxRHIFeatureLevel);
|
|
|
|
const FLandscapeMergeTexturesPS::FPermutationDomain PixelPermutationVector = FLandscapeMergeTexturesPS::GetPermutationVector(bInIsHeightmap);
|
|
TShaderMapRef<FLandscapeMergeTexturesPS> PixelShader(ShaderMap, PixelPermutationVector);
|
|
|
|
FPixelShaderUtils::AddFullscreenPass(
|
|
GraphBuilder,
|
|
ShaderMap,
|
|
RDG_EVENT_NAME("LandscapeMergeTexture"),
|
|
PixelShader,
|
|
InParameters,
|
|
InRenderTargetArea);
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_GLOBAL_SHADER(FLandscapeMergeTexturesPS, "/Engine/Private/Landscape/LandscapeMergeTexturesPS.usf", "MergeTexture", SF_Pixel);
|
|
|
|
|
|
// ----------------------------------------------------------------------------------
|
|
|
|
// This shader allows to resample the heightmap/weightmap (bilinear interpolation) from a given atlas usually produced by FLandscapeMergeTexturesPS :
|
|
// For heightmap, the output can be either compressed or uncompressed depending on the render target format (8 bits/channel for the former, 16/32 bits/channel for the latter)
|
|
class FLandscapeResampleMergedTexturePS : public FGlobalShader
|
|
{
|
|
DECLARE_GLOBAL_SHADER(FLandscapeResampleMergedTexturePS);
|
|
SHADER_USE_PARAMETER_STRUCT(FLandscapeResampleMergedTexturePS, FGlobalShader);
|
|
|
|
public:
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
|
|
SHADER_PARAMETER(FMatrix44f, InOutputUVToMergedTextureUV)
|
|
SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D<float>, InMergedTexture)
|
|
SHADER_PARAMETER_SAMPLER(SamplerState, InMergedTextureSampler)
|
|
SHADER_PARAMETER(FUintVector2, InRenderAreaSize)
|
|
RENDER_TARGET_BINDING_SLOTS()
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
class FIsHeightmap : SHADER_PERMUTATION_BOOL("IS_HEIGHTMAP");
|
|
class FCompressHeight : SHADER_PERMUTATION_BOOL("COMPRESS_HEIGHT");
|
|
|
|
using FPermutationDomain = TShaderPermutationDomain<FIsHeightmap, FCompressHeight>;
|
|
|
|
static FPermutationDomain GetPermutationVector(bool bInIsHeighmap, bool bInCompressHeight)
|
|
{
|
|
FPermutationDomain PermutationVector;
|
|
PermutationVector.Set<FIsHeightmap>(bInIsHeighmap);
|
|
PermutationVector.Set<FCompressHeight>(bInCompressHeight);
|
|
return PermutationVector;
|
|
}
|
|
|
|
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& InParameters)
|
|
{
|
|
FPermutationDomain PermutationVector(InParameters.PermutationId);
|
|
bool bIsHeightmap = PermutationVector.Get<FIsHeightmap>();
|
|
bool bCompressHeight = PermutationVector.Get<FCompressHeight>();
|
|
// No need for heightmap compression for weightmaps
|
|
return (bIsHeightmap || !bCompressHeight);
|
|
}
|
|
|
|
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& InParameters, FShaderCompilerEnvironment& OutEnvironment)
|
|
{
|
|
OutEnvironment.SetDefine(TEXT("RESAMPLE_MERGED_TEXTURE"), 1);
|
|
}
|
|
|
|
static void ResampleMergedTexture(FRDGBuilder& GraphBuilder, FParameters* InParameters, bool bInIsHeightmap, bool bInCompressHeight)
|
|
{
|
|
FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(GMaxRHIFeatureLevel);
|
|
|
|
const FLandscapeResampleMergedTexturePS::FPermutationDomain PixelPermutationVector = FLandscapeResampleMergedTexturePS::GetPermutationVector(bInIsHeightmap, bInCompressHeight);
|
|
TShaderMapRef<FLandscapeResampleMergedTexturePS> PixelShader(ShaderMap, PixelPermutationVector);
|
|
|
|
FPixelShaderUtils::AddFullscreenPass(
|
|
GraphBuilder,
|
|
ShaderMap,
|
|
RDG_EVENT_NAME("ResampleMergedTexture"),
|
|
PixelShader,
|
|
InParameters,
|
|
FIntRect(0, 0, InParameters->InRenderAreaSize.X, InParameters->InRenderAreaSize.Y));
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_GLOBAL_SHADER(FLandscapeResampleMergedTexturePS, "/Engine/Private/Landscape/LandscapeMergeTexturesPS.usf", "ResampleMergedTexture", SF_Pixel);
|
|
|
|
|
|
// ----------------------------------------------------------------------------------
|
|
|
|
// This shader allows to pack up to 4 single-channel textures onto a single rgba one
|
|
class FLandscapePackRGBAChannelsPS : public FGlobalShader
|
|
{
|
|
DECLARE_GLOBAL_SHADER(FLandscapePackRGBAChannelsPS);
|
|
SHADER_USE_PARAMETER_STRUCT(FLandscapePackRGBAChannelsPS, FGlobalShader);
|
|
|
|
public:
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
|
|
SHADER_PARAMETER(int32, InNumChannels)
|
|
SHADER_PARAMETER_RDG_TEXTURE_SRV_ARRAY(Texture2D<float>, InSourceTextures, [4])
|
|
RENDER_TARGET_BINDING_SLOTS()
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& InParameters)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& InParameters, FShaderCompilerEnvironment& OutEnvironment)
|
|
{
|
|
OutEnvironment.SetDefine(TEXT("PACK_RGBA_CHANNELS"), 1);
|
|
}
|
|
|
|
static void PackRGBAChannels(FRDGBuilder& GraphBuilder, FParameters* InParameters, const FIntRect& InRenderTargetArea)
|
|
{
|
|
FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(GMaxRHIFeatureLevel);
|
|
|
|
TShaderMapRef<FLandscapePackRGBAChannelsPS> PixelShader(ShaderMap);
|
|
|
|
FPixelShaderUtils::AddFullscreenPass(
|
|
GraphBuilder,
|
|
ShaderMap,
|
|
RDG_EVENT_NAME("PackRGBAChannels"),
|
|
PixelShader,
|
|
InParameters,
|
|
InRenderTargetArea);
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_GLOBAL_SHADER(FLandscapePackRGBAChannelsPS, "/Engine/Private/Landscape/LandscapeMergeTexturesPS.usf", "PackRGBAChannels", SF_Pixel);
|
|
|
|
// Render-thread version of the data / functions we need for the local merge of edit layers :
|
|
namespace UE::Landscape::Private::RenderMergedTexture_RenderThread
|
|
{
|
|
struct FRenderInfo
|
|
{
|
|
// Transform to go from the output render area space ((0,0) in the lower left corner, (1,1) in the upper-right) to the temporary render target space
|
|
FMatrix OutputUVToMergedTextureUV;
|
|
FIntPoint SubsectionSizeQuads;
|
|
int32 NumSubsections = 1;
|
|
bool bIsHeightmap = true;
|
|
bool bCompressHeight = false;
|
|
FName TargetLayerName;
|
|
|
|
TMap<FIntPoint, FTexture2DResourceSubregion> ComponentTexturesToRender;
|
|
};
|
|
|
|
void RenderMergedTexture(const FRenderInfo& InRenderInfo, FRDGBuilder& GraphBuilder, const FRenderTargetBinding& InOutputRenderTargetBinding)
|
|
{
|
|
RDG_EVENT_SCOPE(GraphBuilder, "RenderMergedTexture %s", *InRenderInfo.TargetLayerName.ToString());
|
|
|
|
// Find the total area that those components need to be rendered to :
|
|
FIntRect ComponentKeyRect;
|
|
for (auto Iter = InRenderInfo.ComponentTexturesToRender.CreateConstIterator(); Iter; ++Iter)
|
|
{
|
|
ComponentKeyRect.Include(Iter.Key());
|
|
}
|
|
|
|
ComponentKeyRect.Max += FIntPoint(1, 1);
|
|
FIntPoint NumComponentsToRender(ComponentKeyRect.Width(), ComponentKeyRect.Height());
|
|
FIntPoint NumSubsectionsToRender = NumComponentsToRender * InRenderInfo.NumSubsections;
|
|
FIntPoint RenderTargetSize = NumSubsectionsToRender * InRenderInfo.SubsectionSizeQuads + 1; // add one for the end vertex
|
|
FIntPoint ComponentSizeQuads = InRenderInfo.SubsectionSizeQuads * InRenderInfo.NumSubsections;
|
|
|
|
// We need a temporary render target that can contain all textures.
|
|
// For heightmaps, use PF_G16 (decoded height) as this will be resampled using bilinear sampling :
|
|
EPixelFormat AtlasTextureFormat = InRenderInfo.bIsHeightmap ? PF_G16 : PF_G8;
|
|
FRDGTextureDesc Desc = FRDGTextureDesc::Create2D(RenderTargetSize, AtlasTextureFormat, FClearValueBinding::Black, TexCreate_RenderTargetable | TexCreate_ShaderResource);
|
|
FRDGTextureRef AtlasTexture = GraphBuilder.CreateTexture(Desc, TEXT("LandscapeMergedTextureAtlas"));
|
|
FRDGTextureSRVRef AtlasTextureSRV = GraphBuilder.CreateSRV(FRDGTextureSRVDesc::Create(AtlasTexture));
|
|
// Start with a cleared atlas :
|
|
FRenderTargetBinding AtlasTextureRT(AtlasTexture, ERenderTargetLoadAction::ENoAction);
|
|
FRDGTextureClearInfo ClearInfo;
|
|
AddClearRenderTargetPass(GraphBuilder, AtlasTexture, ClearInfo);
|
|
|
|
TMap<FTexture2DResource*, FRDGTextureSRVRef> SourceTextureSRVs;
|
|
|
|
// Fill that render target subsection by subsection, in order to bypass the redundant columns/lines on the subsection edges:
|
|
for (int32 ComponentY = ComponentKeyRect.Min.Y; ComponentY < ComponentKeyRect.Max.Y; ++ComponentY)
|
|
{
|
|
for (int32 ComponentX = ComponentKeyRect.Min.X; ComponentX < ComponentKeyRect.Max.X; ++ComponentX)
|
|
{
|
|
FIntPoint LandscapeComponentKey(ComponentX, ComponentY);
|
|
if (const FTexture2DResourceSubregion* SourceTextureResourceSubregion = InRenderInfo.ComponentTexturesToRender.Find(LandscapeComponentKey))
|
|
{
|
|
FIntPoint SubsectionSubregionSize = SourceTextureResourceSubregion->Subregion.Size() / InRenderInfo.NumSubsections;
|
|
FRDGTextureSRVRef* SourceTextureSRV = SourceTextureSRVs.Find(SourceTextureResourceSubregion->Texture);
|
|
if (SourceTextureSRV == nullptr)
|
|
{
|
|
FString* DebugString = GraphBuilder.AllocObject<FString>(SourceTextureResourceSubregion->Texture->GetTextureName().ToString());
|
|
FRDGTextureRef TextureRef = GraphBuilder.RegisterExternalTexture(CreateRenderTarget(SourceTextureResourceSubregion->Texture->TextureRHI, **DebugString));
|
|
SourceTextureSRV = &SourceTextureSRVs.Add(SourceTextureResourceSubregion->Texture, GraphBuilder.CreateSRV(FRDGTextureSRVDesc::Create(TextureRef)));
|
|
}
|
|
|
|
for (int32 SubsectionY = 0; SubsectionY < InRenderInfo.NumSubsections; ++SubsectionY)
|
|
{
|
|
for (int32 SubsectionX = 0; SubsectionX < InRenderInfo.NumSubsections; ++SubsectionX)
|
|
{
|
|
FIntPoint SubsectionLocalKey(SubsectionX, SubsectionY);
|
|
FIntPoint SubsectionKey = LandscapeComponentKey * InRenderInfo.NumSubsections + SubsectionLocalKey;
|
|
|
|
FIntRect AtlasTextureSubregion;
|
|
AtlasTextureSubregion.Min = SubsectionKey * InRenderInfo.SubsectionSizeQuads;
|
|
// We only really need the +1 on the very last subsection to get the last row/column, since we end up overwriting the other end
|
|
// rows/columns when we proceed to the next tile. However it's much easier to add the +1 here and do a small amount of duplicate
|
|
// writes, because otherwise we would have to adjust SubsectionSubregion to align with the region we're writing, which would get
|
|
// messy in cases of different mip levels.
|
|
AtlasTextureSubregion.Max = AtlasTextureSubregion.Min + InRenderInfo.SubsectionSizeQuads + 1;
|
|
|
|
FIntRect SubsectionSubregion;
|
|
SubsectionSubregion.Min = SourceTextureResourceSubregion->Subregion.Min + SubsectionLocalKey * SubsectionSubregionSize;
|
|
SubsectionSubregion.Max = SubsectionSubregion.Min + SubsectionSubregionSize;
|
|
|
|
FLandscapeMergeTexturesPS::FParameters* MergeTexturesPSParams = GraphBuilder.AllocParameters<FLandscapeMergeTexturesPS::FParameters>();
|
|
MergeTexturesPSParams->InAtlasSubregion = FUintVector4(AtlasTextureSubregion.Min.X, AtlasTextureSubregion.Min.Y, AtlasTextureSubregion.Max.X, AtlasTextureSubregion.Max.Y);
|
|
MergeTexturesPSParams->InSourceTexture = *SourceTextureSRV;
|
|
MergeTexturesPSParams->InSourceTextureSubregion = FUintVector4(SubsectionSubregion.Min.X, SubsectionSubregion.Min.Y, SubsectionSubregion.Max.X, SubsectionSubregion.Max.Y);
|
|
check(InRenderInfo.bIsHeightmap || ((SourceTextureResourceSubregion->ChannelIndex >= 0) && (SourceTextureResourceSubregion->ChannelIndex < 4)));
|
|
MergeTexturesPSParams->InSourceTextureChannel = SourceTextureResourceSubregion->ChannelIndex;
|
|
MergeTexturesPSParams->RenderTargets[0] = AtlasTextureRT;
|
|
|
|
FLandscapeMergeTexturesPS::MergeTexture(GraphBuilder, MergeTexturesPSParams, AtlasTextureSubregion, InRenderInfo.bIsHeightmap);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
FRDGTexture* OutputTexture = InOutputRenderTargetBinding.GetTexture();
|
|
check(OutputTexture != nullptr);
|
|
FIntVector RenderAreaSize = OutputTexture->Desc.GetSize();
|
|
FLandscapeResampleMergedTexturePS::FParameters* ResampleMergedTexturePSParams = GraphBuilder.AllocParameters<FLandscapeResampleMergedTexturePS::FParameters>();
|
|
ResampleMergedTexturePSParams->InOutputUVToMergedTextureUV = FMatrix44f(InRenderInfo.OutputUVToMergedTextureUV);
|
|
ResampleMergedTexturePSParams->InMergedTexture = AtlasTextureSRV;
|
|
ResampleMergedTexturePSParams->InMergedTextureSampler = TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
|
|
ResampleMergedTexturePSParams->InRenderAreaSize = FUintVector2((uint32)RenderAreaSize.X, (uint32)RenderAreaSize.Y);
|
|
ResampleMergedTexturePSParams->RenderTargets[0] = InOutputRenderTargetBinding;
|
|
|
|
// We now need to resample the atlas texture where the render area is :
|
|
FLandscapeResampleMergedTexturePS::ResampleMergedTexture(GraphBuilder, ResampleMergedTexturePSParams, InRenderInfo.bIsHeightmap, InRenderInfo.bCompressHeight);
|
|
}
|
|
}
|
|
} // namespace UE::Landscape::Private::RenderMergedTexture_RenderThread
|
|
|
|
bool ALandscape::IsValidRenderTargetFormatHeightmap(EPixelFormat InRenderTargetFormat, bool& bOutCompressHeight)
|
|
{
|
|
bOutCompressHeight = false;
|
|
switch (InRenderTargetFormat)
|
|
{
|
|
// 8 bits formats : need compression
|
|
case PF_A8R8G8B8:
|
|
case PF_R8G8B8A8:
|
|
case PF_R8G8:
|
|
case PF_B8G8R8A8:
|
|
{
|
|
bOutCompressHeight = true;
|
|
return true;
|
|
}
|
|
// 16 bits formats :
|
|
case PF_G16:
|
|
// We don't use 16 bit float formats because they will have precision issues
|
|
// (we need 16 bits of mantissa)
|
|
|
|
// TODO: We can support 32 bit floating point formats, but for these, we probably
|
|
// want to output the height as an unpacked, signed values. We'll add support for
|
|
// that in a later CL.
|
|
//case PF_R32_FLOAT:
|
|
//case PF_G32R32F:
|
|
//case PF_R32G32B32F:
|
|
//case PF_A32B32G32R32F:
|
|
{
|
|
return true;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ALandscape::IsValidRenderTargetFormatWeightmap(EPixelFormat InRenderTargetFormat, int32& OutNumChannels)
|
|
{
|
|
OutNumChannels = 0;
|
|
switch (InRenderTargetFormat)
|
|
{
|
|
// TODO [jonathan.bard] : for now, we only support 8 bits formats as they're the weightmap format but possibly we could handle the conversion to other formats
|
|
case PF_G8:
|
|
case PF_A8:
|
|
case PF_R8G8:
|
|
case PF_A8R8G8B8:
|
|
case PF_R8G8B8A8:
|
|
case PF_B8G8R8A8:
|
|
{
|
|
OutNumChannels = GPixelFormats[InRenderTargetFormat].NumComponents;
|
|
return true;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ALandscape::RenderMergedTextureInternal(const FTransform& InRenderAreaWorldTransform, const FBox2D& InRenderAreaExtents, const TArray<FName>& InWeightmapLayerNames, UTextureRenderTarget* OutRenderTarget)
|
|
{
|
|
// TODO: We may want a version of this function that returns a lambda that can be passed to the render thread and run
|
|
// there to add the pass to an existing FRDGBuilder, in case the user wants this to be a part of a render graph with
|
|
// other passes. In that case RenderMergedTextureInternal would just use that function.
|
|
|
|
using namespace UE::Landscape;
|
|
using namespace UE::Landscape::Private;
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Landscape_RenderMergedTextureInternal);
|
|
|
|
ULandscapeInfo* Info = GetLandscapeInfo();
|
|
if (Info == nullptr)
|
|
{
|
|
UE_LOG(LogLandscape, Error, TEXT("RenderMergedTexture : Cannot render anything if there's no associated landscape info with this landscape (%s)"), *GetFullName());
|
|
return false;
|
|
}
|
|
|
|
// Check render target validity :
|
|
if (OutRenderTarget == nullptr)
|
|
{
|
|
UE_LOG(LogLandscape, Error, TEXT("RenderMergedTexture : Missing render target"));
|
|
return false;
|
|
}
|
|
|
|
// Check Render target format :
|
|
const bool bIsHeightmap = InWeightmapLayerNames.IsEmpty();
|
|
bool bCompressHeight = false;
|
|
UTextureRenderTarget2D* RenderTarget2D = Cast<UTextureRenderTarget2D>(OutRenderTarget);
|
|
UTextureRenderTarget2DArray* RenderTarget2DArray = Cast<UTextureRenderTarget2DArray>(OutRenderTarget);
|
|
EPixelFormat RenderTargetFormat = (RenderTarget2DArray != nullptr) ? RenderTarget2DArray->GetFormat() : (RenderTarget2D != nullptr) ? RenderTarget2D->GetFormat() : PF_Unknown;
|
|
if (bIsHeightmap)
|
|
{
|
|
if (RenderTarget2D == nullptr)
|
|
{
|
|
UE_LOG(LogLandscape, Error, TEXT("RenderMergedTexture : Heightmap capture requires a UTextureRenderTarget2D"));
|
|
return false;
|
|
}
|
|
|
|
if (!IsValidRenderTargetFormatHeightmap(RenderTargetFormat, bCompressHeight))
|
|
{
|
|
UE_LOG(LogLandscape, Warning, TEXT("RenderMergedTexture : invalid render target format for rendering heightmap (%s)"), GetPixelFormatString(RenderTargetFormat));
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If more than 1 weightmaps are requested, we expected a texture array or at the very least a texture 2D with enough channels to fit all weightmaps :
|
|
int32 NumChannels = 0;
|
|
if (!IsValidRenderTargetFormatWeightmap(RenderTargetFormat, NumChannels))
|
|
{
|
|
UE_LOG(LogLandscape, Warning, TEXT("RenderMergedTexture : invalid render target format for rendering weightmap (%s)"), GetPixelFormatString(RenderTargetFormat));
|
|
return false;
|
|
}
|
|
|
|
if (InWeightmapLayerNames.Num() > 1)
|
|
{
|
|
if ((RenderTarget2D != nullptr) && (NumChannels < InWeightmapLayerNames.Num()))
|
|
{
|
|
UE_LOG(LogLandscape, Warning, TEXT("RenderMergedTexture : Not enough channels available (%i) in render target to accomodate for all requested weightmaps (%i)"), NumChannels, InWeightmapLayerNames.Num());
|
|
return false;
|
|
}
|
|
else if ((RenderTarget2DArray != nullptr) && ((NumChannels * RenderTarget2DArray->Slices) < InWeightmapLayerNames.Num()))
|
|
{
|
|
UE_LOG(LogLandscape, Warning, TEXT("RenderMergedTexture : Not enough channels available (%i) in render target array to accomodate for all requested weightmaps (%i)"), NumChannels * RenderTarget2DArray->Slices, InWeightmapLayerNames.Num());
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the requested extents are invalid, use the entire loaded landscape are as extents and transform :
|
|
const FTransform& LandscapeTransform = GetTransform();
|
|
FTransform FinalRenderAreaWorldTransform = InRenderAreaWorldTransform;
|
|
FBox2D FinalRenderAreaExtents = InRenderAreaExtents;
|
|
if (!InRenderAreaExtents.bIsValid || InRenderAreaExtents.GetExtent().IsZero())
|
|
{
|
|
FinalRenderAreaWorldTransform = LandscapeTransform;
|
|
FBox LoadedBounds = Info->GetLoadedBounds();
|
|
FinalRenderAreaExtents = FBox2D(FVector2D(LandscapeTransform.InverseTransformPosition(LoadedBounds.Min)), FVector2D(LandscapeTransform.InverseTransformPosition(LoadedBounds.Max)));
|
|
}
|
|
|
|
// It can be helpful to visualize where the render happened so leave a visual log for that:
|
|
UE_VLOG_OBOX(this, LogLandscape, Log, FBox(FVector(FinalRenderAreaExtents.Min, 0.0), FVector(FinalRenderAreaExtents.Max, 0.0)), FinalRenderAreaWorldTransform.ToMatrixWithScale(), FColor::Blue, TEXT("LandscapeRenderMergedTexture"));
|
|
|
|
// Don't do anything if this render area overlaps with no landscape component :
|
|
TMap<FIntPoint, ULandscapeComponent*> OverlappedComponents;
|
|
FIntRect OverlappedComponentIndicesBoundingRect;
|
|
if (!Info->GetOverlappedComponents(FinalRenderAreaWorldTransform, FinalRenderAreaExtents, OverlappedComponents, OverlappedComponentIndicesBoundingRect))
|
|
{
|
|
UE_LOG(LogLandscape, Log, TEXT("RenderMergedTexture : nothing to render"));
|
|
return true;
|
|
}
|
|
|
|
RenderCaptureInterface::FScopedCapture RenderCapture((RenderCaptureNextMergeRenders != 0), TEXT("LandscapeRenderMergedTextureCapture"));
|
|
RenderCaptureNextMergeRenders = FMath::Max(RenderCaptureNextMergeRenders - 1, 0);
|
|
|
|
|
|
// We'll want to perform one merge per target layer (i.e. as many as there are weightmaps, or just 1 in the case of heightmap) :
|
|
const int32 NumTargetLayers = bIsHeightmap ? 1 : InWeightmapLayerNames.Num();
|
|
|
|
TArray<RenderMergedTexture_RenderThread::FRenderInfo> MergeTextureRenderInfos;
|
|
MergeTextureRenderInfos.Reserve(NumTargetLayers);
|
|
|
|
for (int32 TargetLayerIndex = 0; TargetLayerIndex < NumTargetLayers; ++TargetLayerIndex)
|
|
{
|
|
const FName TargetLayerName = bIsHeightmap ? FName(TEXT("Heightmap")) : InWeightmapLayerNames[TargetLayerIndex];
|
|
|
|
RenderMergedTexture_RenderThread::FRenderInfo& MergeTextureRenderInfo = MergeTextureRenderInfos.Emplace_GetRef();
|
|
// For now, merge the texture at max resolution :
|
|
MergeTextureRenderInfo.SubsectionSizeQuads = SubsectionSizeQuads;
|
|
MergeTextureRenderInfo.NumSubsections = NumSubsections;
|
|
MergeTextureRenderInfo.bIsHeightmap = bIsHeightmap;
|
|
MergeTextureRenderInfo.bCompressHeight = bCompressHeight;
|
|
MergeTextureRenderInfo.TargetLayerName = TargetLayerName;
|
|
|
|
// Indices of the components being rendered by this target layer :
|
|
FIntRect RenderTargetComponentIndicesBoundingRect;
|
|
|
|
for (auto It : OverlappedComponents)
|
|
{
|
|
ULandscapeComponent* Component = It.Value;
|
|
FIntPoint ComponentKey = It.Key;
|
|
|
|
UTexture2D* SourceTexture = nullptr;
|
|
FVector2D SourceTextureBias = FVector2D::ZeroVector;
|
|
int32 SourceTextureChannel = INDEX_NONE;
|
|
|
|
if (bIsHeightmap)
|
|
{
|
|
SourceTexture = Component->GetHeightmap();
|
|
SourceTextureBias = FVector2D(Component->HeightmapScaleBias.Z, Component->HeightmapScaleBias.W);
|
|
}
|
|
else
|
|
{
|
|
const TArray<UTexture2D*>& WeightmapTextures = Component->GetWeightmapTextures();
|
|
const TArray<FWeightmapLayerAllocationInfo>& AllocInfos = Component->GetWeightmapLayerAllocations();
|
|
const FWeightmapLayerAllocationInfo* AllocInfo = AllocInfos.FindByPredicate([TargetLayerName](const FWeightmapLayerAllocationInfo& InAllocInfo) { return InAllocInfo.IsAllocated() && (InAllocInfo.GetLayerName() == TargetLayerName); });
|
|
if (AllocInfo != nullptr)
|
|
{
|
|
SourceTexture = WeightmapTextures[AllocInfo->WeightmapTextureIndex];
|
|
check(SourceTexture != nullptr);
|
|
// Note : don't use WeightmapScaleBias here, it has a different meaning than HeightmapScaleBias (very conveniently!) : this is compensated by the FloorToInt32 later on,
|
|
// but still, let's set this to zero here and use the fact that there's no texture sharing on weightmaps :
|
|
SourceTextureBias = FVector2D::ZeroVector;
|
|
SourceTextureChannel = AllocInfo->WeightmapTextureChannel;
|
|
}
|
|
}
|
|
|
|
if (SourceTexture != nullptr)
|
|
{
|
|
// Get the subregion of the source texture that this component uses (differs due to texture sharing).
|
|
// SourceTextureBias values give us the offset of the component in a shared texture
|
|
int32 ComponentSize = Component->NumSubsections * (Component->SubsectionSizeQuads + 1);
|
|
|
|
FIntPoint SourceTextureOffset(0, 0);
|
|
FTextureResource* SourceTextureResource = SourceTexture->GetResource();
|
|
if (ensure(SourceTextureResource))
|
|
{
|
|
// We get the overall source texture size via the resource instead of direct GetSizeX/Y calls because the latter are unreliable while the texture is being built.
|
|
SourceTextureOffset = FIntPoint(
|
|
FMath::FloorToInt32(SourceTextureBias.X * SourceTextureResource->GetSizeX()),
|
|
FMath::FloorToInt32(SourceTextureBias.Y * SourceTextureResource->GetSizeY()));
|
|
}
|
|
|
|
// When mips are partially loaded, we need to take that into consideration when merging the source texture :
|
|
uint32 MipBias = SourceTexture->GetNumMips() - SourceTexture->GetNumResidentMips();
|
|
|
|
// Theoretically speaking, all of our component source textures should be powers of two when we include the duplicated
|
|
// rows/columns across subsections, so we shouldn't get weird truncation results here...
|
|
SourceTextureOffset.X >>= MipBias;
|
|
SourceTextureOffset.Y >>= MipBias;
|
|
ComponentSize >>= MipBias;
|
|
|
|
// Effective area of the texture affecting this component (because of texture sharing) :
|
|
FIntRect SourceTextureSubregion(SourceTextureOffset, SourceTextureOffset + ComponentSize);
|
|
MergeTextureRenderInfo.ComponentTexturesToRender.Add(ComponentKey, FTexture2DResourceSubregion(SourceTextureResource->GetTexture2DResource(), SourceTextureSubregion, SourceTextureChannel));
|
|
|
|
// Since this component will be rendered in the render target, we can now expand the render target's bounds :
|
|
RenderTargetComponentIndicesBoundingRect.Union(FIntRect(ComponentKey, ComponentKey + FIntPoint(1)));
|
|
}
|
|
}
|
|
|
|
// Create the transform that will go from output target UVs to world space:
|
|
FVector OutputUVOrigin = FinalRenderAreaWorldTransform.TransformPosition(FVector(FinalRenderAreaExtents.Min.X, FinalRenderAreaExtents.Min.Y, 0.0));
|
|
FVector OutputUVScale = FinalRenderAreaWorldTransform.GetScale3D() * FVector(FinalRenderAreaExtents.GetSize(), 1.0);
|
|
FTransform OutputUVToWorld(FinalRenderAreaWorldTransform.GetRotation(), OutputUVOrigin, OutputUVScale);
|
|
|
|
// Create the transform that will go from the merged texture (atlas) UVs to world space. Note that this is slightly trickier because
|
|
// vertices in the landscape correspond to pixel centers. So UV (0,0) is not at the minimal landscape vertex, but is instead
|
|
// half a quad further (one pixel is one quad in size, so the center of the first pixel ends up at the minimal vertex).
|
|
// For related reasons, the size of the merged texture in world coordinates is actually one quad bigger in each direction.
|
|
check(RenderTargetComponentIndicesBoundingRect.IsEmpty()
|
|
|| (RenderTargetComponentIndicesBoundingRect.Min.X < RenderTargetComponentIndicesBoundingRect.Max.X) && (RenderTargetComponentIndicesBoundingRect.Min.Y < RenderTargetComponentIndicesBoundingRect.Max.Y));
|
|
FVector MergedTextureScale = (FVector(RenderTargetComponentIndicesBoundingRect.Max - RenderTargetComponentIndicesBoundingRect.Min) * static_cast<double>(ComponentSizeQuads) + 1)
|
|
* LandscapeTransform.GetScale3D();
|
|
MergedTextureScale.Z = 1.0f;
|
|
FVector MergedTextureUVOrigin = LandscapeTransform.TransformPosition(FVector(RenderTargetComponentIndicesBoundingRect.Min) * (double)ComponentSizeQuads - FVector(0.5, 0.5, 0));
|
|
FTransform MergedTextureUVToWorld(LandscapeTransform.GetRotation(), MergedTextureUVOrigin, MergedTextureScale);
|
|
|
|
MergeTextureRenderInfo.OutputUVToMergedTextureUV = OutputUVToWorld.ToMatrixWithScale() * MergedTextureUVToWorld.ToInverseMatrixWithScale();
|
|
}
|
|
|
|
// Extract the render thread version of the output render target :
|
|
FTextureRenderTargetResource* OutputRenderTargetResource = OutRenderTarget->GameThread_GetRenderTargetResource();
|
|
check(OutputRenderTargetResource != nullptr);
|
|
|
|
ENQUEUE_RENDER_COMMAND(RenderMergedTexture)([MergeTextureRenderInfos, OutputRenderTargetResource, bIsHeightmap, RenderTargetFormat](FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
FRDGBuilder GraphBuilder(RHICmdList, RDG_EVENT_NAME("RenderMergedTexture"));
|
|
|
|
FTextureRenderTarget2DResource* OutputRenderTarget2DResource = OutputRenderTargetResource->GetTextureRenderTarget2DResource();
|
|
FTextureRenderTarget2DArrayResource* OutputRenderTarget2DArrayResource = OutputRenderTargetResource->GetTextureRenderTarget2DArrayResource();
|
|
check((OutputRenderTarget2DResource != nullptr) || (OutputRenderTarget2DArrayResource != nullptr)); // either a render target 2D array or a render target 2D
|
|
|
|
FRDGTextureRef OutputTexture = GraphBuilder.RegisterExternalTexture(CreateRenderTarget(OutputRenderTargetResource->GetTextureRHI(), TEXT("MergedTexture")));
|
|
|
|
// If we perform a single merge, we can simply render to the final texture :
|
|
const int32 NumTargetLayers = MergeTextureRenderInfos.Num();
|
|
if (NumTargetLayers == 1)
|
|
{
|
|
// If it's a texture array, we need to specify the slice index
|
|
const int16 ArraySlice = (OutputRenderTarget2DArrayResource != nullptr) ? 0 : -1;
|
|
FRenderTargetBinding RenderTargetBinding(OutputTexture, ERenderTargetLoadAction::ENoAction, /*InMipIndex = */0, ArraySlice);
|
|
RenderMergedTexture_RenderThread::RenderMergedTexture(MergeTextureRenderInfos[0], GraphBuilder, RenderTargetBinding);
|
|
}
|
|
// In the case of multiple target layers, we'll render them one by one and pack them on the available output channels :
|
|
else
|
|
{
|
|
const int32 NumChannels = GPixelFormats[RenderTargetFormat].NumComponents;
|
|
const int32 NumChannelPackingOperations = FMath::DivideAndRoundUp<int32>(NumTargetLayers, NumChannels);
|
|
check(NumChannelPackingOperations > 0);
|
|
checkf((OutputRenderTarget2DArrayResource != nullptr) || (NumTargetLayers <= NumChannels), TEXT("Trying to merge %i weightmaps onto a 2D texture of %i channels only"), NumTargetLayers, NumChannels);
|
|
checkf(!bIsHeightmap, TEXT("We should only be able to merge multiple textures in the case of weightmaps"));
|
|
FRDGTextureSRVRef DummyBlackTextureSRV = GraphBuilder.CreateSRV(FRDGTextureSRVDesc::Create(GSystemTextures.GetBlackDummy(GraphBuilder)));
|
|
|
|
// We'll need temporary 1 channel-texture for each weightmap that will then be packed onto the needed channels. This is for weightmaps only so PF_G8 pixel format is what we need for
|
|
FIntPoint OutputTextureSize(OutputTexture->Desc.GetSize().X, OutputTexture->Desc.GetSize().Y);
|
|
FRDGTextureDesc SingleChannelTextureDesc = FRDGTextureDesc::Create2D(OutputTextureSize, PF_G8, FClearValueBinding::Black, TexCreate_RenderTargetable | TexCreate_ShaderResource);
|
|
int32 TargetLayerIndex = 0;
|
|
const int32 NumSlices = (OutputRenderTarget2DArrayResource != nullptr) ? OutputTexture->Desc.ArraySize : 1;
|
|
for (int32 SliceIndex = 0; SliceIndex < NumSlices; ++SliceIndex)
|
|
{
|
|
RDG_EVENT_SCOPE(GraphBuilder, "RenderMergedTexture Slice %d", SliceIndex);
|
|
|
|
// The last slice might have to render less than the actual number of channels of the texture :
|
|
const int32 NumEffectiveChannels = FMath::Min(NumChannels, (NumTargetLayers - SliceIndex * NumChannels));
|
|
check((NumEffectiveChannels >= 0) && (NumEffectiveChannels <= NumChannels));
|
|
TArray<FRDGTextureRef, TInlineAllocator<4>> SingleChannelTextures;
|
|
|
|
// First, render the each channel independently :
|
|
for (int32 ChannelIndex = 0; ChannelIndex < NumEffectiveChannels; ++ChannelIndex)
|
|
{
|
|
FRDGTextureRef& SingleChannelTexture = SingleChannelTextures.Add_GetRef(GraphBuilder.CreateTexture(SingleChannelTextureDesc, TEXT("LandscapeMergedTextureTargetLayer")));
|
|
FRenderTargetBinding SingleChannelRenderTargetBinding(SingleChannelTexture, ERenderTargetLoadAction::ENoAction);
|
|
RenderMergedTexture_RenderThread::RenderMergedTexture(MergeTextureRenderInfos[TargetLayerIndex], GraphBuilder, SingleChannelRenderTargetBinding);
|
|
// We have rendered a new target layer, move on to the next :
|
|
++TargetLayerIndex;
|
|
check(TargetLayerIndex <= NumTargetLayers);
|
|
}
|
|
|
|
// Now pack the channels directly to the final render target (slice)
|
|
{
|
|
FLandscapePackRGBAChannelsPS::FParameters* PackRGBAChannelsParams = GraphBuilder.AllocParameters<FLandscapePackRGBAChannelsPS::FParameters>();
|
|
PackRGBAChannelsParams->InNumChannels = NumEffectiveChannels;
|
|
for (int32 ChannelIndex = 0; ChannelIndex < 4; ++ChannelIndex)
|
|
{
|
|
PackRGBAChannelsParams->InSourceTextures[ChannelIndex] = (ChannelIndex < NumEffectiveChannels) ? GraphBuilder.CreateSRV(FRDGTextureSRVDesc::Create(SingleChannelTextures[ChannelIndex])) : DummyBlackTextureSRV;
|
|
}
|
|
const int16 ArraySlice = (OutputRenderTarget2DArrayResource != nullptr) ? SliceIndex : -1;
|
|
// If it's a texture 2D or a texture 2D array with individually targetable slices, we can pack directly using the slice's RTV :
|
|
if ((OutputRenderTarget2DArrayResource == nullptr) || EnumHasAnyFlags(OutputTexture->Desc.Flags, TexCreate_TargetArraySlicesIndependently))
|
|
{
|
|
FRenderTargetBinding RenderTargetBinding(OutputTexture, ERenderTargetLoadAction::ENoAction, /*InMipIndex = */0, ArraySlice);
|
|
PackRGBAChannelsParams->RenderTargets[0] = RenderTargetBinding;
|
|
FLandscapePackRGBAChannelsPS::PackRGBAChannels(GraphBuilder, PackRGBAChannelsParams, FIntRect(FIntPoint::ZeroValue, OutputTextureSize));
|
|
}
|
|
else
|
|
{
|
|
// Otherwise (2D array but with non-individually targetable slices), we need to render to another render target and use a copy :
|
|
FRDGTextureDesc IntermediateRenderTargetDesc = FRDGTextureDesc::Create2D(OutputTextureSize, OutputTexture->Desc.Format, FClearValueBinding::Black, TexCreate_RenderTargetable);
|
|
FRDGTextureRef IntermediateRenderTarget = GraphBuilder.CreateTexture(IntermediateRenderTargetDesc, TEXT("PackedRGBASlice"));
|
|
FRenderTargetBinding RenderTargetBinding(IntermediateRenderTarget, ERenderTargetLoadAction::ENoAction, /*InMipIndex = */0, /*InArraySlice = */-1);
|
|
PackRGBAChannelsParams->RenderTargets[0] = RenderTargetBinding;
|
|
FLandscapePackRGBAChannelsPS::PackRGBAChannels(GraphBuilder, PackRGBAChannelsParams, FIntRect(FIntPoint::ZeroValue, OutputTextureSize));
|
|
|
|
FRHICopyTextureInfo CopyTextureInfo;
|
|
CopyTextureInfo.DestSliceIndex = ArraySlice;
|
|
AddCopyTexturePass(GraphBuilder, IntermediateRenderTarget, OutputTexture, CopyTextureInfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
GraphBuilder.Execute();
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ALandscape::RenderHeightmap(FTransform InRenderAreaWorldTransform, FBox2D InRenderAreaExtents, UTextureRenderTarget2D* OutRenderTarget)
|
|
{
|
|
return RenderMergedTextureInternal(InRenderAreaWorldTransform, InRenderAreaExtents, /*InWeightmapLayerNames = */{}, OutRenderTarget);
|
|
}
|
|
|
|
bool ALandscape::RenderWeightmap(FTransform InRenderAreaWorldTransform, FBox2D InRenderAreaExtents, FName InWeightmapLayerName, UTextureRenderTarget2D* OutRenderTarget)
|
|
{
|
|
return RenderMergedTextureInternal(InRenderAreaWorldTransform, InRenderAreaExtents, /*InWeightmapLayerNames = */{ InWeightmapLayerName }, OutRenderTarget);
|
|
}
|
|
|
|
bool ALandscape::RenderWeightmaps(FTransform InRenderAreaWorldTransform, FBox2D InRenderAreaExtents, const TArray<FName>& InWeightmapLayerNames, UTextureRenderTarget* OutRenderTarget)
|
|
{
|
|
return RenderMergedTextureInternal(InRenderAreaWorldTransform, InRenderAreaExtents, InWeightmapLayerNames, OutRenderTarget);
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
|
|
FBox ALandscape::GetCompleteBounds() const
|
|
{
|
|
if (GetLandscapeInfo())
|
|
{
|
|
return GetLandscapeInfo()->GetCompleteBounds();
|
|
}
|
|
else
|
|
{
|
|
return FBox(EForceInit::ForceInit);
|
|
}
|
|
}
|
|
|
|
void ALandscape::SetUseGeneratedLandscapeSplineMeshesActors(bool bInEnabled)
|
|
{
|
|
bUseGeneratedLandscapeSplineMeshesActors = bInEnabled;
|
|
}
|
|
|
|
bool ALandscape::GetUseGeneratedLandscapeSplineMeshesActors() const
|
|
{
|
|
return bUseGeneratedLandscapeSplineMeshesActors;
|
|
}
|
|
|
|
void ALandscape::EnableNaniteSkirts(bool bInEnable, float InSkirtDepth, bool bInShouldDirtyPackage)
|
|
{
|
|
bNaniteSkirtEnabled = bInEnable;
|
|
NaniteSkirtDepth = InSkirtDepth;
|
|
|
|
InvalidateOrUpdateNaniteRepresentation(/*bInCheckContentId*/true, /*InTargetPlatform*/nullptr);
|
|
UpdateRenderingMethod();
|
|
MarkComponentsRenderStateDirty();
|
|
Modify(bInShouldDirtyPackage);
|
|
|
|
if (ULandscapeInfo* LandscapeInfo = GetLandscapeInfo())
|
|
{
|
|
LandscapeInfo->ForEachLandscapeProxy([&](ALandscapeProxy* Proxy)
|
|
{
|
|
if (Proxy != nullptr)
|
|
{
|
|
Proxy->Modify(bInShouldDirtyPackage);
|
|
Proxy->SynchronizeSharedProperties(this);
|
|
Proxy->InvalidateOrUpdateNaniteRepresentation(/*bInCheckContentId*/true, /*InTargetPlatform*/nullptr);
|
|
Proxy->UpdateRenderingMethod();
|
|
Proxy->MarkComponentsRenderStateDirty();
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
|
|
void ALandscape::SetDisableRuntimeGrassMapGeneration(bool bInDisableRuntimeGrassMapGeneration)
|
|
{
|
|
bDisableRuntimeGrassMapGeneration = bInDisableRuntimeGrassMapGeneration;
|
|
if (ULandscapeInfo* LandscapeInfo = GetLandscapeInfo())
|
|
{
|
|
LandscapeInfo->ForEachLandscapeProxy([bInDisableRuntimeGrassMapGeneration](ALandscapeProxy* Proxy) -> bool
|
|
{
|
|
Proxy->SetDisableRuntimeGrassMapGenerationProxyOnly(bInDisableRuntimeGrassMapGeneration);
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::OnFeatureLevelChanged(ERHIFeatureLevel::Type NewFeatureLevel)
|
|
{
|
|
FlushGrassComponents(nullptr, /*bFlushGrassMaps=*/ false); // rebuild grass instances, but keep the grass maps
|
|
|
|
UpdateAllComponentMaterialInstances();
|
|
|
|
if (NewFeatureLevel == ERHIFeatureLevel::ES3_1)
|
|
{
|
|
for (ULandscapeComponent* Component : LandscapeComponents)
|
|
{
|
|
if (Component != nullptr)
|
|
{
|
|
Component->CheckGenerateMobilePlatformData(/*bIsCooking = */ false, /*TargetPlatform = */ nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
UpdateRenderingMethod();
|
|
}
|
|
|
|
void ALandscapeProxy::BeginCacheForCookedPlatformData(const ITargetPlatform* TargetPlatform)
|
|
{
|
|
InstallOrUpdateTextureUserDatas(TargetPlatform);
|
|
Super::BeginCacheForCookedPlatformData(TargetPlatform);
|
|
}
|
|
#endif
|
|
|
|
|
|
void ALandscapeProxy::PreSave(FObjectPreSaveContext ObjectSaveContext)
|
|
{
|
|
Super::PreSave(ObjectSaveContext);
|
|
|
|
#if WITH_EDITOR
|
|
if (!ObjectSaveContext.IsProceduralSave()) // only finalize grass in a true editor save (when a GPU is available).
|
|
{
|
|
ALandscape* Landscape = GetLandscapeActor();
|
|
if (Landscape)
|
|
{
|
|
check(ObjectSaveContext.IsFirstConcurrentSave()); //if PreSave ever actually becomes concurrent, this will need some change to make it safe.
|
|
Landscape->FlushLayerContentThisFrame();
|
|
}
|
|
|
|
// It would be nice to strip grass data at editor save time to reduce asset size on disk.
|
|
// Unfortunately we can't easily know if there is a platform out there that may need to use the serialized grass map path.
|
|
// And future cook processes may not have a GPU available to build the grass data themselves.
|
|
// So for now, we always build all grass maps on editor save, just in case.
|
|
// The grass maps will get stripped later in BeginCacheForCookedPlatformData for cooked builds that don't need them.
|
|
{
|
|
// generate all of the grass data
|
|
BuildGrassMaps();
|
|
|
|
int32 ValidGrassCount = 0;
|
|
for (ULandscapeComponent* Component : LandscapeComponents)
|
|
{
|
|
if (Component->GrassData->HasValidData())
|
|
{
|
|
ValidGrassCount++;
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogGrass, Verbose, TEXT("PRESAVE: landscape %s has %d / %d valid grass components (UseRuntimeGeneration %d Disable %d)"), *GetName(), ValidGrassCount, LandscapeComponents.Num(), GGrassMapUseRuntimeGeneration, bDisableRuntimeGrassMapGeneration);
|
|
}
|
|
}
|
|
|
|
// Update nanite (and block to wait for it). Don't update nanite on auto-save, since its so slow.
|
|
if ((ObjectSaveContext.GetSaveFlags() & SAVE_FromAutosave) == 0)
|
|
{
|
|
if (ULandscapeInfo* LandscapeInfo = GetLandscapeInfo())
|
|
{
|
|
LandscapeInfo->UpdateNanite(ObjectSaveContext.GetTargetPlatform());
|
|
}
|
|
}
|
|
|
|
if (ALandscape* Landscape = GetLandscapeActor())
|
|
{
|
|
for (ULandscapeComponent* LandscapeComponent : LandscapeComponents)
|
|
{
|
|
Landscape->ClearDirtyData(LandscapeComponent);
|
|
|
|
// Make sure edit layer debug names are synchronized upon save :
|
|
LandscapeComponent->ForEachLayer([&](const FGuid& LayerGuid, FLandscapeLayerComponentData& LayerData)
|
|
{
|
|
if (const ULandscapeEditLayerBase* EditLayer = Landscape->GetEditLayerConst(LayerGuid))
|
|
{
|
|
LayerData.DebugName = EditLayer->GetName();
|
|
}
|
|
});
|
|
}
|
|
UpdateRenderingMethod();
|
|
}
|
|
|
|
for (ULandscapeComponent* LandscapeComponent : LandscapeComponents)
|
|
{
|
|
if (LandscapeComponent->CanUpdatePhysicalMaterial() && LandscapeComponent->PhysicalMaterialTask.IsValid())
|
|
{
|
|
UE_LOG(LogLandscape, Display, TEXT("Completing landscape physical material before saving. %s"), *LandscapeComponent->GetFullName());
|
|
if (LandscapeComponent->PhysicalMaterialTask.IsInProgress())
|
|
{
|
|
LandscapeComponent->PhysicalMaterialTask.Flush();
|
|
}
|
|
check(LandscapeComponent->PhysicalMaterialTask.IsComplete());
|
|
// IsInProgress tests the render thread status. Now finish the last steps on the game thread.
|
|
LandscapeComponent->UpdatePhysicalMaterialTasks();
|
|
}
|
|
|
|
// Ensure the component's cached bounds are correct
|
|
FBox OldCachedLocalBox = LandscapeComponent->CachedLocalBox;
|
|
if (LandscapeComponent->UpdateCachedBounds(/* bInApproximateBounds= */ false))
|
|
{
|
|
// conservative bounds are true bounding boxes, just not as tight/optimal as they could be
|
|
// if it's not conservative, then visibility flashing issues can occur because of self-occlusion in culling
|
|
bool bOldBoxIsConservative = LandscapeComponent->CachedLocalBox.IsInsideOrOn(OldCachedLocalBox);
|
|
if (bOldBoxIsConservative)
|
|
{
|
|
UE_LOG(LogLandscape, Display, TEXT("The component %s had non-optimal bounds. The bounds have been recalculated (old CachedLocalBox: %s, new CachedLocalBox: %s)"), *LandscapeComponent->GetPathName(), *OldCachedLocalBox.ToString(), *LandscapeComponent->CachedLocalBox.ToString());
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogLandscape, Display, TEXT("The component %s had incorrect bounds. The bounds have been recalculated (old CachedLocalBox: %s, new CachedLocalBox: %s)"), *LandscapeComponent->GetPathName(), *OldCachedLocalBox.ToString(), *LandscapeComponent->CachedLocalBox.ToString());
|
|
}
|
|
check(LandscapeComponent->CachedLocalBox.GetVolume() > 0.0);
|
|
}
|
|
}
|
|
|
|
if (LandscapeGuid.IsValid())
|
|
{
|
|
if (ULandscapeInfo* LandscapeInfo = GetLandscapeInfo())
|
|
{
|
|
LandscapeInfo->OnModifiedPackageSaved(GetPackage());
|
|
}
|
|
}
|
|
|
|
if (ObjectSaveContext.IsCooking())
|
|
{
|
|
InstallOrUpdateTextureUserDatas(ObjectSaveContext.GetTargetPlatform());
|
|
}
|
|
#endif // WITH_EDITOR
|
|
}
|
|
|
|
void ALandscapeProxy::Serialize(FArchive& Ar)
|
|
{
|
|
FGuid InstanceLandscapeGuid = this->LandscapeGuid;
|
|
if (Ar.IsSaving() && Ar.IsPersistent())
|
|
{
|
|
// if we're using an instance-modified landscape guid, we need to restore the original before saving to persistent storage
|
|
// (this can happen when you are cooking a level containing level instances in a commandlet)
|
|
if ((LandscapeGuid != OriginalLandscapeGuid) && OriginalLandscapeGuid.IsValid())
|
|
{
|
|
this->LandscapeGuid = this->OriginalLandscapeGuid;
|
|
}
|
|
}
|
|
|
|
Super::Serialize(Ar);
|
|
|
|
Ar.UsingCustomVersion(FLandscapeCustomVersion::GUID);
|
|
Ar.UsingCustomVersion(FEditorObjectVersion::GUID);
|
|
Ar.UsingCustomVersion(FFortniteReleaseBranchCustomObjectVersion::GUID);
|
|
Ar.UsingCustomVersion(FFortniteMainBranchObjectVersion::GUID);
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
if (Ar.IsLoading() && Ar.CustomVer(FLandscapeCustomVersion::GUID) < FLandscapeCustomVersion::MigrateOldPropertiesToNewRenderingProperties)
|
|
{
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
if (LODDistanceFactor_DEPRECATED > 0)
|
|
{
|
|
const float LOD0LinearDistributionSettingMigrationTable[11] = { 1.75f, 1.75f, 1.75f, 1.75f, 1.75f, 1.68f, 1.55f, 1.4f, 1.25f, 1.25f, 1.25f };
|
|
const float LODDLinearDistributionSettingMigrationTable[11] = { 2.0f, 2.0f, 2.0f, 1.65f, 1.35f, 1.25f, 1.25f, 1.25f, 1.25f, 1.25f, 1.25f };
|
|
const float LOD0SquareRootDistributionSettingMigrationTable[11] = { 1.75f, 1.6f, 1.25f, 1.25f, 1.25f, 1.25f, 1.25f, 1.25f, 1.25f, 1.25f, 1.25f };
|
|
const float LODDSquareRootDistributionSettingMigrationTable[11] = { 2.0f, 1.8f, 1.55f, 1.3f, 1.25f, 1.25f, 1.25f, 1.25f, 1.25f, 1.25f, 1.25f };
|
|
|
|
if (LODFalloff_DEPRECATED == ELandscapeLODFalloff::Type::Linear)
|
|
{
|
|
LOD0DistributionSetting = LOD0LinearDistributionSettingMigrationTable[FMath::RoundToInt(LODDistanceFactor_DEPRECATED)];
|
|
LODDistributionSetting = LODDLinearDistributionSettingMigrationTable[FMath::RoundToInt(LODDistanceFactor_DEPRECATED)];
|
|
}
|
|
else if (LODFalloff_DEPRECATED == ELandscapeLODFalloff::Type::SquareRoot)
|
|
{
|
|
LOD0DistributionSetting = LOD0SquareRootDistributionSettingMigrationTable[FMath::RoundToInt(LODDistanceFactor_DEPRECATED)];
|
|
LODDistributionSetting = LODDSquareRootDistributionSettingMigrationTable[FMath::RoundToInt(LODDistanceFactor_DEPRECATED)];
|
|
}
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
}
|
|
#endif
|
|
|
|
if (Ar.IsSaving() && Ar.IsPersistent())
|
|
{
|
|
// restore the instance guid
|
|
this->LandscapeGuid = InstanceLandscapeGuid;
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector)
|
|
{
|
|
ALandscapeProxy* This = CastChecked<ALandscapeProxy>(InThis);
|
|
|
|
Super::AddReferencedObjects(InThis, Collector);
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
Collector.AddReferencedObjects(This->MaterialInstanceConstantMap, This);
|
|
#endif
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
|
|
FName FLandscapeInfoLayerSettings::GetLayerName() const
|
|
{
|
|
checkSlow(LayerInfoObj == nullptr || LayerInfoObj->LayerName == LayerName);
|
|
|
|
return LayerName;
|
|
}
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
FLandscapeEditorLayerSettings& FLandscapeInfoLayerSettings::GetEditorSettings() const
|
|
{
|
|
static FLandscapeEditorLayerSettings DeprecatedSettings;
|
|
return DeprecatedSettings;
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
const FLandscapeTargetLayerSettings& FLandscapeInfoLayerSettings::GetTargetLayerSettings() const
|
|
{
|
|
check(LayerInfoObj);
|
|
|
|
ULandscapeInfo* LandscapeInfo = Owner->GetLandscapeInfo();
|
|
return LandscapeInfo->GetTargetLayerSettings(LayerInfoObj);
|
|
}
|
|
|
|
FLandscapeTargetLayerSettings& ULandscapeInfo::GetLayerEditorSettings(ULandscapeLayerInfoObject* LayerInfo) const
|
|
{
|
|
static FLandscapeTargetLayerSettings DeprecatedSettings;
|
|
return DeprecatedSettings;
|
|
}
|
|
|
|
const FLandscapeTargetLayerSettings& ULandscapeInfo::GetTargetLayerSettings(ULandscapeLayerInfoObject* LayerInfo) const
|
|
{
|
|
ALandscapeProxy* Proxy = GetLandscapeProxy();
|
|
const FName* LayerName = Proxy->GetTargetLayers().FindKey(FLandscapeTargetLayerSettings(LayerInfo));
|
|
if (LayerName)
|
|
{
|
|
return *Proxy->GetTargetLayers().Find(*LayerName);
|
|
}
|
|
else
|
|
{
|
|
return Proxy->AddTargetLayer(LayerInfo->LayerName, FLandscapeTargetLayerSettings(LayerInfo));
|
|
}
|
|
}
|
|
|
|
void ULandscapeInfo::CreateTargetLayerSettingsFor(ULandscapeLayerInfoObject* LayerInfo)
|
|
{
|
|
ForEachLandscapeProxy([LayerInfo](ALandscapeProxy* Proxy)
|
|
{
|
|
if (Proxy->HasTargetLayer(LayerInfo->LayerName))
|
|
{
|
|
Proxy->UpdateTargetLayer(LayerInfo->LayerName, FLandscapeTargetLayerSettings(LayerInfo));
|
|
}
|
|
else
|
|
{
|
|
Proxy->AddTargetLayer(LayerInfo->LayerName, FLandscapeTargetLayerSettings(LayerInfo));
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}
|
|
|
|
ULandscapeLayerInfoObject* ULandscapeInfo::GetLayerInfoByName(FName LayerName, ALandscapeProxy* Owner /*= nullptr*/) const
|
|
{
|
|
ULandscapeLayerInfoObject* LayerInfo = nullptr;
|
|
for (int32 j = 0; j < Layers.Num(); j++)
|
|
{
|
|
if (Layers[j].LayerInfoObj && Layers[j].LayerInfoObj->LayerName == LayerName
|
|
&& (Owner == nullptr || Layers[j].Owner == Owner))
|
|
{
|
|
LayerInfo = Layers[j].LayerInfoObj;
|
|
}
|
|
}
|
|
return LayerInfo;
|
|
}
|
|
|
|
int32 ULandscapeInfo::GetLayerInfoIndex(ULandscapeLayerInfoObject* LayerInfo, ALandscapeProxy* Owner /*= nullptr*/) const
|
|
{
|
|
for (int32 j = 0; j < Layers.Num(); j++)
|
|
{
|
|
if (Layers[j].LayerInfoObj && Layers[j].LayerInfoObj == LayerInfo
|
|
&& (Owner == nullptr || Layers[j].Owner == Owner))
|
|
{
|
|
return j;
|
|
}
|
|
}
|
|
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
int32 ULandscapeInfo::GetLayerInfoIndex(FName LayerName, ALandscapeProxy* Owner /*= nullptr*/) const
|
|
{
|
|
for (int32 j = 0; j < Layers.Num(); j++)
|
|
{
|
|
if (Layers[j].GetLayerName() == LayerName
|
|
&& (Owner == nullptr || Layers[j].Owner == Owner))
|
|
{
|
|
return j;
|
|
}
|
|
}
|
|
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
|
|
bool ULandscapeInfo::UpdateLayerInfoMapInternal(ALandscapeProxy* Proxy)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(ULandscapeInfo::UpdateLayerInfoMapInternal);
|
|
|
|
bool bLayerInfoMapChanged = false;
|
|
if( !LandscapeActor.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Perform a delayed (see where TargetLayersForFixup is set), one-time deprecation of the landscape layer data based on the content of the materials in the components (see FixupLandscapeTargetLayersInLandscapeActor)
|
|
if (GIsEditor && Proxy && !Proxy->TargetLayersForFixup.IsEmpty())
|
|
{
|
|
// Go through the list of layer names / info to fixup and declare new or update existing layers in the main landscape actor if we have one that the main landscape doesn't know about :
|
|
for (const auto& It : Proxy->TargetLayersForFixup)
|
|
{
|
|
FName LayerName = It.Key;
|
|
ULandscapeLayerInfoObject* LayerInfo = It.Value;
|
|
check(LayerName.IsValid());
|
|
|
|
const FLandscapeTargetLayerSettings* LayerSettingsInLandscapeActor = LandscapeActor->GetTargetLayers().Find(LayerName);
|
|
// If the layer isn't known to the main landscape, add it now :
|
|
if (LayerSettingsInLandscapeActor == nullptr)
|
|
{
|
|
// Mark the parent landscape actor dirty with bInForceResave == true so that the parent actor is put into the list of files to save even if we do this fixup on load :
|
|
MarkObjectDirty(/*InObject = */LandscapeActor.Get(), /*bInForceResave = */true);
|
|
|
|
LandscapeActor->AddTargetLayer(LayerName, FLandscapeTargetLayerSettings(LayerInfo), false);
|
|
bLayerInfoMapChanged = true;
|
|
}
|
|
// If the layer name is known to the main landscape but it hasn't got a landscape info associated to it yet, update it to use this LayerInfo :
|
|
else if ((LayerInfo != nullptr) && (LayerSettingsInLandscapeActor->LayerInfoObj == nullptr))
|
|
{
|
|
// Mark the parent landscape actor dirty with bInForceResave == true so that the parent actor is put into the list of files to save even if we do this fixup on load :
|
|
MarkObjectDirty(/*InObject = */LandscapeActor.Get(), /*bInForceResave = */true);
|
|
|
|
LandscapeActor->UpdateTargetLayer(LayerName, FLandscapeTargetLayerSettings(LayerInfo), false);
|
|
bLayerInfoMapChanged = true;
|
|
}
|
|
}
|
|
|
|
Proxy->TargetLayersForFixup.Empty();
|
|
}
|
|
|
|
// Keep a temp copy of the previous layers to keep the thumbnail MICs alive :
|
|
TArray<FLandscapeInfoLayerSettings> PreviousLayers;
|
|
Swap(Layers, PreviousLayers);
|
|
|
|
for (const TTuple<FName, FLandscapeTargetLayerSettings>& TargetLayer : LandscapeActor->GetTargetLayers())
|
|
{
|
|
FLandscapeInfoLayerSettings InfoLayerSettings (TargetLayer.Key, LandscapeActor.Get());
|
|
InfoLayerSettings.LayerInfoObj = TargetLayer.Value.LayerInfoObj;
|
|
if (const FLandscapeInfoLayerSettings* PreviousLayer = Algo::FindBy(PreviousLayers, TargetLayer.Key, &FLandscapeInfoLayerSettings::LayerName))
|
|
{
|
|
InfoLayerSettings.ThumbnailMIC = PreviousLayer->ThumbnailMIC;
|
|
}
|
|
|
|
Layers.Add(InfoLayerSettings);
|
|
}
|
|
|
|
// Add Visibility Layer info if not initialized
|
|
if (ALandscapeProxy::VisibilityLayer != nullptr)
|
|
{
|
|
int32 LayerInfoIndex = GetLayerInfoIndex(ALandscapeProxy::VisibilityLayer->LayerName);
|
|
|
|
if ((LayerInfoIndex != INDEX_NONE) && (Layers[LayerInfoIndex].LayerInfoObj == nullptr))
|
|
{
|
|
Layers[LayerInfoIndex].LayerInfoObj = ALandscapeProxy::VisibilityLayer;
|
|
}
|
|
}
|
|
|
|
return bLayerInfoMapChanged;
|
|
}
|
|
|
|
bool ULandscapeInfo::UpdateLayerInfoMap(ALandscapeProxy* Proxy /*= nullptr*/, bool bInvalidate /*= false*/)
|
|
{
|
|
bool bLayerInfoMapChanged = UpdateLayerInfoMapInternal(Proxy);
|
|
|
|
if (GIsEditor)
|
|
{
|
|
ALandscape* Landscape = LandscapeActor.Get();
|
|
if (Landscape && Landscape->HasLayersContent())
|
|
{
|
|
Landscape->RequestLayersInitialization(/*bInRequestContentUpdate*/false, /*bInForceLayerResourceReset*/false);
|
|
}
|
|
}
|
|
return bLayerInfoMapChanged;
|
|
|
|
}
|
|
|
|
#endif // WITH_EDITOR
|
|
|
|
/* if the outer world is instanced, we need to change our landscape guid(in a deterministic way)
|
|
* this avoids guid collisions when you instance a world (and its landscapes) multiple times,
|
|
* while maintaining the same GUID between landscape proxy objects within an instance
|
|
*/
|
|
void ChangeLandscapeGuidIfObjectIsInstanced(FGuid& InOutGuid, UObject* InObject)
|
|
{
|
|
// we shouldn't be dealing with any instanced landscapes in these cases, early out
|
|
if (InObject->IsTemplate())
|
|
{
|
|
return;
|
|
}
|
|
|
|
UWorldPartition* WorldPartition = FWorldPartitionHelpers::GetWorldPartition(InObject);
|
|
|
|
#if WITH_EDITOR
|
|
// In PIE, Actors that are part of a Unsaved cluster of actors can end up being duplicated through a UActorContainer.
|
|
// In this case we need to resolve the WorldPartition differently. This could be fixed in a more generic way but would require a lot more testing (Jira: tbd)
|
|
if (!WorldPartition)
|
|
{
|
|
if (UActorContainer* Container = InObject->GetTypedOuter<UActorContainer>())
|
|
{
|
|
WorldPartition = FWorldPartitionHelpers::GetWorldPartition(Container->RuntimeLevel.Get());
|
|
}
|
|
}
|
|
#endif
|
|
|
|
UWorld* OuterWorld = WorldPartition ? WorldPartition->GetTypedOuter<UWorld>() : InObject->GetTypedOuter<UWorld>();
|
|
|
|
// TODO [chris.tchou] : Note this is not 100% correct, IsInstanced() returns TRUE when using PIE on non-instanced landscapes.
|
|
// That is generally ok however, as the GUID remaps are still deterministic and landscape still works.
|
|
if (OuterWorld && OuterWorld->IsInstanced())
|
|
{
|
|
FArchiveMD5 Ar;
|
|
FGuid OldLandscapeGuid = InOutGuid;
|
|
Ar << OldLandscapeGuid;
|
|
|
|
UPackage* OuterWorldPackage = OuterWorld->GetPackage();
|
|
if (ensure(OuterWorldPackage))
|
|
{
|
|
FName PackageName = OuterWorldPackage->GetFName();
|
|
Ar << PackageName;
|
|
}
|
|
|
|
InOutGuid = Ar.GetGuidFromHash();
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::PostLoadFixupLandscapeGuidsIfInstanced()
|
|
{
|
|
// record the original value before modification
|
|
check(!OriginalLandscapeGuid.IsValid() || (OriginalLandscapeGuid == LandscapeGuid));
|
|
OriginalLandscapeGuid = this->LandscapeGuid;
|
|
|
|
ChangeLandscapeGuidIfObjectIsInstanced(this->LandscapeGuid, this);
|
|
}
|
|
|
|
void ALandscapeProxy::PostLoad()
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(ALandscapeProxy::PostLoad);
|
|
|
|
|
|
#if WITH_EDITOR
|
|
// Not sure that this can ever happen without someone deliberately changing the root component but a landscape without a root component is
|
|
// worthless and will lead to pain and crash, so attempt to fix it up on load here :
|
|
if (GetRootComponent() == nullptr)
|
|
{
|
|
TInlineComponentArray<USceneComponent*> SceneComponents;
|
|
GetComponents<USceneComponent>(SceneComponents, /*bIncludeFromChildActors = */false);
|
|
UClass* SceneComponentClass = USceneComponent::StaticClass();
|
|
if (USceneComponent** RootComponentCandidate = Algo::FindByPredicate(SceneComponents, [SceneComponentClass](USceneComponent* InComponent) { return (InComponent->GetClass() == SceneComponentClass); }))
|
|
{
|
|
SetRootComponent(*RootComponentCandidate);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogLandscape, Error, TEXT("Unable to retrieve a root component for landscape proxy %s. The landscape will not work properly."), *GetFullName());
|
|
}
|
|
}
|
|
|
|
// Fix up bHasLayersContent if needed : there was a point where there was a missing call (when adding new components) that was leading to it not being
|
|
// properly updated even though there was some layer content. We need to fix it now, so that we don't attempt to migrate data from non-edit layers to
|
|
// edit layers later on, since that would stomp the edit layer data :
|
|
if (!bHasLayersContent && !LandscapeComponents.IsEmpty() && (LandscapeComponents[0] != nullptr) && LandscapeComponents[0]->HasLayersData())
|
|
{
|
|
bHasLayersContent = true;
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
// save the load time state of layers content before doing anything else (specifically FixupSharedData can stomp it)
|
|
check(!HadLayersContentAtPostLoadTime.IsSet());
|
|
HadLayersContentAtPostLoadTime = bHasLayersContent;
|
|
|
|
Super::PostLoad();
|
|
|
|
PostLoadFixupLandscapeGuidsIfInstanced();
|
|
|
|
#if WITH_EDITOR
|
|
FixupOverriddenSharedProperties();
|
|
|
|
ALandscape* LandscapeActor = GetLandscapeActor();
|
|
|
|
// Try to fixup shared properties if everything is ready for it as some postload process may depend on it.
|
|
if ((GetLandscapeInfo() != nullptr) && (LandscapeActor != nullptr) && (LandscapeActor != this))
|
|
{
|
|
const bool bMapCheck = true;
|
|
FixupSharedData(LandscapeActor, bMapCheck);
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
// Temporary
|
|
if (ComponentSizeQuads == 0 && LandscapeComponents.Num() > 0)
|
|
{
|
|
ULandscapeComponent* Comp = LandscapeComponents[0];
|
|
if (Comp)
|
|
{
|
|
ComponentSizeQuads = Comp->ComponentSizeQuads;
|
|
SubsectionSizeQuads = Comp->SubsectionSizeQuads;
|
|
NumSubsections = Comp->NumSubsections;
|
|
}
|
|
}
|
|
|
|
if (IsTemplate() == false)
|
|
{
|
|
BodyInstance.FixupData(this);
|
|
}
|
|
|
|
for (ULandscapeComponent* Comp : LandscapeComponents)
|
|
{
|
|
if (Comp == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
UE_LOG(LogGrass, Verbose, TEXT("POSTLOAD: component %s on landscape %s UseRuntimeGeneration %d Disable: %d data: %d"),
|
|
*Comp->GetName(),
|
|
*GetName(),
|
|
GGrassMapUseRuntimeGeneration, bDisableRuntimeGrassMapGeneration, Comp->GrassData->NumElements);
|
|
|
|
#if !WITH_EDITOR
|
|
// if using runtime grass gen, it should have been cleared out in PreSave
|
|
if (GGrassMapUseRuntimeGeneration && !bDisableRuntimeGrassMapGeneration)
|
|
{
|
|
if (Comp->GrassData->HasData())
|
|
{
|
|
UE_LOG(LogGrass, Warning, TEXT("grass.GrassMap.UseRuntimeGeneration is enabled, but component %s on landscape %s has unnecessary grass data saved. Ensure grass.GrassMap.UseRuntimeGeneration is enabled at cook time to reduce cooked data size."),
|
|
*Comp->GetName(),
|
|
*GetName());
|
|
|
|
// Free the memory, so at least we will save the space at runtime.
|
|
Comp->GrassData = MakeShared<FLandscapeComponentGrassData>();
|
|
}
|
|
}
|
|
#endif // !WITH_EDITOR
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
if (!LandscapeMaterialsOverride_DEPRECATED.IsEmpty())
|
|
{
|
|
PerLODOverrideMaterials.Reserve(LandscapeMaterialsOverride_DEPRECATED.Num());
|
|
for (const FLandscapeProxyMaterialOverride& LocalMaterialOverride : LandscapeMaterialsOverride_DEPRECATED)
|
|
{
|
|
PerLODOverrideMaterials.Add({ LocalMaterialOverride.LODIndex.Default, LocalMaterialOverride.Material });
|
|
}
|
|
LandscapeMaterialsOverride_DEPRECATED.Reset();
|
|
}
|
|
|
|
if (!EditorLayerSettings_DEPRECATED.IsEmpty())
|
|
{
|
|
// If we still have access to EditorLayerSettings_DEPRECATED because it's the first time we deprecate this proxy since FFortniteMainBranchObjectVersion::LandscapeTargetLayersInLandscapeActor,
|
|
// fill the list of target layers to fixup based on the original property because it's the most accurate (it has layer info assignment even if there's no weightmap allocation for a given layer) :
|
|
TargetLayersForFixup.Reserve(EditorLayerSettings_DEPRECATED.Num());
|
|
for (const FLandscapeEditorLayerSettings& EditorLayerSetting : EditorLayerSettings_DEPRECATED)
|
|
{
|
|
if (EditorLayerSetting.LayerInfoObj != nullptr)
|
|
{
|
|
TargetLayersForFixup.Add(EditorLayerSetting.LayerInfoObj->LayerName, EditorLayerSetting.LayerInfoObj);
|
|
}
|
|
}
|
|
EditorLayerSettings_DEPRECATED.Reset();
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
const int32 LinkerVersion = GetLinkerCustomVersion(FFortniteMainBranchObjectVersion::GUID);
|
|
// With and before LandscapeTargetLayersInLandscapeActor and until FixupLandscapeTargetLayersInLandscapeActor, some layer info objects have been incorrectly unassigned and we now have to go through
|
|
// all materials of all streaming proxies to gather the missing landscape layer info objects and this can only be done after proxies are united with their parent landscape by their ULandscapeInfo
|
|
// (since TargetLayers is a LandscapeInherited property of the parent landscape, propagated to the child proxies), so we must delay this operation until then. What we do here is prepare a list of
|
|
// layers to fixup in the main landscape actor (TargetLayersForFixup) and when the proxy is registered to the parent landscape, we'll go through that list and update the landscape's TargetLayers list,
|
|
// which will then be synchronized with all proxies if necessary:
|
|
if (LinkerVersion < FFortniteMainBranchObjectVersion::FixupLandscapeTargetLayersInLandscapeActor)
|
|
{
|
|
// Go through the list of materials and weightmap allocations to gather potential layer name / layer info associations :
|
|
{
|
|
TMap<FName, ULandscapeLayerInfoObject*> LayerInfosFromAllocations = RetrieveTargetLayerInfosFromAllocations();
|
|
for (const auto& It : LayerInfosFromAllocations)
|
|
{
|
|
FName LayerName = It.Key;
|
|
ULandscapeLayerInfoObject* LayerInfo = It.Value;
|
|
TObjectPtr<ULandscapeLayerInfoObject>* LayerInfoInFixupMap = TargetLayersForFixup.Find(LayerName);
|
|
// Unknown layer name yet, let's add a layer name / info association :
|
|
if (LayerInfoInFixupMap == nullptr)
|
|
{
|
|
TargetLayersForFixup.Add(LayerName, LayerInfo);
|
|
}
|
|
// Known layer name, but we have no valid layer info associated with it yet, update it :
|
|
else if (*LayerInfoInFixupMap == nullptr)
|
|
{
|
|
*LayerInfoInFixupMap = LayerInfo;
|
|
}
|
|
// Otherwise, don't touch it, we consider that TargetLayersForFixup has the authority over this layer already
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GIsEditor)
|
|
{
|
|
// We may not have run PostLoad on LandscapeComponents yet
|
|
for (TObjectPtr<ULandscapeComponent>& LandscapeComponent : LandscapeComponents)
|
|
{
|
|
if (LandscapeComponent)
|
|
{
|
|
LandscapeComponent->ConditionalPostLoad();
|
|
}
|
|
}
|
|
|
|
// We may not have run PostLoad on CollisionComponent yet
|
|
for (TObjectPtr<ULandscapeHeightfieldCollisionComponent>& CollisionComponent : CollisionComponents)
|
|
{
|
|
if (CollisionComponent)
|
|
{
|
|
CollisionComponent->ConditionalPostLoad();
|
|
}
|
|
}
|
|
|
|
if ((GetLinker() && (GetLinker()->UEVer() < VER_UE4_LANDSCAPE_COMPONENT_LAZY_REFERENCES)) ||
|
|
LandscapeComponents.Num() != CollisionComponents.Num() ||
|
|
LandscapeComponents.ContainsByPredicate([](ULandscapeComponent* Comp) { return ((Comp != nullptr) && (Comp->GetCollisionComponent() == nullptr)); }) ||
|
|
CollisionComponents.ContainsByPredicate([](ULandscapeHeightfieldCollisionComponent* Comp) { return ((Comp != nullptr) && (Comp->GetRenderComponent() == nullptr)); }))
|
|
{
|
|
// Need to clean up invalid collision and render components
|
|
RecreateCollisionComponents();
|
|
}
|
|
}
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
if (EditorCachedLayerInfos_DEPRECATED.Num() > 0)
|
|
{
|
|
for (int32 i = 0; i < EditorCachedLayerInfos_DEPRECATED.Num(); i++)
|
|
{
|
|
TargetLayers.Add(EditorCachedLayerInfos_DEPRECATED[i]->LayerName, FLandscapeTargetLayerSettings(EditorCachedLayerInfos_DEPRECATED[i]));
|
|
}
|
|
EditorCachedLayerInfos_DEPRECATED.Empty();
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
bool bFixedUpInvalidMaterialInstances = false;
|
|
for (ULandscapeComponent* Comp : LandscapeComponents)
|
|
{
|
|
if (Comp == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Validate the layer combination and store it in the MaterialInstanceConstantMap
|
|
UMaterialInstance* MaterialInstance = Comp->GetMaterialInstance(0, false);
|
|
|
|
if (MaterialInstance == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
UMaterialInstanceConstant* CombinationMaterialInstance = Cast<UMaterialInstanceConstant>(MaterialInstance->Parent);
|
|
// Only validate if uncooked and in the editor/commandlet mode (we cannot re-build material instance constants if this is not the case : see UMaterialInstance::CacheResourceShadersForRendering, which is only called if FApp::CanEverRender() returns true)
|
|
if (!Comp->GetOutermost()->HasAnyPackageFlags(PKG_FilterEditorOnly) && (GIsEditor && FApp::CanEverRender()))
|
|
{
|
|
UMaterial* BaseLandscapeMaterial = Comp->GetLandscapeMaterial()->GetMaterial();
|
|
// MaterialInstance is different from the used LandscapeMaterial, we need to update the material as we cannot properly validate used combinations.
|
|
if (MaterialInstance->GetMaterial() != BaseLandscapeMaterial)
|
|
{
|
|
Comp->UpdateMaterialInstances();
|
|
bFixedUpInvalidMaterialInstances = true;
|
|
continue;
|
|
}
|
|
|
|
if (Comp->ValidateCombinationMaterial(CombinationMaterialInstance))
|
|
{
|
|
MaterialInstanceConstantMap.Add(*ULandscapeComponent::GetLayerAllocationKey(Comp->GetWeightmapLayerAllocations(), CombinationMaterialInstance->Parent), CombinationMaterialInstance);
|
|
}
|
|
else
|
|
{
|
|
// There was a problem with the loaded material : it doesn't match the expected material combination, we need to regenerate the material instances :
|
|
Comp->UpdateMaterialInstances();
|
|
bFixedUpInvalidMaterialInstances = true;
|
|
}
|
|
}
|
|
else if (CombinationMaterialInstance)
|
|
{
|
|
// Skip ValidateCombinationMaterial
|
|
MaterialInstanceConstantMap.Add(*ULandscapeComponent::GetLayerAllocationKey(Comp->GetWeightmapLayerAllocations(), CombinationMaterialInstance->Parent), CombinationMaterialInstance);
|
|
}
|
|
}
|
|
|
|
if (bFixedUpInvalidMaterialInstances)
|
|
{
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("ProxyPackage"), FText::FromString(GetOutermost()->GetName()));
|
|
FMessageLog("MapCheck").Info()
|
|
->AddToken(FUObjectToken::Create(this, FText::FromString(GetActorNameOrLabel())))
|
|
->AddToken(FTextToken::Create(FText::Format(LOCTEXT("MapCheck_Message_FixedUpInvalidLandscapeMaterialInstances", "Fixed up invalid landscape material instances. Please re-save {ProxyPackage}."), Arguments)))
|
|
->AddToken(FMapErrorToken::Create(FMapErrors::FixedUpInvalidLandscapeMaterialInstances));
|
|
}
|
|
|
|
// Display a MapCheck warning if the Nanite data is stale with the option to trigger a rebuild & Save
|
|
if (!IsNaniteMeshUpToDate() && !IsRunningCookCommandlet())
|
|
{
|
|
auto CreateMapCheckMessage = [](FMessageLog& MessageLog)
|
|
{
|
|
if (CVarLandscapeSilenceMapCheckWarnings_Nanite->GetBool())
|
|
{
|
|
return MessageLog.Info();
|
|
}
|
|
return MessageLog.Warning();
|
|
};
|
|
|
|
TWeakObjectPtr<ALandscapeProxy> WeakLandscapeProxy(this);
|
|
|
|
FMessageLog MessageLog("MapCheck");
|
|
CreateMapCheckMessage(MessageLog)
|
|
->AddToken(FUObjectToken::Create(this, FText::FromString(GetActorNameOrLabel())))
|
|
->AddToken(FTextToken::Create(LOCTEXT("MapCheck_Message_LandscapeRebuildNanite", "Landscape Nanite is enabled but the saved mesh data is out of date. ")))
|
|
->AddToken(FActionToken::Create(LOCTEXT("MapCheck_SaveFixedUpData", "Save Modified Landscapes"), LOCTEXT("MapCheck_SaveFixedUpData_Desc", "Saves the modified landscape proxy actors"),
|
|
FOnActionTokenExecuted::CreateLambda([WeakLandscapeProxy]()
|
|
{
|
|
if (!WeakLandscapeProxy.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
ULandscapeInfo* Info = WeakLandscapeProxy->GetLandscapeInfo();
|
|
check(Info);
|
|
|
|
TSet<UPackage*> DirtyNanitePackages;
|
|
Info->ForEachLandscapeProxy([&DirtyNanitePackages](const ALandscapeProxy* Proxy)
|
|
{
|
|
if (!Proxy->IsNaniteMeshUpToDate())
|
|
{
|
|
DirtyNanitePackages.Add(Proxy->GetOutermost());
|
|
}
|
|
return true;
|
|
});
|
|
|
|
Info->UpdateNanite(nullptr);
|
|
|
|
constexpr bool bPromptUserToSave = true;
|
|
constexpr bool bSaveMapPackages = true;
|
|
constexpr bool bSaveContentPackages = true;
|
|
constexpr bool bFastSave = false;
|
|
constexpr bool bNotifyNoPackagesSaved = false;
|
|
constexpr bool bCanBeDeclined = true;
|
|
|
|
FEditorFileUtils::SaveDirtyPackages(bPromptUserToSave, bSaveMapPackages, bSaveContentPackages, bFastSave, bNotifyNoPackagesSaved, bCanBeDeclined, nullptr,
|
|
[&DirtyNanitePackages](const UPackage* Package) { return !DirtyNanitePackages.Contains(Package); });
|
|
|
|
}), FCanExecuteActionToken::CreateLambda([WeakLandscapeProxy]() { return WeakLandscapeProxy.IsValid() ? !WeakLandscapeProxy->IsNaniteMeshUpToDate() : false; }))
|
|
);
|
|
}
|
|
|
|
UWorld* World = GetWorld();
|
|
|
|
// track feature level change to flush grass cache
|
|
if (World)
|
|
{
|
|
FOnFeatureLevelChanged::FDelegate FeatureLevelChangedDelegate = FOnFeatureLevelChanged::FDelegate::CreateUObject(this, &ALandscapeProxy::OnFeatureLevelChanged);
|
|
FeatureLevelChangedDelegateHandle = World->AddOnFeatureLevelChangedHandler(FeatureLevelChangedDelegate);
|
|
}
|
|
RepairInvalidTextures();
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
if (NaniteComponent_DEPRECATED)
|
|
{
|
|
NaniteComponents.Add(NaniteComponent_DEPRECATED);
|
|
NaniteComponent_DEPRECATED = nullptr;
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
// Fix for nanite components that have lost their link to their source landscape components
|
|
for (int32 i = 0; i < NaniteComponents.Num(); ++i)
|
|
{
|
|
if (NaniteComponents[i] && NaniteComponents[i]->GetSourceLandscapeComponents().Num() == 0)
|
|
{
|
|
NaniteComponents[i]->SetSourceLandscapeComponents(GatherSourceComponentsForNaniteComponent(i));
|
|
}
|
|
}
|
|
|
|
// Handle Nanite representation invalidation on load:
|
|
if (!HasAnyFlags(RF_ClassDefaultObject) && !FPlatformProperties::RequiresCookedData())
|
|
{
|
|
// FFortniteReleaseBranchCustomObjectVersion::FixupNaniteLandscapeMeshes : Fixup Nanite meshes which were using the wrong material and didn't have proper UVs
|
|
// FFortniteReleaseBranchCustomObjectVersion::RemoveUselessLandscapeMeshesCookedCollisionData : Remove cooked collision data from Nanite landscape meshes, since collisions are handled by ULandscapeHeightfieldCollisionComponent
|
|
// FFortniteReleaseBranchCustomObjectVersion::FixNaniteLandscapeMeshNames : Fix the names of the generated Nanite landscape UStaticMesh so that it's unique in a given package
|
|
// FFortniteMainBranchObjectVersion::FixNaniteLandscapeMeshDDCKey : Fix the non-deterministic hash being used by the generated Nanite landscape UStaticMesh so that it can benefit from DDC sharing if it's identical to a previously uploaded mesh derived data
|
|
if ((GetLinkerCustomVersion(FFortniteReleaseBranchCustomObjectVersion::GUID) < FFortniteReleaseBranchCustomObjectVersion::FixNaniteLandscapeMeshNames)
|
|
|| (GetLinkerCustomVersion(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::FixNaniteLandscapeMeshDDCKey)
|
|
|| CVarForceInvalidateNaniteOnLoad->GetBool())
|
|
{
|
|
// This will force the Nanite meshes to be properly regenerated during the next save :
|
|
InvalidateNaniteRepresentation(/* bCheckContentId = */ false);
|
|
}
|
|
else
|
|
{
|
|
// On load, get rid of the Nanite representation if it's not up-to-date so that it's marked as needing an update and will get fixed by the user when building Nanite data
|
|
InvalidateNaniteRepresentation(/* bCheckContentId = */ true);
|
|
}
|
|
|
|
// Remove RF_Transactional from Nanite components : they're re-created upon transacting now :
|
|
ClearNaniteTransactional();
|
|
}
|
|
|
|
// Keep previous behavior of landscape HLODs if created before the settings were added
|
|
if (GetLinkerCustomVersion(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::LandscapeAddedHLODSettings)
|
|
{
|
|
HLODTextureSizePolicy = ELandscapeHLODTextureSizePolicy::AutomaticSize;
|
|
HLODMeshSourceLODPolicy = ELandscapeHLODMeshSourceLODPolicy::AutomaticLOD;
|
|
}
|
|
#endif // WITH_EDITOR
|
|
}
|
|
|
|
FIntPoint ALandscapeProxy::GetSectionBaseOffset() const
|
|
{
|
|
return LandscapeSectionOffset;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
|
|
void ALandscapeProxy::Destroyed()
|
|
{
|
|
Super::Destroyed();
|
|
|
|
UWorld* World = GetWorld();
|
|
|
|
if (GIsEditor)
|
|
{
|
|
ULandscapeInfo::RecreateLandscapeInfo(World, false);
|
|
|
|
if (SplineComponent)
|
|
{
|
|
SplineComponent->ModifySplines();
|
|
}
|
|
}
|
|
|
|
// Destroy the Nanite component when we get destroyed so that we don't restore a garbage Nanite component (it's non-transactional and will get regenerated anyway)
|
|
InvalidateNaniteRepresentation(/* bInCheckContentId = */false);
|
|
|
|
// unregister feature level changed handler for grass
|
|
if (FeatureLevelChangedDelegateHandle.IsValid())
|
|
{
|
|
World->RemoveOnFeatureLevelChangedHandler(FeatureLevelChangedDelegateHandle);
|
|
FeatureLevelChangedDelegateHandle.Reset();
|
|
}
|
|
}
|
|
|
|
namespace UE::Landscape::Private
|
|
{
|
|
static bool CopyProperty(FProperty* InProperty, UObject* InSourceObject, UObject* InDestinationObject)
|
|
{
|
|
void* SrcValuePtr = InProperty->ContainerPtrToValuePtr<void>(InSourceObject);
|
|
void* DestValuePtr = InProperty->ContainerPtrToValuePtr<void>(InDestinationObject);
|
|
|
|
if ((DestValuePtr == nullptr) || (SrcValuePtr == nullptr))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
InProperty->CopyCompleteValue(DestValuePtr, SrcValuePtr);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool CopyPostEditPropertyByName(const TWeakObjectPtr<ALandscapeProxy>& InLandscapeProxy, const TWeakObjectPtr<ALandscape>& InParentLandscape, const FName& InPropertyName)
|
|
{
|
|
if (!InLandscapeProxy.IsValid() || !InParentLandscape.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
UClass* LandscapeProxyClass = InLandscapeProxy->GetClass();
|
|
|
|
if (LandscapeProxyClass == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FProperty* PropertyToCopy = LandscapeProxyClass->FindPropertyByName(InPropertyName);
|
|
|
|
if (PropertyToCopy == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CopyProperty(PropertyToCopy, InParentLandscape.Get(), InLandscapeProxy.Get());
|
|
|
|
// Some properties may need additional processing (ex: LandscapeMaterial), notify the proxy of the change.
|
|
FPropertyChangedEvent PropertyChangedEvent(PropertyToCopy);
|
|
InLandscapeProxy->PostEditChangeProperty(PropertyChangedEvent);
|
|
|
|
InLandscapeProxy->Modify();
|
|
|
|
return true;
|
|
}
|
|
|
|
static void DisplaySynchronizedPropertiesMapcheckWarning(const TArray<FName>& InSynchronizedProperties, const ALandscapeProxy& InSynchronizedProxy, const ALandscapeProxy& InParentLandscape, const bool bAddSilencingMessage = false)
|
|
{
|
|
check(!InSynchronizedProperties.IsEmpty());
|
|
|
|
TStringBuilder<1024> SynchronizedPropertiesStringBuilder;
|
|
ULandscapeSubsystem* LandscapeSubsystem = InSynchronizedProxy.GetWorld() ? InSynchronizedProxy.GetWorld()->GetSubsystem<ULandscapeSubsystem>() : nullptr;
|
|
checkf(LandscapeSubsystem != nullptr, TEXT("DisplaySynchronizedPropertiesMapcheckWarning can only be called when a subsystem is available"));
|
|
|
|
for (const FName& SynchronizedProperty : InSynchronizedProperties)
|
|
{
|
|
if (SynchronizedPropertiesStringBuilder.Len() > 0)
|
|
{
|
|
SynchronizedPropertiesStringBuilder.Append(TEXT(", "));
|
|
}
|
|
|
|
SynchronizedPropertiesStringBuilder.Append(SynchronizedProperty.ToString());
|
|
}
|
|
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("Proxy"), FText::FromString(InSynchronizedProxy.GetActorNameOrLabel()));
|
|
Arguments.Add(TEXT("Landscape"), FText::FromString(InParentLandscape.GetActorNameOrLabel()));
|
|
TSharedRef<FTokenizedMessage> Message = FMessageLog("MapCheck").Warning()
|
|
->AddToken(FUObjectToken::Create(&InSynchronizedProxy, FText::FromString(InSynchronizedProxy.GetActorNameOrLabel())))
|
|
->AddToken(FTextToken::Create(LOCTEXT("MapCheck_Message_LandscapeProxy_FixupSharedData", "had some shared properties not in sync with its parent landscape actor. This has been fixed but the proxy needs to be saved in order to ensure cooking behaves as expected. ")))
|
|
->AddToken(FActionToken::Create(LOCTEXT("MapCheck_SaveFixedUpData", "Save Modified Landscapes"), LOCTEXT("MapCheck_SaveFixedUpData_Desc", "Saves the modified landscape proxy actors"),
|
|
FOnActionTokenExecuted::CreateUObject(LandscapeSubsystem, &ULandscapeSubsystem::SaveModifiedLandscapes, UE::Landscape::EBuildFlags::WriteFinalLog),
|
|
FCanExecuteActionToken::CreateUObject(LandscapeSubsystem, &ULandscapeSubsystem::HasModifiedLandscapes),
|
|
/*bInSingleUse = */false))
|
|
->AddToken(FTextToken::Create(FText::Format(LOCTEXT("MapCheck_Message_LandscapeProxy_FixupSharedData_SharedProperties", "The following properties were synchronized: {0}."), FText::FromString(SynchronizedPropertiesStringBuilder.ToString()))));
|
|
|
|
if (bAddSilencingMessage)
|
|
{
|
|
Message->AddToken(FTextToken::Create(LOCTEXT("MapCheck_Message_LandscapeProxy_SilenceWarning", "You can silence this warning and perform the deprecation silently using the landscape.SilenceSharedPropertyDeprecationFixup CVar. ")));
|
|
}
|
|
|
|
Message->AddToken(FMapErrorToken::Create(FMapErrors::LandscapeComponentPostLoad_Warning));
|
|
|
|
// Show MapCheck window
|
|
FMessageLog("MapCheck").Open(EMessageSeverity::Warning);
|
|
}
|
|
} // namespace UE::Landscape::Private
|
|
|
|
void ALandscapeProxy::CopySharedProperties(ALandscapeProxy* InLandscape)
|
|
{
|
|
SynchronizeUnmarkedSharedProperties(InLandscape);
|
|
|
|
for (TFieldIterator<FProperty> PropertyIterator(GetClass()); PropertyIterator; ++PropertyIterator)
|
|
{
|
|
FProperty* Property = *PropertyIterator;
|
|
|
|
if (Property == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (IsSharedProperty(Property))
|
|
{
|
|
UE::Landscape::Private::CopyProperty(Property, InLandscape, this);
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<FName> ALandscapeProxy::SynchronizeSharedProperties(ALandscapeProxy* InLandscape)
|
|
{
|
|
TArray<FName> SynchronizedProperties = SynchronizeUnmarkedSharedProperties(InLandscape);
|
|
|
|
for (TFieldIterator<FProperty> PropertyIterator(GetClass()); PropertyIterator; ++PropertyIterator)
|
|
{
|
|
FProperty* Property = *PropertyIterator;
|
|
|
|
if (Property == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ((IsPropertyInherited(Property) ||
|
|
(IsPropertyOverridable(Property) && !IsSharedPropertyOverridden(Property->GetFName()))) &&
|
|
!Property->Identical_InContainer(this, InLandscape))
|
|
{
|
|
SynchronizedProperties.Emplace(Property->GetFName());
|
|
UE::Landscape::Private::CopyProperty(Property, InLandscape, this);
|
|
}
|
|
}
|
|
|
|
if (!SynchronizedProperties.IsEmpty())
|
|
{
|
|
Modify();
|
|
}
|
|
|
|
return SynchronizedProperties;
|
|
}
|
|
|
|
bool ALandscapeProxy::IsSharedProperty(const FName& InPropertyName) const
|
|
{
|
|
FProperty* Property = StaticClass()->FindPropertyByName(InPropertyName);
|
|
|
|
return IsSharedProperty(Property);
|
|
}
|
|
|
|
bool ALandscapeProxy::IsSharedProperty(const FProperty* InProperty) const
|
|
{
|
|
return IsPropertyInherited(InProperty) || IsPropertyOverridable(InProperty);
|
|
}
|
|
|
|
bool ALandscapeProxy::IsPropertyInherited(const FProperty* InProperty) const
|
|
{
|
|
if (InProperty == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return InProperty->HasMetaData(LandscapeInheritedTag);
|
|
}
|
|
|
|
bool ALandscapeProxy::IsPropertyOverridable(const FProperty* InProperty) const
|
|
{
|
|
if (InProperty == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return InProperty->HasMetaData(LandscapeOverridableTag);
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
UMaterialInterface* ALandscapeProxy::GetLandscapeMaterial(int8 InLODIndex) const
|
|
{
|
|
if (InLODIndex != INDEX_NONE)
|
|
{
|
|
UWorld* World = GetWorld();
|
|
|
|
if (World != nullptr)
|
|
{
|
|
if (const FLandscapePerLODMaterialOverride* LocalMaterialOverride = PerLODOverrideMaterials.FindByPredicate(
|
|
[InLODIndex](const FLandscapePerLODMaterialOverride& InOverride) { return (InOverride.LODIndex == InLODIndex) && (InOverride.Material != nullptr); }))
|
|
{
|
|
return LocalMaterialOverride->Material;
|
|
}
|
|
}
|
|
}
|
|
|
|
return LandscapeMaterial != nullptr ? LandscapeMaterial : UMaterial::GetDefaultMaterial(MD_Surface);
|
|
}
|
|
|
|
UMaterialInterface* ALandscapeProxy::GetLandscapeHoleMaterial() const
|
|
{
|
|
return LandscapeHoleMaterial;
|
|
}
|
|
|
|
UMaterialInterface* ALandscapeStreamingProxy::GetLandscapeMaterial(int8 InLODIndex) const
|
|
{
|
|
if (InLODIndex != INDEX_NONE)
|
|
{
|
|
UWorld* World = GetWorld();
|
|
|
|
if (World != nullptr)
|
|
{
|
|
if (const FLandscapePerLODMaterialOverride* LocalMaterialOverride = PerLODOverrideMaterials.FindByPredicate(
|
|
[InLODIndex](const FLandscapePerLODMaterialOverride& InOverride) { return (InOverride.LODIndex == InLODIndex) && (InOverride.Material != nullptr); }))
|
|
{
|
|
return LocalMaterialOverride->Material;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (LandscapeMaterial != nullptr)
|
|
{
|
|
return LandscapeMaterial;
|
|
}
|
|
|
|
if (const ALandscape* Landscape = GetLandscapeActor())
|
|
{
|
|
return Landscape->GetLandscapeMaterial(InLODIndex);
|
|
}
|
|
|
|
return UMaterial::GetDefaultMaterial(MD_Surface);
|
|
}
|
|
|
|
UMaterialInterface* ALandscapeStreamingProxy::GetLandscapeHoleMaterial() const
|
|
{
|
|
if (LandscapeHoleMaterial)
|
|
{
|
|
return LandscapeHoleMaterial;
|
|
}
|
|
else if (const ALandscape* Landscape = GetLandscapeActor())
|
|
{
|
|
return Landscape->GetLandscapeHoleMaterial();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
|
|
bool ALandscapeStreamingProxy::IsSharedPropertyOverridden(const FName& InPropertyName) const
|
|
{
|
|
return OverriddenSharedProperties.Contains(InPropertyName);
|
|
}
|
|
|
|
void ALandscapeStreamingProxy::SetSharedPropertyOverride(const FName& InPropertyName, const bool bIsOverridden)
|
|
{
|
|
check(IsSharedProperty(InPropertyName));
|
|
|
|
Modify();
|
|
|
|
if (bIsOverridden)
|
|
{
|
|
OverriddenSharedProperties.Add(InPropertyName);
|
|
}
|
|
else
|
|
{
|
|
TWeakObjectPtr<ALandscapeProxy> LandscapeProxy = this;
|
|
TWeakObjectPtr<ALandscape> ParentLandscape = GetLandscapeActor();
|
|
|
|
if (!ParentLandscape.IsValid())
|
|
{
|
|
UE_LOG(LogLandscape, Warning, TEXT("Unable to retrieve the parent landscape's shared property value (ALandscapeStreamingProxy: %s, Property: %s). The proper value will be fixedup when reloading this proxy."),
|
|
*GetFullName(), *InPropertyName.ToString());
|
|
}
|
|
else
|
|
{
|
|
UE::Landscape::Private::CopyPostEditPropertyByName(LandscapeProxy, ParentLandscape, InPropertyName);
|
|
}
|
|
|
|
OverriddenSharedProperties.Remove(InPropertyName);
|
|
}
|
|
}
|
|
|
|
void ALandscapeStreamingProxy::FixupOverriddenSharedProperties()
|
|
{
|
|
const UClass* StreamingProxyClass = StaticClass();
|
|
|
|
for (const FName& PropertyName : OverriddenSharedProperties)
|
|
{
|
|
const FProperty* Property = StreamingProxyClass->FindPropertyByName(PropertyName);
|
|
checkf(Property != nullptr, TEXT("An overridden property is referenced but cannot be found. Please check this property hasn't been renamed or deprecated and/or provide the proper adapting mechanism."));
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::UpgradeSharedProperties(ALandscape* InParentLandscape)
|
|
{
|
|
TArray<FName> SynchronizedProperties;
|
|
bool bOpenMapCheckWindow = false;
|
|
ULandscapeInfo* LandscapeInfo = GetLandscapeInfo();
|
|
checkf(LandscapeInfo != nullptr, TEXT("UpgradeSharedProperties can only be called after the proxies are registered to ULandscapeInfo"));
|
|
|
|
for (TFieldIterator<FProperty> PropertyIterator(GetClass()); PropertyIterator; ++PropertyIterator)
|
|
{
|
|
FProperty* Property = *PropertyIterator;
|
|
|
|
if (Property == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (IsPropertyInherited(Property) && !Property->Identical_InContainer(this, InParentLandscape))
|
|
{
|
|
SynchronizedProperties.Emplace(Property->GetFName());
|
|
UE::Landscape::Private::CopyProperty(Property, InParentLandscape, this);
|
|
}
|
|
else if (IsPropertyOverridable(Property) && !IsSharedPropertyOverridden(Property->GetFName()) && !Property->Identical_InContainer(this, InParentLandscape))
|
|
{
|
|
if (CVarSilenceSharedPropertyDeprecationFixup->GetBool())
|
|
{
|
|
SetSharedPropertyOverride(Property->GetFName(), true);
|
|
}
|
|
else
|
|
{
|
|
FFormatNamedArguments Arguments;
|
|
TWeakObjectPtr<ALandscapeProxy> LandscapeProxy = this;
|
|
TWeakObjectPtr<ALandscape> ParentLandscape = InParentLandscape;
|
|
const FName PropertyName = Property->GetFName();
|
|
|
|
bOpenMapCheckWindow = true;
|
|
|
|
Arguments.Add(TEXT("Proxy"), FText::FromString(GetActorNameOrLabel()));
|
|
Arguments.Add(TEXT("Landscape"), FText::FromString(InParentLandscape->GetActorNameOrLabel()));
|
|
FMessageLog("MapCheck").Warning()
|
|
->AddToken(FUObjectToken::Create(this, FText::FromString(GetActorNameOrLabel())))
|
|
->AddToken(FTextToken::Create(FText::Format(LOCTEXT("MapCheck_Message_LandscapeProxy_UpgradeSharedProperties", "Contains a property ({0}) different from parent's landscape actor. Please select between "), FText::FromString(PropertyName.ToString()))))
|
|
->AddToken(FActionToken::Create(LOCTEXT("MapCheck_OverrideProperty", "Override property"), LOCTEXT("MapCheck_OverrideProperty_Desc", "Keeping the current value and marking the property as overriding the parent landscape's value."),
|
|
FOnActionTokenExecuted::CreateLambda([LandscapeProxy, PropertyName]()
|
|
{
|
|
if (LandscapeProxy.IsValid())
|
|
{
|
|
LandscapeProxy->SetSharedPropertyOverride(PropertyName, true);
|
|
}
|
|
}),
|
|
/*bInSingleUse = */true))
|
|
->AddToken(FTextToken::Create(LOCTEXT("MapCheck_Message_LandscapeProxy_UpgradeSharedProperties_Or", " or ")))
|
|
->AddToken(FActionToken::Create(LOCTEXT("MapCheck_InheritProperty", "Inherit from parent landscape"), LOCTEXT("MapCheck_InheritProperty_Desc", "Copying the parent landscape's value for this property."),
|
|
FOnActionTokenExecuted::CreateLambda([LandscapeProxy, ParentLandscape, PropertyName]()
|
|
{
|
|
UE::Landscape::Private::CopyPostEditPropertyByName(LandscapeProxy, ParentLandscape, PropertyName);
|
|
}),
|
|
/*bInSingleUse = */true))
|
|
->AddToken(FMapErrorToken::Create(FMapErrors::LandscapeComponentPostLoad_Warning));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!SynchronizedProperties.IsEmpty())
|
|
{
|
|
// This function may be called from PostLoad, in which case InParentLandscape will be non-null. Pass it along to LandscapeInfo so that if the landscape actor has not registered to the
|
|
// landscape info yet, it can still retrieve it via this direct pointer :
|
|
LandscapeInfo->MarkObjectDirty(/*InObject = */this, /*bInForceResave = */true, InParentLandscape);
|
|
|
|
if (!CVarSilenceSharedPropertyDeprecationFixup->GetBool())
|
|
{
|
|
UE::Landscape::Private::DisplaySynchronizedPropertiesMapcheckWarning(SynchronizedProperties, /*InSynchronizedProxy = */*this, *InParentLandscape, /*bAddSilencingMessage = */true);
|
|
}
|
|
}
|
|
|
|
if (bOpenMapCheckWindow)
|
|
{
|
|
// Show MapCheck window
|
|
FMessageLog("MapCheck").Open(EMessageSeverity::Warning);
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::FixupSharedData(ALandscape* Landscape, const bool bMapCheck)
|
|
{
|
|
if ((Landscape == nullptr) || (Landscape == this))
|
|
{
|
|
return;
|
|
}
|
|
|
|
const bool bUpgradeSharedPropertiesPerformedBefore = bUpgradeSharedPropertiesPerformed;
|
|
if (!bUpgradeSharedPropertiesPerformed &&
|
|
((GetLinkerCustomVersion(FFortniteReleaseBranchCustomObjectVersion::GUID) < FFortniteReleaseBranchCustomObjectVersion::LandscapeSharedPropertiesEnforcement)
|
|
|| (GetLinkerCustomVersion(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::LandscapeBodyInstanceAsSharedProperty)))
|
|
{
|
|
UpgradeSharedProperties(Landscape);
|
|
bUpgradeSharedPropertiesPerformed = true;
|
|
}
|
|
else
|
|
{
|
|
ULandscapeInfo* LandscapeInfo = GetLandscapeInfo();
|
|
checkf(LandscapeInfo != nullptr, TEXT("FixupSharedData can only be called after the proxies are registered to ULandscapeInfo"));
|
|
|
|
TArray<FName> SynchronizedProperties = SynchronizeSharedProperties(Landscape);
|
|
bool bUpdated = !SynchronizedProperties.IsEmpty();
|
|
|
|
TSet<FGuid> LayerGuids;
|
|
Algo::Transform(Landscape->GetEditLayersConst(), LayerGuids, [](const ULandscapeEditLayerBase* EditLayer) { return EditLayer->GetGuid(); });
|
|
bUpdated |= RemoveObsoleteLayers(LayerGuids);
|
|
|
|
for (const ULandscapeEditLayerBase* EditLayer : Landscape->GetEditLayersConst())
|
|
{
|
|
bUpdated |= AddLayer(EditLayer->GetGuid());
|
|
}
|
|
|
|
if (bUpdated)
|
|
{
|
|
// In cases where LandscapeInfo is not fully ready yet, we forward the provided ALandscape. If ALandscape is available in LandscapeInfo, we let the object function naturally.
|
|
const ALandscape* LandscapeActor = (LandscapeInfo->LandscapeActor == nullptr) ? Landscape : nullptr;
|
|
|
|
// Force resave the proxy through the modified landscape system, so that the user can then use the Build > Save Modified Landscapes (or Build > Build Landscape) button and therefore manually trigger the re-save of all modified proxies. * /
|
|
bool bNeedsManualResave = LandscapeInfo->MarkObjectDirty(/*InObject = */this, /*bInForceResave = */true, LandscapeActor);
|
|
|
|
if (bMapCheck && bNeedsManualResave && !SynchronizedProperties.IsEmpty())
|
|
{
|
|
UE::Landscape::Private::DisplaySynchronizedPropertiesMapcheckWarning(SynchronizedProperties, /*InSynchronizedProxy = */*this, *Landscape);
|
|
}
|
|
}
|
|
}
|
|
|
|
OnLandscapeProxyFixupSharedDataDelegate.Broadcast(/*Proxy = */this, FOnLandscapeProxyFixupSharedDataParams { .Landscape = Landscape, .bUpgradeSharedPropertiesPerformed = bUpgradeSharedPropertiesPerformedBefore });
|
|
}
|
|
|
|
void ALandscapeProxy::SetAbsoluteSectionBase(FIntPoint InSectionBase)
|
|
{
|
|
FIntPoint Difference = InSectionBase - LandscapeSectionOffset;
|
|
LandscapeSectionOffset = InSectionBase;
|
|
|
|
RecreateComponentsRenderState([Difference](ULandscapeComponent* Comp)
|
|
{
|
|
FIntPoint AbsoluteSectionBase = Comp->GetSectionBase() + Difference;
|
|
Comp->SetSectionBase(AbsoluteSectionBase);
|
|
});
|
|
|
|
for (int32 CompIdx = 0; CompIdx < CollisionComponents.Num(); CompIdx++)
|
|
{
|
|
ULandscapeHeightfieldCollisionComponent* Comp = CollisionComponents[CompIdx];
|
|
if (Comp)
|
|
{
|
|
FIntPoint AbsoluteSectionBase = Comp->GetSectionBase() + Difference;
|
|
Comp->SetSectionBase(AbsoluteSectionBase);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::RecreateComponentsState()
|
|
{
|
|
RecreateComponentsRenderState([](ULandscapeComponent* Comp)
|
|
{
|
|
Comp->UpdateComponentToWorld();
|
|
Comp->UpdateCachedBounds();
|
|
Comp->UpdateBounds();
|
|
});
|
|
|
|
for (int32 ComponentIndex = 0; ComponentIndex < CollisionComponents.Num(); ComponentIndex++)
|
|
{
|
|
ULandscapeHeightfieldCollisionComponent* Comp = CollisionComponents[ComponentIndex];
|
|
if (Comp)
|
|
{
|
|
Comp->UpdateComponentToWorld();
|
|
Comp->RecreatePhysicsState();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::RecreateComponentsRenderState(TFunctionRef<void(ULandscapeComponent*)> Fn)
|
|
{
|
|
// Batch component render state recreation
|
|
TArray<FComponentRecreateRenderStateContext> ComponentRecreateRenderStates;
|
|
ComponentRecreateRenderStates.Reserve(LandscapeComponents.Num());
|
|
|
|
for (int32 ComponentIndex = 0; ComponentIndex < LandscapeComponents.Num(); ComponentIndex++)
|
|
{
|
|
ULandscapeComponent* Comp = LandscapeComponents[ComponentIndex];
|
|
if (Comp)
|
|
{
|
|
Fn(Comp);
|
|
ComponentRecreateRenderStates.Emplace(Comp);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ULandscapeInfo::GetDirtyOnlyInMode() const
|
|
{
|
|
if (const ALandscape* Landscape = LandscapeActor.Get())
|
|
{
|
|
if (const UWorld* World = Landscape->GetWorld())
|
|
{
|
|
if (const ULandscapeSubsystem* LandscapeSubsystem = World->GetSubsystem<ULandscapeSubsystem>())
|
|
{
|
|
return LandscapeSubsystem->GetDirtyOnlyInMode();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
FLandscapeDirtyOnlyInModeScope::FLandscapeDirtyOnlyInModeScope(ULandscapeInfo* InLandscapeInfo)
|
|
: LandscapeInfo(InLandscapeInfo)
|
|
, bDirtyOnlyInModePrevious(InLandscapeInfo->bDirtyOnlyInMode)
|
|
{
|
|
LandscapeInfo->bDirtyOnlyInMode = LandscapeInfo->GetDirtyOnlyInMode();
|
|
}
|
|
|
|
|
|
FLandscapeDirtyOnlyInModeScope::FLandscapeDirtyOnlyInModeScope(ULandscapeInfo* InLandscapeInfo, bool bInOverrideDirtyMode)
|
|
: LandscapeInfo(InLandscapeInfo)
|
|
, bDirtyOnlyInModePrevious(InLandscapeInfo->bDirtyOnlyInMode)
|
|
{
|
|
LandscapeInfo->bDirtyOnlyInMode = bInOverrideDirtyMode;
|
|
}
|
|
|
|
FLandscapeDirtyOnlyInModeScope::~FLandscapeDirtyOnlyInModeScope()
|
|
{
|
|
LandscapeInfo->bDirtyOnlyInMode = bDirtyOnlyInModePrevious;
|
|
}
|
|
|
|
void ULandscapeInfo::OnModifiedPackageSaved(UPackage* InPackage)
|
|
{
|
|
ModifiedPackages.Remove(InPackage);
|
|
}
|
|
|
|
TArray<UPackage*> ULandscapeInfo::GetModifiedPackages() const
|
|
{
|
|
TArray<UPackage*> LocalModifiedPackages;
|
|
LocalModifiedPackages.Reserve(ModifiedPackages.Num());
|
|
Algo::TransformIf(ModifiedPackages, LocalModifiedPackages,
|
|
[](const TWeakObjectPtr<UPackage>& InWeakPackagePtr) { return InWeakPackagePtr.IsValid(); },
|
|
[](const TWeakObjectPtr<UPackage>& InWeakPackagePtr) { return InWeakPackagePtr.Get(); });
|
|
return LocalModifiedPackages;
|
|
}
|
|
|
|
bool ULandscapeInfo::IsPackageModified(UPackage* InPackage) const
|
|
{
|
|
return ModifiedPackages.Contains(InPackage);
|
|
}
|
|
|
|
int32 ULandscapeInfo::MarkModifiedPackagesAsDirty()
|
|
{
|
|
int32 NumDirtied = 0;
|
|
for (TWeakObjectPtr<UPackage> WeakPackagePtr : ModifiedPackages)
|
|
{
|
|
if (UPackage* Package = WeakPackagePtr.Get())
|
|
{
|
|
const bool bWasDirty = Package->IsDirty();
|
|
const bool bIsDirty = Package->MarkPackageDirty();
|
|
NumDirtied += (!bWasDirty && bIsDirty) ? 1 : 0;
|
|
}
|
|
}
|
|
ModifiedPackages.Empty();
|
|
|
|
ProcessDeferredDeletions();
|
|
|
|
return NumDirtied;
|
|
}
|
|
|
|
int32 ULandscapeInfo::GetModifiedPackageCount() const
|
|
{
|
|
return IntCastChecked<int32>(Algo::CountIf(ModifiedPackages, [](const TWeakObjectPtr<UPackage>& InWeakPackagePtr) { return InWeakPackagePtr.IsValid(); }));
|
|
}
|
|
|
|
bool ULandscapeInfo::TryAddToModifiedPackages(UPackage* InPackage, const ALandscape* InLandscapeOverride)
|
|
{
|
|
const ALandscape* LocalLandscapeActor = (InLandscapeOverride != nullptr) ? InLandscapeOverride : LandscapeActor.Get();
|
|
check(LocalLandscapeActor);
|
|
|
|
// We don't want to bother with packages being marked dirty for anything else than the Editor world
|
|
if (LocalLandscapeActor->GetWorld()->WorldType != EWorldType::Editor)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Also don't track packages when rolling back a transaction because they are already dirty anyway
|
|
if (GIsTransacting)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// No need to add the package to ModifiedPackages if it's already dirty.
|
|
if (InPackage->IsDirty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Don't consider unsaved packages as modified/not dirty because they will be saved later on anyway. What we're really after are existing packages made dirty on load
|
|
if (FPackageName::IsTempPackage(InPackage->GetName()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ModifiedPackages.Add(InPackage);
|
|
return true;
|
|
}
|
|
|
|
bool ULandscapeInfo::MarkObjectDirty(UObject* InObject, bool bInForceResave, const ALandscape* InLandscapeOverride)
|
|
{
|
|
check(InObject);
|
|
|
|
bool bWasAddedToModifiedPackages = false;
|
|
if (bInForceResave)
|
|
{
|
|
if (!InObject->MarkPackageDirty())
|
|
{
|
|
// When force-resaving (e.g. when syncing must-sync properties on load), unconditionally add the package to the list of packages to save if we couldn't mark it dirty already, so that
|
|
// the user can manually resave all that needs to be saved with the Build > Save Modified Landscapes (or Build > Build Landscape) button :
|
|
bWasAddedToModifiedPackages = TryAddToModifiedPackages(InObject->GetPackage(), InLandscapeOverride);
|
|
}
|
|
}
|
|
else if (bDirtyOnlyInMode)
|
|
{
|
|
const ALandscape* LocalLandscapeActor = (InLandscapeOverride != nullptr) ? InLandscapeOverride : LandscapeActor.Get();
|
|
check(LocalLandscapeActor);
|
|
if (LocalLandscapeActor->HasLandscapeEdMode())
|
|
{
|
|
InObject->MarkPackageDirty();
|
|
}
|
|
else
|
|
{
|
|
bWasAddedToModifiedPackages = TryAddToModifiedPackages(InObject->GetPackage(), InLandscapeOverride);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
InObject->MarkPackageDirty();
|
|
}
|
|
|
|
return bWasAddedToModifiedPackages;
|
|
}
|
|
|
|
bool ULandscapeInfo::ModifyObject(UObject* InObject, bool bAlwaysMarkDirty)
|
|
{
|
|
check(InObject && (InObject->IsA<ALandscapeProxy>() || InObject->GetTypedOuter<ALandscapeProxy>() != nullptr));
|
|
bool bWasAddedToModifiedPackages = false;
|
|
|
|
if (!bAlwaysMarkDirty)
|
|
{
|
|
InObject->Modify(false);
|
|
}
|
|
else if(!bDirtyOnlyInMode)
|
|
{
|
|
InObject->Modify(true);
|
|
}
|
|
else
|
|
{
|
|
ALandscape* LocalLandscapeActor = LandscapeActor.Get();
|
|
check(LocalLandscapeActor);
|
|
if (LocalLandscapeActor->HasLandscapeEdMode())
|
|
{
|
|
InObject->Modify(true);
|
|
// We just marked the package dirty, no need to keep track of it with ModifiedPackages.
|
|
ModifiedPackages.Remove(InObject->GetPackage());
|
|
}
|
|
else
|
|
{
|
|
InObject->Modify(false);
|
|
bWasAddedToModifiedPackages = TryAddToModifiedPackages(InObject->GetPackage());
|
|
}
|
|
}
|
|
|
|
return bWasAddedToModifiedPackages;
|
|
}
|
|
|
|
void ULandscapeInfo::DeleteActorWhenApplyingModifiedStatus(AActor* InActor, bool bInAllowUI)
|
|
{
|
|
check(InActor->GetWorld() && InActor->GetWorld()->WorldType == EWorldType::Editor);
|
|
|
|
// If we can mark the package dirty, then we can also delete it right away. If we can't, then enqueue it for deletion at the same time as other deferred package dirtying.
|
|
if (InActor->MarkPackageDirty())
|
|
{
|
|
UE::Landscape::DeleteActors({ InActor }, InActor->GetWorld(), bInAllowUI);
|
|
}
|
|
else
|
|
{
|
|
ActorsToDelete.Add(InActor);
|
|
}
|
|
}
|
|
|
|
void ULandscapeInfo::ProcessDeferredDeletions()
|
|
{
|
|
TArray<AActor*> FinalActorsToDelete;
|
|
FinalActorsToDelete.Reserve(ActorsToDelete.Num());
|
|
for (TWeakObjectPtr<AActor> WeakActorPtr : ActorsToDelete)
|
|
{
|
|
if (AActor* Actor = WeakActorPtr.Get())
|
|
{
|
|
FinalActorsToDelete.Add(Actor);
|
|
}
|
|
}
|
|
if (!FinalActorsToDelete.IsEmpty())
|
|
{
|
|
ensure(UE::Landscape::DeleteActors(FinalActorsToDelete, FinalActorsToDelete[0]->GetWorld(), /* bInAllowUI = */true));
|
|
}
|
|
ActorsToDelete.Empty();
|
|
}
|
|
|
|
void ULandscapeInfo::DirtyRuntimeVirtualTextureForLandscapeArea(int32 X1, int32 Y1, int32 X2, int32 Y2) const
|
|
{
|
|
FBox DirtyWorldBounds(ForceInit);
|
|
|
|
// Iterate touched components to find touched runtime virtual textures.
|
|
TSet<ULandscapeComponent*> Components;
|
|
GetComponentsInRegion(X1 + 1, Y1 + 1, X2 - 1, Y2 - 1, Components);
|
|
|
|
TArray<URuntimeVirtualTexture*> RuntimeVirtualTextures;
|
|
for (ULandscapeComponent* Component : Components)
|
|
{
|
|
ALandscapeProxy* Landscape = Component->GetLandscapeProxy();
|
|
if (Landscape != nullptr && Landscape->RuntimeVirtualTextures.Num() > 0)
|
|
{
|
|
for (URuntimeVirtualTexture* RuntimeVirtualTexture : Landscape->RuntimeVirtualTextures)
|
|
{
|
|
RuntimeVirtualTextures.AddUnique(RuntimeVirtualTexture);
|
|
}
|
|
|
|
// Also accumulate bounds in world space.
|
|
const int32 LocalX1 = FMath::Max(X1, Component->GetSectionBase().X) - Component->GetSectionBase().X;
|
|
const int32 LocalY1 = FMath::Max(Y1, Component->GetSectionBase().Y) - Component->GetSectionBase().Y;
|
|
const int32 LocalX2 = FMath::Min(X2, Component->GetSectionBase().X + ComponentSizeQuads) - Component->GetSectionBase().X;
|
|
const int32 LocalY2 = FMath::Min(Y2, Component->GetSectionBase().Y + ComponentSizeQuads) - Component->GetSectionBase().Y;
|
|
const FBox LocalDirtyBounds(FVector(LocalX1, LocalY1, 0), FVector(LocalX2, LocalY2, 1));
|
|
|
|
DirtyWorldBounds += LocalDirtyBounds.TransformBy(Component->GetComponentToWorld());
|
|
}
|
|
}
|
|
|
|
// Find matching runtime virtual texture components and invalidate dirty region.
|
|
if (RuntimeVirtualTextures.Num() > 0)
|
|
{
|
|
for (TObjectIterator<URuntimeVirtualTextureComponent> It(/*AdditionalExclusionFlags = */RF_ClassDefaultObject, /*bIncludeDerivedClasses = */true, /*InInternalExclusionFlags = */EInternalObjectFlags::Garbage); It; ++It)
|
|
{
|
|
if (It->GetVirtualTexture() != nullptr && RuntimeVirtualTextures.Contains(It->GetVirtualTexture()))
|
|
{
|
|
It->Invalidate(FBoxSphereBounds(DirtyWorldBounds), GLandscapePrioritizeDirtyRVTPages ? EVTInvalidatePriority::High : EVTInvalidatePriority::Normal);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ALandscapeProxy* ULandscapeInfo::GetLandscapeProxyForLevel(ULevel* Level) const
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(ULandscapeInfo::GetLandscapeProxyForLevel);
|
|
ALandscapeProxy* LandscapeProxy = nullptr;
|
|
ForEachLandscapeProxy([&LandscapeProxy, Level](ALandscapeProxy* Proxy) -> bool
|
|
{
|
|
if (Proxy->GetLevel() == Level)
|
|
{
|
|
LandscapeProxy = Proxy;
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
return LandscapeProxy;
|
|
}
|
|
|
|
#endif // WITH_EDITOR
|
|
|
|
ALandscapeProxy* ULandscapeInfo::GetCurrentLevelLandscapeProxy(bool bRegistered) const
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(ULandscapeInfo::GetCurrentLevelLandscapeProxy);
|
|
|
|
ALandscapeProxy* LandscapeProxy = nullptr;
|
|
ForEachLandscapeProxy([&LandscapeProxy, bRegistered](ALandscapeProxy* Proxy) -> bool
|
|
{
|
|
if (!bRegistered || (Proxy->GetRootComponent() && Proxy->GetRootComponent()->IsRegistered()))
|
|
{
|
|
UWorld* ProxyWorld = Proxy->GetWorld();
|
|
if (ProxyWorld &&
|
|
ProxyWorld->GetCurrentLevel() == Proxy->GetOuter())
|
|
{
|
|
LandscapeProxy = Proxy;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
return LandscapeProxy;
|
|
}
|
|
|
|
ALandscapeProxy* ULandscapeInfo::GetLandscapeProxy() const
|
|
{
|
|
// Mostly this Proxy used to calculate transformations
|
|
// in Editor all proxies of same landscape actor have root components in same locations
|
|
// so it doesn't really matter which proxy we return here
|
|
|
|
// prefer LandscapeActor in case it is loaded
|
|
if (LandscapeActor.IsValid())
|
|
{
|
|
ALandscape* Landscape = LandscapeActor.Get();
|
|
USceneComponent* LandscapeRootComponent = (Landscape != nullptr) ? Landscape->GetRootComponent() : nullptr;
|
|
|
|
if ((LandscapeRootComponent != nullptr) && (LandscapeRootComponent->IsRegistered()))
|
|
{
|
|
return Landscape;
|
|
}
|
|
}
|
|
|
|
// prefer current level proxy
|
|
if (ALandscapeProxy* Proxy = GetCurrentLevelLandscapeProxy(true))
|
|
{
|
|
return Proxy;
|
|
}
|
|
|
|
// any proxy in the world
|
|
for (TWeakObjectPtr<ALandscapeStreamingProxy> ProxyPtr : StreamingProxies)
|
|
{
|
|
ALandscapeStreamingProxy* Proxy = ProxyPtr.Get();
|
|
USceneComponent* ProxyRootComponent = (Proxy != nullptr) ? Proxy->GetRootComponent() : nullptr;
|
|
|
|
if ((ProxyRootComponent != nullptr) && (ProxyRootComponent->IsRegistered()))
|
|
{
|
|
return Proxy;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
|
|
void ULandscapeInfo::Reset()
|
|
{
|
|
LandscapeActor.Reset();
|
|
|
|
StreamingProxies.Empty();
|
|
XYtoComponentMap.Empty();
|
|
XYtoAddCollisionMap.Empty();
|
|
|
|
//SelectedComponents.Empty();
|
|
//SelectedRegionComponents.Empty();
|
|
//SelectedRegion.Empty();
|
|
}
|
|
|
|
void ULandscapeInfo::FixupProxiesTransform(bool bDirty)
|
|
{
|
|
ALandscape* Landscape = LandscapeActor.Get();
|
|
|
|
if ((Landscape == nullptr)
|
|
|| (Landscape->GetRootComponent() == nullptr)
|
|
|| !Landscape->GetRootComponent()->IsRegistered())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Make sure section offset of all proxies is multiple of ALandscapeProxy::ComponentSizeQuads
|
|
for (TWeakObjectPtr<ALandscapeStreamingProxy> ProxyPtr : StreamingProxies)
|
|
{
|
|
ALandscapeProxy* Proxy = ProxyPtr.Get();
|
|
if (!Proxy)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (bDirty)
|
|
{
|
|
Proxy->Modify();
|
|
}
|
|
|
|
FIntPoint LandscapeSectionOffset = Proxy->LandscapeSectionOffset - Landscape->LandscapeSectionOffset;
|
|
FIntPoint LandscapeSectionOffsetRem(
|
|
LandscapeSectionOffset.X % Proxy->ComponentSizeQuads,
|
|
LandscapeSectionOffset.Y % Proxy->ComponentSizeQuads);
|
|
|
|
if (LandscapeSectionOffsetRem.X != 0 || LandscapeSectionOffsetRem.Y != 0)
|
|
{
|
|
FIntPoint NewLandscapeSectionOffset = Proxy->LandscapeSectionOffset - LandscapeSectionOffsetRem;
|
|
|
|
UE_LOG(LogLandscape, Warning, TEXT("Landscape section base is not multiple of component size, attempted automated fix: '%s', %d,%d vs %d,%d."),
|
|
*Proxy->GetFullName(), Proxy->LandscapeSectionOffset.X, Proxy->LandscapeSectionOffset.Y, NewLandscapeSectionOffset.X, NewLandscapeSectionOffset.Y);
|
|
|
|
Proxy->SetAbsoluteSectionBase(NewLandscapeSectionOffset);
|
|
}
|
|
}
|
|
|
|
FTransform LandscapeTM = Landscape->LandscapeActorToWorld();
|
|
// Update transformations of all linked landscape proxies
|
|
for (TWeakObjectPtr<ALandscapeStreamingProxy> ProxyPtr : StreamingProxies)
|
|
{
|
|
ALandscapeProxy* Proxy = ProxyPtr.Get();
|
|
if (!Proxy)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FTransform ProxyRelativeTM(FVector(Proxy->LandscapeSectionOffset));
|
|
FTransform ProxyTransform = ProxyRelativeTM * LandscapeTM;
|
|
|
|
if (!Proxy->GetTransform().Equals(ProxyTransform))
|
|
{
|
|
Proxy->SetActorTransform(ProxyTransform);
|
|
|
|
// Let other systems know that an actor was moved
|
|
GEngine->BroadcastOnActorMoved(Proxy);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeInfo::UpdateComponentLayerAllowList()
|
|
{
|
|
ForEachLandscapeProxy([](ALandscapeProxy* Proxy)
|
|
{
|
|
for (ULandscapeComponent* Comp : Proxy->LandscapeComponents)
|
|
{
|
|
Comp->UpdateLayerAllowListFromPaintedLayers();
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
void ULandscapeInfo::RecreateLandscapeInfo(UWorld* InWorld, bool bMapCheck, bool bKeepRegistrationStatus)
|
|
{
|
|
check(InWorld);
|
|
|
|
ULandscapeInfoMap& LandscapeInfoMap = ULandscapeInfoMap::GetLandscapeInfoMap(InWorld);
|
|
LandscapeInfoMap.Modify();
|
|
|
|
// reset all LandscapeInfo objects
|
|
for (auto& LandscapeInfoPair : LandscapeInfoMap.Map)
|
|
{
|
|
ULandscapeInfo* LandscapeInfo = LandscapeInfoPair.Value;
|
|
|
|
if (LandscapeInfo != nullptr)
|
|
{
|
|
LandscapeInfo->Modify();
|
|
|
|
// this effectively unregisters all proxies, but does not flag them as unregistered
|
|
// so we can use the flags below to determine what was previously registered
|
|
LandscapeInfo->Reset();
|
|
}
|
|
}
|
|
|
|
TMap<FGuid, TArray<ALandscapeProxy*>> ValidLandscapesMap;
|
|
// Gather all valid landscapes in the world
|
|
for (ALandscapeProxy* Proxy : TActorRange<ALandscapeProxy>(InWorld))
|
|
{
|
|
if (Proxy->GetLevel() &&
|
|
Proxy->GetLevel()->bIsVisible &&
|
|
!Proxy->HasAnyFlags(RF_BeginDestroyed) &&
|
|
IsValid(Proxy) &&
|
|
(!bKeepRegistrationStatus || Proxy->bIsRegisteredWithLandscapeInfo) &&
|
|
!Proxy->IsPendingKillPending())
|
|
{
|
|
ValidLandscapesMap.FindOrAdd(Proxy->GetLandscapeGuid()).Add(Proxy);
|
|
}
|
|
}
|
|
|
|
// Register landscapes in global landscape map
|
|
for (auto& ValidLandscapesPair : ValidLandscapesMap)
|
|
{
|
|
auto& LandscapeList = ValidLandscapesPair.Value;
|
|
for (ALandscapeProxy* Proxy : LandscapeList)
|
|
{
|
|
// note this may re-register already registered actors
|
|
Proxy->CreateLandscapeInfo()->RegisterActor(Proxy, bMapCheck);
|
|
}
|
|
}
|
|
|
|
// Remove empty entries from global LandscapeInfo map
|
|
for (auto It = LandscapeInfoMap.Map.CreateIterator(); It; ++It)
|
|
{
|
|
ULandscapeInfo* Info = It.Value();
|
|
|
|
if (Info != nullptr && Info->GetLandscapeProxy() == nullptr)
|
|
{
|
|
Info->MarkAsGarbage();
|
|
It.RemoveCurrent();
|
|
}
|
|
else if (Info == nullptr) // remove invalid entry
|
|
{
|
|
It.RemoveCurrent();
|
|
}
|
|
}
|
|
|
|
// We need to inform Landscape editor tools about LandscapeInfo updates
|
|
FEditorSupportDelegates::WorldChange.Broadcast();
|
|
}
|
|
|
|
|
|
#endif // WITH_EDITOR
|
|
|
|
ULandscapeInfo* ULandscapeInfo::Find(UWorld* InWorld, const FGuid& LandscapeGuid)
|
|
{
|
|
ULandscapeInfo* LandscapeInfo = nullptr;
|
|
if (InWorld != nullptr && LandscapeGuid.IsValid())
|
|
{
|
|
auto& LandscapeInfoMap = ULandscapeInfoMap::GetLandscapeInfoMap(InWorld);
|
|
LandscapeInfo = LandscapeInfoMap.Map.FindRef(LandscapeGuid);
|
|
}
|
|
return LandscapeInfo;
|
|
}
|
|
|
|
ULandscapeInfo* ULandscapeInfo::FindOrCreate(UWorld* InWorld, const FGuid& LandscapeGuid)
|
|
{
|
|
ULandscapeInfo* LandscapeInfo = nullptr;
|
|
|
|
check(LandscapeGuid.IsValid());
|
|
check(InWorld);
|
|
|
|
auto& LandscapeInfoMap = ULandscapeInfoMap::GetLandscapeInfoMap(InWorld);
|
|
LandscapeInfo = LandscapeInfoMap.Map.FindRef(LandscapeGuid);
|
|
|
|
if (!LandscapeInfo)
|
|
{
|
|
LandscapeInfo = NewObject<ULandscapeInfo>(GetTransientPackage(), NAME_None, RF_Transactional | RF_Transient);
|
|
LandscapeInfoMap.Modify(false);
|
|
LandscapeInfo->Initialize(InWorld, LandscapeGuid);
|
|
LandscapeInfoMap.Map.Add(LandscapeGuid, LandscapeInfo);
|
|
}
|
|
check(LandscapeInfo);
|
|
return LandscapeInfo;
|
|
}
|
|
|
|
void ULandscapeInfo::Initialize(UWorld* InWorld, const FGuid& InLandscapeGuid)
|
|
{
|
|
LandscapeGuid = InLandscapeGuid;
|
|
}
|
|
|
|
void ULandscapeInfo::ForEachLandscapeProxy(TFunctionRef<bool(ALandscapeProxy*)> Fn) const
|
|
{
|
|
if (ALandscape* Landscape = LandscapeActor.Get())
|
|
{
|
|
if (!Landscape->IsPendingKillPending())
|
|
{
|
|
if ( !Fn(Landscape))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
for (TWeakObjectPtr<ALandscapeStreamingProxy> StreamingProxyPtr : StreamingProxies)
|
|
{
|
|
if (ALandscapeProxy* LandscapeProxy = StreamingProxyPtr.Get())
|
|
{
|
|
if (!LandscapeProxy->IsPendingKillPending())
|
|
{
|
|
if (!Fn(LandscapeProxy))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeInfo::UpdateNanite(const ITargetPlatform* InTargetPlatform)
|
|
{
|
|
ALandscape* Landscape = LandscapeActor.Get();
|
|
if (!Landscape)
|
|
{
|
|
return;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (!Landscape->IsNaniteEnabled())
|
|
{
|
|
return;
|
|
}
|
|
|
|
UWorld* World = Landscape->GetWorld();
|
|
bool bDoFinishAllNaniteBuildsInFlightNow = false;
|
|
ForEachLandscapeProxy([&bDoFinishAllNaniteBuildsInFlightNow, InTargetPlatform](ALandscapeProxy* LandscapeProxy)
|
|
{
|
|
FGraphEventRef GraphEvent = LandscapeProxy->UpdateNaniteRepresentationAsync(InTargetPlatform);
|
|
bDoFinishAllNaniteBuildsInFlightNow |= GraphEvent.IsValid();
|
|
|
|
return true;
|
|
});
|
|
|
|
|
|
if ((World != nullptr) && bDoFinishAllNaniteBuildsInFlightNow)
|
|
{
|
|
ULandscapeSubsystem* LandscapeSubsystem = World->GetSubsystem<ULandscapeSubsystem>();
|
|
const bool bAllNaniteBuildsDone = LandscapeSubsystem->FinishAllNaniteBuildsInFlightNow(ULandscapeSubsystem::EFinishAllNaniteBuildsInFlightFlags::Default);
|
|
// Not passing ULandscapeSubsystem::EFinishAllNaniteBuildsInFlightFlags::AllowCancel, so there should be no way that FinishAllNaniteBuildsInFlightNow returns false :
|
|
check(bAllNaniteBuildsDone);
|
|
}
|
|
#endif //WITH_EDITOR
|
|
|
|
}
|
|
|
|
bool ULandscapeInfo::IsRegistered(const ALandscapeProxy* Proxy) const
|
|
{
|
|
if (Proxy == nullptr)
|
|
return false;
|
|
|
|
bool bResult = false;
|
|
if (Proxy->IsA<ALandscape>())
|
|
{
|
|
bResult = (LandscapeActor.Get() == Proxy);
|
|
}
|
|
else if (const ALandscapeStreamingProxy *StreamingProxy = Cast<ALandscapeStreamingProxy>(Proxy))
|
|
{
|
|
TWeakObjectPtr<const ALandscapeStreamingProxy> StreamingProxyPtr = StreamingProxy;
|
|
bResult = StreamingProxies.Contains(StreamingProxyPtr);
|
|
}
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
// NOTE: during an Undo operation, the LandscapeActor/StreamingProxies are transacted, and the registration status may be restored
|
|
// however, in that case, the Proxy is NOT fully registered yet, because some other data in LandscapeInfo still needs to be updated (XY maps for instance are not transacted)
|
|
// so we trust the bIsRegisteredWithLandscapeInfo flag over the actual pointers.
|
|
|
|
// at minimum, if the proxy flag says it is registered, then the pointers should definitely be valid
|
|
if (Proxy->bIsRegisteredWithLandscapeInfo)
|
|
{
|
|
check(bResult == Proxy->bIsRegisteredWithLandscapeInfo);
|
|
}
|
|
|
|
// trust the proxy flag over the landscape info pointers
|
|
bResult = Proxy->bIsRegisteredWithLandscapeInfo;
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
return bResult;
|
|
}
|
|
|
|
|
|
// this function contains all of the registration code that requires the ALandscape actor to be present
|
|
void ULandscapeInfo::RegisterLandscapeActorWithProxyInternal(ALandscapeProxy* Proxy, bool bMapCheck)
|
|
{
|
|
ALandscape* Landscape = LandscapeActor.Get();
|
|
check(Landscape);
|
|
|
|
if (ALandscapeStreamingProxy* StreamingProxy = Cast<ALandscapeStreamingProxy>(Proxy))
|
|
{
|
|
// streaming proxy specific setup here
|
|
StreamingProxy->SetLandscapeActor(Landscape);
|
|
|
|
#if WITH_EDITOR
|
|
StreamingProxy->FixupSharedData(Landscape, bMapCheck);
|
|
#endif // WITH_EDITOR
|
|
|
|
Proxy->bIsLandscapeActorRegisteredWithLandscapeInfo = true;
|
|
FLandscapeGroup::RegisterAllComponentsOnStreamingProxy(StreamingProxy);
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
// generic proxy setup (that requires ALandscape actor) here
|
|
if (bool bLayerInfoMapChanged = UpdateLayerInfoMap(Proxy))
|
|
{
|
|
// The layer info map is part of the main landscape so if it has changed, we need to do another round of shared data fixup on all proxies, so all proxies have their TargetLayers list synchronized.
|
|
// This is a one-time thing because at some point during development, the target layer data was deprecated and the deprecation turned somewhat sour :S
|
|
ForEachLandscapeProxy([this, Landscape, bMapCheck](ALandscapeProxy* Proxy)
|
|
{
|
|
Proxy->FixupSharedData(Landscape, bMapCheck);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
if (GIsEditor)
|
|
{
|
|
// Note: This can happen when loading certain cooked assets in an editor
|
|
// Todo: Determine the root cause of this and fix it at a higher level!
|
|
if (Proxy->LandscapeComponents.Num() > 0 && Proxy->LandscapeComponents[0] == nullptr)
|
|
{
|
|
Proxy->LandscapeComponents.Empty();
|
|
}
|
|
|
|
if (Proxy->WeightmapFixupVersion != Proxy->CurrentVersion)
|
|
{
|
|
Proxy->FixupWeightmaps();
|
|
}
|
|
|
|
Proxy->UpdateCachedHasLayersContent(true);
|
|
|
|
if (Proxy->HadLayersContentAtPostLoadTime.IsSet())
|
|
{
|
|
bool bHasLayersContentBefore = Proxy->HadLayersContentAtPostLoadTime.GetValue();
|
|
check(Proxy->WeightmapFixupVersion == Proxy->CurrentVersion);
|
|
const bool bNeedOldDataMigration = !bHasLayersContentBefore && CanHaveLayersContent();
|
|
if (bNeedOldDataMigration && LandscapeActor->HasLayersContent())
|
|
{
|
|
LandscapeActor->CopyOldDataToDefaultLayer(Proxy);
|
|
Proxy->HadLayersContentAtPostLoadTime = false;
|
|
}
|
|
}
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
Proxy->bIsLandscapeActorRegisteredWithLandscapeInfo = true;
|
|
}
|
|
|
|
void ULandscapeInfo::RegisterActor(ALandscapeProxy* Proxy, bool bMapCheck, bool bUpdateAllAddCollisions)
|
|
{
|
|
UWorld* OwningWorld = Proxy->GetWorld();
|
|
// do not pass here invalid actors
|
|
checkSlow(Proxy);
|
|
check(Proxy->GetLandscapeGuid().IsValid());
|
|
check(LandscapeGuid.IsValid());
|
|
|
|
// in case this Info object is not initialized yet
|
|
// initialized it with properties from passed actor
|
|
if (GetLandscapeProxy() == nullptr)
|
|
{
|
|
ComponentSizeQuads = Proxy->ComponentSizeQuads;
|
|
ComponentNumSubsections = Proxy->NumSubsections;
|
|
SubsectionSizeQuads = Proxy->SubsectionSizeQuads;
|
|
}
|
|
|
|
// check that passed actor matches all shared parameters
|
|
check(LandscapeGuid == Proxy->GetLandscapeGuid());
|
|
check(ComponentSizeQuads == Proxy->ComponentSizeQuads);
|
|
check(ComponentNumSubsections == Proxy->NumSubsections);
|
|
check(SubsectionSizeQuads == Proxy->SubsectionSizeQuads);
|
|
|
|
// register
|
|
if (ALandscape* Landscape = Cast<ALandscape>(Proxy))
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
USceneComponent* Root = Proxy->GetRootComponent();
|
|
if (Root)
|
|
{
|
|
DrawScale = Root->GetRelativeScale3D();
|
|
bDrawScaleSetByActor = true;
|
|
}
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
if (!LandscapeActor.IsValid())
|
|
{
|
|
LandscapeActor = Landscape;
|
|
|
|
#if WITH_EDITOR
|
|
// Now we have associated a LandscapeActor with this info
|
|
// we can ask for the WeightMaps
|
|
UpdateLayerInfoMap(LandscapeActor.Get());
|
|
|
|
// Update registered splines so they can pull the actor pointer
|
|
for (TScriptInterface<ILandscapeSplineInterface> SplineActor : SplineActors)
|
|
{
|
|
SplineActor->UpdateSharedProperties(this);
|
|
}
|
|
|
|
// In world composition user is not allowed to move landscape in editor, only through WorldBrowser
|
|
bool bIsLockLocation = LandscapeActor->IsLockLocation();
|
|
bIsLockLocation |= OwningWorld != nullptr ? OwningWorld->WorldComposition != nullptr : false;
|
|
LandscapeActor->SetLockLocation(bIsLockLocation);
|
|
#endif // WITH_EDITOR
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
Landscape->bIsRegisteredWithLandscapeInfo = true;
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
// run post-landscape actor registration on the LandscapeActor first, then on each streaming proxy
|
|
RegisterLandscapeActorWithProxyInternal(Landscape, bMapCheck);
|
|
for (TWeakObjectPtr<ALandscapeStreamingProxy> StreamingProxyPtr : StreamingProxies)
|
|
{
|
|
if (ALandscapeStreamingProxy* StreamingProxy = StreamingProxyPtr.Get())
|
|
{
|
|
RegisterLandscapeActorWithProxyInternal(StreamingProxy, bMapCheck);
|
|
}
|
|
}
|
|
}
|
|
else if (LandscapeActor != Landscape)
|
|
{
|
|
UE_LOG(LogLandscape, Warning, TEXT("Multiple landscape actors with the same GUID detected: %s vs %s"), *LandscapeActor->GetPathName(), *Landscape->GetPathName());
|
|
}
|
|
#if WITH_EDITORONLY_DATA
|
|
Landscape->bIsRegisteredWithLandscapeInfo = true;
|
|
#endif // WITH_EDITORONLY_DATA
|
|
}
|
|
else
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
if (!bDrawScaleSetByActor)
|
|
{
|
|
USceneComponent* Root = Proxy->GetRootComponent();
|
|
if (Root)
|
|
{
|
|
DrawScale = Root->GetRelativeScale3D();
|
|
}
|
|
}
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
auto LamdbdaLowerBound = [](TWeakObjectPtr<ALandscapeProxy> APtr, TWeakObjectPtr<ALandscapeProxy> BPtr)
|
|
{
|
|
ALandscapeProxy *A = APtr.Get();
|
|
ALandscapeProxy *B = BPtr.Get();
|
|
|
|
// sort nulls, assuming null < !null
|
|
if (!A || !B)
|
|
{
|
|
return !!B;
|
|
}
|
|
|
|
FIntPoint SectionBaseA = A->GetSectionBaseOffset();
|
|
FIntPoint SectionBaseB = B->GetSectionBaseOffset();
|
|
|
|
if (SectionBaseA.X != SectionBaseB.X)
|
|
{
|
|
return SectionBaseA.X < SectionBaseB.X;
|
|
}
|
|
|
|
return SectionBaseA.Y < SectionBaseB.Y;
|
|
};
|
|
|
|
// Insert Proxies in a sorted fashion into the landscape info Proxies list, for generating deterministic results in the Layer system
|
|
ALandscapeStreamingProxy* StreamingProxy = CastChecked<ALandscapeStreamingProxy>(Proxy);
|
|
TWeakObjectPtr<ALandscapeStreamingProxy> StreamingProxyPtr = StreamingProxy;
|
|
if (!StreamingProxies.Contains(StreamingProxyPtr))
|
|
{
|
|
// NOTE: if a streaming proxy somehow gets garbage collected without de-registering from the Proxies list, then
|
|
// this search may return a non-deterministic index because the Proxies list will contain a null
|
|
uint32 InsertIndex = Algo::LowerBound(StreamingProxies, StreamingProxyPtr, LamdbdaLowerBound);
|
|
StreamingProxies.Insert(StreamingProxyPtr, InsertIndex);
|
|
}
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
StreamingProxy->bIsRegisteredWithLandscapeInfo = true;
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
// If we have a LandscapeActor, register it with the streaming proxy. If not, it is deferred until a LandscapeActor is registered.
|
|
if (LandscapeActor.IsValid())
|
|
{
|
|
RegisterLandscapeActorWithProxyInternal(StreamingProxy, bMapCheck);
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if(bUpdateAllAddCollisions)
|
|
{
|
|
UpdateAllAddCollisions();
|
|
}
|
|
RegisterSplineActor(Proxy);
|
|
#endif // WITH_EDITOR
|
|
|
|
//
|
|
// add proxy components to the XY map
|
|
//
|
|
for (int32 CompIdx = 0; CompIdx < Proxy->LandscapeComponents.Num(); ++CompIdx)
|
|
{
|
|
RegisterActorComponent(Proxy->LandscapeComponents[CompIdx], bMapCheck);
|
|
}
|
|
|
|
for (ULandscapeHeightfieldCollisionComponent* CollComp : Proxy->CollisionComponents)
|
|
{
|
|
RegisterCollisionComponent(CollComp);
|
|
}
|
|
}
|
|
|
|
void ULandscapeInfo::UnregisterActor(ALandscapeProxy* Proxy)
|
|
{
|
|
UWorld* OwningWorld = Proxy->GetWorld();
|
|
if (ALandscape* Landscape = Cast<ALandscape>(Proxy))
|
|
{
|
|
// Note: UnregisterActor sometimes gets triggered twice, e.g. it has been observed to happen during undo/ redo
|
|
// Note: In some cases LandscapeActor could be updated to a new landscape actor before the old landscape is unregistered/destroyed
|
|
// e.g. this has been observed when merging levels in the editor
|
|
|
|
if (LandscapeActor.Get() == Landscape)
|
|
{
|
|
LandscapeActor = nullptr;
|
|
}
|
|
|
|
// update proxies reference to landscape actor
|
|
for (TWeakObjectPtr<ALandscapeStreamingProxy> StreamingProxyPtr : StreamingProxies)
|
|
{
|
|
if (ALandscapeStreamingProxy* StreamingProxy = StreamingProxyPtr.Get())
|
|
{
|
|
StreamingProxy->SetLandscapeActor(LandscapeActor.Get());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ALandscapeStreamingProxy* StreamingProxy = CastChecked<ALandscapeStreamingProxy>(Proxy);
|
|
TWeakObjectPtr<ALandscapeStreamingProxy> StreamingProxyPtr = StreamingProxy;
|
|
StreamingProxies.Remove(StreamingProxyPtr);
|
|
|
|
FLandscapeGroup::UnregisterAllComponentsOnStreamingProxy(StreamingProxy);
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
UnregisterSplineActor(Proxy);
|
|
#endif // WITH_EDITOR
|
|
|
|
// remove proxy components from the XY map
|
|
for (int32 CompIdx = 0; CompIdx < Proxy->LandscapeComponents.Num(); ++CompIdx)
|
|
{
|
|
ULandscapeComponent* Component = Proxy->LandscapeComponents[CompIdx];
|
|
if (Component) // When a landscape actor is being GC'd it's possible the components were already GC'd and are null
|
|
{
|
|
UnregisterActorComponent(Component);
|
|
}
|
|
}
|
|
XYtoComponentMap.Compact();
|
|
|
|
for (ULandscapeHeightfieldCollisionComponent* CollComp : Proxy->CollisionComponents)
|
|
{
|
|
if (CollComp)
|
|
{
|
|
UnregisterCollisionComponent(CollComp);
|
|
}
|
|
}
|
|
XYtoCollisionComponentMap.Compact();
|
|
|
|
#if WITH_EDITOR
|
|
UpdateLayerInfoMap();
|
|
UpdateAllAddCollisions();
|
|
#endif
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
Proxy->bIsRegisteredWithLandscapeInfo = false;
|
|
#endif // WITH_EDITORONLY_DATA
|
|
Proxy->bIsLandscapeActorRegisteredWithLandscapeInfo = false;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
ALandscapeSplineActor* ULandscapeInfo::CreateSplineActor(const FVector& Location)
|
|
{
|
|
check(LandscapeActor.Get());
|
|
UWorld* World = LandscapeActor->GetWorld();
|
|
check(World);
|
|
FActorSpawnParameters SpawnParams;
|
|
SpawnParams.OverrideLevel = LandscapeActor->GetLevel();
|
|
SpawnParams.bNoFail = true;
|
|
SpawnParams.ObjectFlags |= RF_Transactional;
|
|
ALandscapeSplineActor* SplineActor = World->SpawnActor<ALandscapeSplineActor>(Location, FRotator::ZeroRotator, SpawnParams);
|
|
SplineActor->GetSharedProperties(this);
|
|
SplineActor->GetSplinesComponent()->ShowSplineEditorMesh(true);
|
|
SplineActor->SetIsSpatiallyLoaded(AreNewLandscapeActorsSpatiallyLoaded());
|
|
|
|
FActorLabelUtilities::SetActorLabelUnique(SplineActor, ALandscapeSplineActor::StaticClass()->GetName());
|
|
|
|
RegisterSplineActor(SplineActor);
|
|
return SplineActor;
|
|
}
|
|
|
|
void ULandscapeInfo::ForAllSplineActors(TFunctionRef<void(TScriptInterface<ILandscapeSplineInterface>)> Fn) const
|
|
{
|
|
for (const TScriptInterface<ILandscapeSplineInterface>& SplineActor : SplineActors)
|
|
{
|
|
Fn(SplineActor);
|
|
}
|
|
}
|
|
|
|
TArray<TScriptInterface<ILandscapeSplineInterface>> ULandscapeInfo::GetSplineActors() const
|
|
{
|
|
TArray<TScriptInterface<ILandscapeSplineInterface>> CopySplineActors(SplineActors);
|
|
return MoveTemp(CopySplineActors);
|
|
}
|
|
|
|
void ULandscapeInfo::RegisterSplineActor(TScriptInterface<ILandscapeSplineInterface> SplineActor)
|
|
{
|
|
Modify();
|
|
|
|
// Sort on insert to ensure spline actors are always processed in the same order, regardless of variation in the
|
|
// sub level streaming/registration sequence.
|
|
auto SortPredicate = [](const TScriptInterface<ILandscapeSplineInterface>& A, const TScriptInterface<ILandscapeSplineInterface>& B)
|
|
{
|
|
return Cast<UObject>(A.GetInterface())->GetPathName() < Cast<UObject>(B.GetInterface())->GetPathName();
|
|
};
|
|
|
|
// Add a unique entry, sorted
|
|
const int32 LBoundIdx = Algo::LowerBound(SplineActors, SplineActor, SortPredicate);
|
|
if (LBoundIdx == SplineActors.Num() || SplineActors[LBoundIdx] != SplineActor)
|
|
{
|
|
SplineActors.Insert(SplineActor, LBoundIdx);
|
|
}
|
|
|
|
SplineActor->UpdateSharedProperties(this);
|
|
|
|
if (SplineActor->GetSplinesComponent())
|
|
{
|
|
RequestSplineLayerUpdate();
|
|
}
|
|
}
|
|
|
|
void ULandscapeInfo::UnregisterSplineActor(TScriptInterface<ILandscapeSplineInterface> SplineActor)
|
|
{
|
|
Modify();
|
|
SplineActors.Remove(SplineActor);
|
|
|
|
if (SplineActor->GetSplinesComponent())
|
|
{
|
|
RequestSplineLayerUpdate();
|
|
}
|
|
}
|
|
|
|
void ULandscapeInfo::UpdateRegistrationForSplineActor(UWorld* InWorld, TScriptInterface<ILandscapeSplineInterface> InSplineActor)
|
|
{
|
|
if (InWorld == nullptr)
|
|
return;
|
|
|
|
ULandscapeInfoMap& LandscapeInfoMap = ULandscapeInfoMap::GetLandscapeInfoMap(InWorld);
|
|
FGuid SplineLandscapeGUID = InSplineActor->GetLandscapeGuid();
|
|
|
|
// first let's unregister from any landscapes that have it (incorrectly) registered
|
|
for (const auto& pair : LandscapeInfoMap.Map)
|
|
{
|
|
ULandscapeInfo* LandscapeInfo = pair.Value;
|
|
|
|
// only unregister if the landscape guids don't match
|
|
if ((LandscapeInfo->LandscapeGuid != SplineLandscapeGUID) &&
|
|
LandscapeInfo->SplineActors.Contains(InSplineActor))
|
|
{
|
|
LandscapeInfo->UnregisterSplineActor(InSplineActor);
|
|
}
|
|
}
|
|
|
|
// then let's make sure it is registered with the correct landscape info
|
|
if (SplineLandscapeGUID.IsValid())
|
|
{
|
|
ULandscapeInfo* LandscapeInfo = InSplineActor->GetLandscapeInfo();
|
|
check(LandscapeInfo);
|
|
if (!LandscapeInfo->SplineActors.Contains(InSplineActor))
|
|
{
|
|
LandscapeInfo->RegisterSplineActor(InSplineActor);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeInfo::RequestSplineLayerUpdate()
|
|
{
|
|
if (LandscapeActor.IsValid())
|
|
{
|
|
LandscapeActor->RequestSplineLayerUpdate();
|
|
}
|
|
}
|
|
|
|
void ULandscapeInfo::ForceLayersFullUpdate()
|
|
{
|
|
if (LandscapeActor.IsValid())
|
|
{
|
|
LandscapeActor->ForceLayersFullUpdate();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void ULandscapeInfo::RegisterCollisionComponent(ULandscapeHeightfieldCollisionComponent* Component)
|
|
{
|
|
if (Component == nullptr || !Component->IsRegistered())
|
|
{
|
|
return;
|
|
}
|
|
|
|
FIntPoint ComponentKey = Component->GetSectionBase() / Component->CollisionSizeQuads;
|
|
auto RegisteredComponent = XYtoCollisionComponentMap.FindRef(ComponentKey);
|
|
|
|
if (RegisteredComponent != Component)
|
|
{
|
|
if (RegisteredComponent == nullptr)
|
|
{
|
|
XYtoCollisionComponentMap.Add(ComponentKey, Component);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeInfo::UnregisterCollisionComponent(ULandscapeHeightfieldCollisionComponent* Component)
|
|
{
|
|
if (ensure(Component))
|
|
{
|
|
FIntPoint ComponentKey = Component->GetSectionBase() / Component->CollisionSizeQuads;
|
|
auto RegisteredComponent = XYtoCollisionComponentMap.FindRef(ComponentKey);
|
|
|
|
if (RegisteredComponent == Component)
|
|
{
|
|
XYtoCollisionComponentMap.Remove(ComponentKey);
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO [jonathan.bard] : improve this function or create another one to take into account unloaded proxies :
|
|
bool ULandscapeInfo::GetOverlappedComponents(const FTransform& InAreaWorldTransform, const FBox2D& InAreaExtents,
|
|
TMap<FIntPoint, ULandscapeComponent*>& OutOverlappedComponents, FIntRect& OutComponentIndicesBoundingRect)
|
|
{
|
|
if (!LandscapeActor.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FIntRect EffectiveBoundingIndices;
|
|
|
|
// Consider invalid extents as meaning "infinite", in which case, return all loaded components :
|
|
if (!InAreaExtents.bIsValid)
|
|
{
|
|
OutOverlappedComponents.Reserve(XYtoComponentMap.Num());
|
|
for (const TPair<FIntPoint, ULandscapeComponent*>& XYComponentPair : XYtoComponentMap)
|
|
{
|
|
EffectiveBoundingIndices.Union(FIntRect(XYComponentPair.Key, XYComponentPair.Key + FIntPoint(1)));
|
|
OutOverlappedComponents.Add(XYComponentPair);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Compute the AABB for this area in landscape space to find which of the landscape components are overlapping :
|
|
FVector Extremas[4];
|
|
const FTransform& LandscapeTransform = LandscapeActor->GetTransform();
|
|
Extremas[0] = LandscapeTransform.InverseTransformPosition(InAreaWorldTransform.TransformPosition(FVector(InAreaExtents.Min.X, InAreaExtents.Min.Y, 0.0)));
|
|
Extremas[1] = LandscapeTransform.InverseTransformPosition(InAreaWorldTransform.TransformPosition(FVector(InAreaExtents.Min.X, InAreaExtents.Max.Y, 0.0)));
|
|
Extremas[2] = LandscapeTransform.InverseTransformPosition(InAreaWorldTransform.TransformPosition(FVector(InAreaExtents.Max.X, InAreaExtents.Min.Y, 0.0)));
|
|
Extremas[3] = LandscapeTransform.InverseTransformPosition(InAreaWorldTransform.TransformPosition(FVector(InAreaExtents.Max.X, InAreaExtents.Max.Y, 0.0)));
|
|
FBox LocalExtents(Extremas, 4);
|
|
|
|
// Indices of the landscape components needed for rendering this area :
|
|
FIntRect BoundingIndices;
|
|
BoundingIndices.Min = FIntPoint(FMath::FloorToInt32(LocalExtents.Min.X / ComponentSizeQuads), FMath::FloorToInt32(LocalExtents.Min.Y / ComponentSizeQuads));
|
|
// The max here is meant to be an exclusive bound, hence the +1
|
|
BoundingIndices.Max = FIntPoint(FMath::FloorToInt32(LocalExtents.Max.X / ComponentSizeQuads), FMath::FloorToInt32(LocalExtents.Max.Y / ComponentSizeQuads)) + FIntPoint(1);
|
|
|
|
// Go through each loaded component and find out the actual bounds of the area we need to render :
|
|
for (int32 KeyY = BoundingIndices.Min.Y; KeyY < BoundingIndices.Max.Y; ++KeyY)
|
|
{
|
|
for (int32 KeyX = BoundingIndices.Min.X; KeyX < BoundingIndices.Max.X; ++KeyX)
|
|
{
|
|
FIntPoint Key(KeyX, KeyY);
|
|
if (ULandscapeComponent* Component = XYtoComponentMap.FindRef(Key))
|
|
{
|
|
EffectiveBoundingIndices.Union(FIntRect(Key, Key + FIntPoint(1)));
|
|
OutOverlappedComponents.Add(Key, Component);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (OutOverlappedComponents.IsEmpty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
OutComponentIndicesBoundingRect = EffectiveBoundingIndices;
|
|
return true;
|
|
}
|
|
|
|
void ULandscapeInfo::RegisterActorComponent(ULandscapeComponent* Component, bool bMapCheck)
|
|
{
|
|
// Do not register components which are not part of the world
|
|
if (Component == nullptr ||
|
|
Component->IsRegistered() == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
check(Component);
|
|
|
|
FIntPoint ComponentKey = Component->GetSectionBase() / Component->ComponentSizeQuads;
|
|
ULandscapeComponent* RegisteredComponent = XYtoComponentMap.FindRef(ComponentKey);
|
|
|
|
if (RegisteredComponent != Component)
|
|
{
|
|
if (RegisteredComponent == nullptr)
|
|
{
|
|
XYtoComponentMap.Add(ComponentKey, Component);
|
|
}
|
|
else if (bMapCheck)
|
|
{
|
|
#if WITH_EDITOR
|
|
ALandscapeProxy* OurProxy = Component->GetLandscapeProxy();
|
|
ALandscapeProxy* ExistingProxy = RegisteredComponent->GetLandscapeProxy();
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("ProxyName1"), FText::FromString(OurProxy->GetName()));
|
|
Arguments.Add(TEXT("LevelName1"), FText::FromString(OurProxy->GetLevel()->GetOutermost()->GetName()));
|
|
Arguments.Add(TEXT("ProxyName2"), FText::FromString(ExistingProxy->GetName()));
|
|
Arguments.Add(TEXT("LevelName2"), FText::FromString(ExistingProxy->GetLevel()->GetOutermost()->GetName()));
|
|
Arguments.Add(TEXT("XLocation"), Component->GetSectionBase().X);
|
|
Arguments.Add(TEXT("YLocation"), Component->GetSectionBase().Y);
|
|
FMessageLog("MapCheck").Warning()
|
|
->AddToken(FUObjectToken::Create(OurProxy, FText::FromString(OurProxy->GetActorNameOrLabel())))
|
|
->AddToken(FTextToken::Create(FText::Format(LOCTEXT("MapCheck_Message_LandscapeComponentPostLoad_Warning", "Landscape {ProxyName1} of {LevelName1} has overlapping render components with {ProxyName2} of {LevelName2} at location ({XLocation}, {YLocation})."), Arguments)))
|
|
->AddToken(FActionToken::Create(LOCTEXT("MapCheck_RemoveDuplicateLandscapeComponent", "Delete Duplicate"), LOCTEXT("MapCheck_RemoveDuplicateLandscapeComponentDesc", "Deletes the duplicate landscape component."), FOnActionTokenExecuted::CreateUObject(OurProxy, &ALandscapeProxy::RemoveOverlappingComponent, Component), true))
|
|
->AddToken(FMapErrorToken::Create(FMapErrors::LandscapeComponentPostLoad_Warning));
|
|
|
|
// Show MapCheck window
|
|
FMessageLog("MapCheck").Open(EMessageSeverity::Warning);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
#if WITH_EDITOR
|
|
// Update Selected Components/Regions
|
|
if (Component->EditToolRenderData.SelectedType)
|
|
{
|
|
if (Component->EditToolRenderData.SelectedType & FLandscapeEditToolRenderData::ST_COMPONENT)
|
|
{
|
|
SelectedComponents.Add(Component);
|
|
}
|
|
else if (Component->EditToolRenderData.SelectedType & FLandscapeEditToolRenderData::ST_REGION)
|
|
{
|
|
SelectedRegionComponents.Add(Component);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
XYComponentBounds.Include(ComponentKey);
|
|
}
|
|
|
|
void ULandscapeInfo::UnregisterActorComponent(ULandscapeComponent* Component)
|
|
{
|
|
if (ensure(Component))
|
|
{
|
|
FIntPoint ComponentKey = Component->GetSectionBase() / Component->ComponentSizeQuads;
|
|
ULandscapeComponent* RegisteredComponent = XYtoComponentMap.FindRef(ComponentKey);
|
|
|
|
if (RegisteredComponent == Component)
|
|
{
|
|
XYtoComponentMap.Remove(ComponentKey);
|
|
}
|
|
|
|
SelectedComponents.Remove(Component);
|
|
SelectedRegionComponents.Remove(Component);
|
|
|
|
// When removing a key, we need to iterate to find the new bounds
|
|
XYComponentBounds = FIntRect(MAX_int32, MAX_int32, MIN_int32, MIN_int32);
|
|
|
|
for (const TPair<FIntPoint, ULandscapeComponent*>& XYComponentPair : XYtoComponentMap)
|
|
{
|
|
XYComponentBounds.Include(XYComponentPair.Key);
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace LandscapeInfoBoundsHelper
|
|
{
|
|
void AccumulateBounds(ALandscapeProxy* Proxy, FBox& Bounds)
|
|
{
|
|
const bool bOnlyCollidingComponents = false;
|
|
const bool bIncludeChildActors = false;
|
|
FVector Origin;
|
|
FVector BoxExtents;
|
|
|
|
Proxy->GetActorBounds(bOnlyCollidingComponents, Origin, BoxExtents, bIncludeChildActors);
|
|
|
|
// Reject invalid bounds
|
|
if (BoxExtents != FVector::Zero())
|
|
{
|
|
Bounds += FBox::BuildAABB(Origin, BoxExtents);
|
|
}
|
|
}
|
|
}
|
|
|
|
FBox ULandscapeInfo::GetLoadedBounds() const
|
|
{
|
|
FBox Bounds(EForceInit::ForceInit);
|
|
|
|
if (LandscapeActor.IsValid())
|
|
{
|
|
LandscapeInfoBoundsHelper::AccumulateBounds(LandscapeActor.Get(), Bounds);
|
|
}
|
|
|
|
// Since in PIE/in-game the Proxies aren't populated, we must iterate through the loaded components
|
|
// but this is functionally equivalent to calling ForAllLandscapeProxies
|
|
TSet<ALandscapeProxy*> LoadedProxies;
|
|
for (auto It = XYtoComponentMap.CreateConstIterator(); It; ++It)
|
|
{
|
|
if (!It.Value())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (ALandscapeProxy* Proxy = Cast<ALandscapeProxy>(It.Value()->GetOwner()))
|
|
{
|
|
LoadedProxies.Add(Proxy);
|
|
}
|
|
}
|
|
|
|
for (ALandscapeProxy* Proxy : LoadedProxies)
|
|
{
|
|
LandscapeInfoBoundsHelper::AccumulateBounds(Proxy, Bounds);
|
|
}
|
|
|
|
return Bounds;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
FBox ULandscapeInfo::GetCompleteBounds() const
|
|
{
|
|
ALandscape* Landscape = LandscapeActor.Get();
|
|
|
|
// In a non-WP situation, the current actor's bounds will do.
|
|
if(!Landscape || !Landscape->GetWorld() || !Landscape->GetWorld()->GetWorldPartition())
|
|
{
|
|
return GetLoadedBounds();
|
|
}
|
|
|
|
FBox Bounds(EForceInit::ForceInit);
|
|
|
|
FWorldPartitionHelpers::ForEachActorDescInstance<ALandscapeProxy>(Landscape->GetWorld()->GetWorldPartition(), [this, &Bounds, Landscape](const FWorldPartitionActorDescInstance* ActorDescInstance)
|
|
{
|
|
FLandscapeActorDesc* LandscapeActorDesc = (FLandscapeActorDesc*)ActorDescInstance->GetActorDesc();
|
|
ALandscapeProxy* LandscapeProxy = Cast<ALandscapeProxy>(ActorDescInstance->GetActor());
|
|
|
|
// Prioritize loaded bounds, as the bounds in the actor desc might not be up-to-date
|
|
if(LandscapeProxy && (LandscapeProxy->GetGridGuid() == LandscapeGuid))
|
|
{
|
|
LandscapeInfoBoundsHelper::AccumulateBounds(LandscapeProxy, Bounds);
|
|
}
|
|
else if (LandscapeActorDesc->GridGuid == LandscapeGuid)
|
|
{
|
|
Bounds += LandscapeActorDesc->GetEditorBounds();
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
return Bounds;
|
|
}
|
|
#endif
|
|
|
|
void ULandscapeComponent::PostInitProperties()
|
|
{
|
|
Super::PostInitProperties();
|
|
|
|
// Initialize MapBuildDataId to something unique, in case this is a new ULandscapeComponent
|
|
MapBuildDataId = FGuid::NewGuid();
|
|
}
|
|
|
|
ULandscapeWeightmapUsage::ULandscapeWeightmapUsage(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
ClearUsage();
|
|
}
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
ALandscapeProxy::~ALandscapeProxy()
|
|
{
|
|
for (int32 Index = 0; Index < AsyncFoliageTasks.Num(); Index++)
|
|
{
|
|
FAsyncTask<FAsyncGrassTask>* Task = AsyncFoliageTasks[Index];
|
|
Task->EnsureCompletion(true);
|
|
FAsyncGrassTask& Inner = Task->GetTask();
|
|
delete Task;
|
|
}
|
|
AsyncFoliageTasks.Empty();
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
LandscapeProxies.Remove(this);
|
|
#endif
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
//
|
|
// ALandscapeMeshProxyActor
|
|
//
|
|
ALandscapeMeshProxyActor::ALandscapeMeshProxyActor(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
SetCanBeDamaged(false);
|
|
|
|
LandscapeMeshProxyComponent = CreateDefaultSubobject<ULandscapeMeshProxyComponent>(TEXT("LandscapeMeshProxyComponent0"));
|
|
LandscapeMeshProxyComponent->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
|
|
LandscapeMeshProxyComponent->Mobility = EComponentMobility::Static;
|
|
LandscapeMeshProxyComponent->SetGenerateOverlapEvents(false);
|
|
|
|
RootComponent = LandscapeMeshProxyComponent;
|
|
}
|
|
|
|
//
|
|
// ULandscapeMeshProxyComponent
|
|
//
|
|
ULandscapeMeshProxyComponent::ULandscapeMeshProxyComponent(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
}
|
|
|
|
void ULandscapeMeshProxyComponent::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
|
|
ChangeLandscapeGuidIfObjectIsInstanced(this->LandscapeGuid, this);
|
|
}
|
|
|
|
void ULandscapeMeshProxyComponent::InitializeForLandscape(ALandscapeProxy* Landscape, int8 InProxyLOD)
|
|
{
|
|
LandscapeGuid = Landscape->GetLandscapeGuid();
|
|
LODGroupKey = Landscape->LODGroupKey;
|
|
|
|
FTransform WorldToLocal = GetComponentTransform().Inverse();
|
|
|
|
bool bFirst = true;
|
|
for (ULandscapeComponent* Component : Landscape->LandscapeComponents)
|
|
{
|
|
if (Component)
|
|
{
|
|
const FTransform& ComponentLocalToWorld = Component->GetComponentTransform();
|
|
|
|
if (bFirst)
|
|
{
|
|
bFirst = false;
|
|
ComponentResolution = Component->ComponentSizeQuads + 1;
|
|
FVector ComponentXVectorWorldSpace = ComponentLocalToWorld.TransformVector(FVector::XAxisVector) * ComponentResolution;
|
|
FVector ComponentYVectorWorldSpace = ComponentLocalToWorld.TransformVector(FVector::YAxisVector) * ComponentResolution;
|
|
ComponentXVectorObjectSpace = WorldToLocal.TransformVector(ComponentXVectorWorldSpace);
|
|
ComponentYVectorObjectSpace = WorldToLocal.TransformVector(ComponentYVectorWorldSpace);
|
|
}
|
|
else
|
|
{
|
|
// assume it's the same resolution and orientation as the first component... (we only record one resolution and orientation)
|
|
}
|
|
|
|
// record the component coordinate
|
|
ProxyComponentBases.Add(Component->GetSectionBase() / Component->ComponentSizeQuads);
|
|
|
|
// record the component center position (in the space of the ULandscapeMeshProxyComponent)
|
|
FBoxSphereBounds ComponentLocalBounds = Component->CalcBounds(FTransform::Identity);
|
|
FVector ComponentOriginWorld = ComponentLocalToWorld.TransformPosition(ComponentLocalBounds.Origin);
|
|
FVector LocalOrigin = WorldToLocal.TransformPosition(ComponentOriginWorld);
|
|
ProxyComponentCentersObjectSpace.Add(LocalOrigin);
|
|
}
|
|
}
|
|
|
|
if (InProxyLOD != INDEX_NONE)
|
|
{
|
|
ProxyLOD = FMath::Clamp<int8>(InProxyLOD, 0, static_cast<int8>(FMath::CeilLogTwo(Landscape->SubsectionSizeQuads + 1) - 1));
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
|
|
void ALandscapeProxy::CreateNaniteComponents(int32 InNumComponents)
|
|
{
|
|
for (int32 i = 0; i < InNumComponents; ++i)
|
|
{
|
|
ULandscapeNaniteComponent* NaniteComponent = NewObject<ULandscapeNaniteComponent>(this, *FString::Format(TEXT("LandscapeNaniteComponent_{0}"), {{ i }}));
|
|
NaniteComponent->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
|
|
NaniteComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
|
NaniteComponent->SetMobility(EComponentMobility::Static);
|
|
NaniteComponent->SetGenerateOverlapEvents(false);
|
|
NaniteComponent->SetCanEverAffectNavigation(false);
|
|
NaniteComponent->CanCharacterStepUpOn = ECanBeCharacterBase::ECB_No;
|
|
NaniteComponent->bSelectable = false;
|
|
NaniteComponent->DepthPriorityGroup = SDPG_World;
|
|
NaniteComponent->bForceNaniteForMasked = true;
|
|
NaniteComponent->RegisterComponent();
|
|
NaniteComponent->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
|
|
|
|
NaniteComponents.Add(NaniteComponent);
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::SerializeStateHashes(FArchive& Ar)
|
|
{
|
|
for (FLandscapePerLODMaterialOverride& MaterialOverride : PerLODOverrideMaterials)
|
|
{
|
|
if (MaterialOverride.Material != nullptr)
|
|
{
|
|
FGuid LocalStateId = MaterialOverride.Material->GetMaterial_Concurrent()->StateId;
|
|
Ar << LocalStateId;
|
|
Ar << MaterialOverride.LODIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::SerializeStateHashes(FArchive& Ar)
|
|
{
|
|
FGuid HeightmapGuid = ULandscapeTextureHash::GetHash(HeightmapTexture);
|
|
Ar << HeightmapGuid;
|
|
for (auto WeightmapTexture : WeightmapTextures)
|
|
{
|
|
FGuid WeightmapGuid = WeightmapTexture->Source.GetId();
|
|
Ar << WeightmapGuid;
|
|
}
|
|
|
|
bool bEnableNanite = GetLandscapeProxy()->IsNaniteEnabled();
|
|
Ar << bEnableNanite;
|
|
|
|
if (GetLandscapeHoleMaterial() && ComponentHasVisibilityPainted())
|
|
{
|
|
FGuid LocalStateId = GetLandscapeHoleMaterial()->GetMaterial_Concurrent()->StateId;
|
|
Ar << LocalStateId;
|
|
}
|
|
|
|
// Take into account the Heightmap offset per component
|
|
Ar << HeightmapScaleBias.Z;
|
|
Ar << HeightmapScaleBias.W;
|
|
|
|
if (OverrideMaterial != nullptr)
|
|
{
|
|
FGuid LocalStateId = OverrideMaterial->GetMaterial_Concurrent()->StateId;
|
|
Ar << LocalStateId;
|
|
}
|
|
|
|
for (FLandscapePerLODMaterialOverride& MaterialOverride : PerLODOverrideMaterials)
|
|
{
|
|
if (MaterialOverride.Material != nullptr)
|
|
{
|
|
FGuid LocalStateId = MaterialOverride.Material->GetMaterial_Concurrent()->StateId;
|
|
Ar << LocalStateId;
|
|
Ar << MaterialOverride.LODIndex;
|
|
}
|
|
}
|
|
|
|
ALandscapeProxy* Proxy = GetLandscapeProxy();
|
|
|
|
if (Proxy->LandscapeMaterial != nullptr)
|
|
{
|
|
FGuid LocalStateId = Proxy->LandscapeMaterial->GetMaterial_Concurrent()->StateId;
|
|
Ar << LocalStateId;
|
|
}
|
|
|
|
Proxy->SerializeStateHashes(Ar);
|
|
}
|
|
|
|
FLandscapePhysicalMaterialBuilder::FLandscapePhysicalMaterialBuilder(UWorld* InWorld)
|
|
:World(InWorld)
|
|
,OudatedPhysicalMaterialComponentsCount(0)
|
|
{
|
|
}
|
|
|
|
// Deprecated
|
|
void FLandscapePhysicalMaterialBuilder::Build()
|
|
{
|
|
Build(UE::Landscape::EBuildFlags::None);
|
|
}
|
|
|
|
void FLandscapePhysicalMaterialBuilder::Build(UE::Landscape::EBuildFlags InBuildFlags)
|
|
{
|
|
if (World)
|
|
{
|
|
int32 NumBuilt = 0;
|
|
for (TActorIterator<ALandscapeProxy> ProxyIt(World); ProxyIt; ++ProxyIt)
|
|
{
|
|
if (EnumHasAnyFlags(InBuildFlags, UE::Landscape::EBuildFlags::ForceRebuild))
|
|
{
|
|
ProxyIt->InvalidatePhysicalMaterial();
|
|
}
|
|
NumBuilt += ProxyIt->BuildPhysicalMaterial() ? 1 : 0;
|
|
}
|
|
|
|
if (EnumHasAnyFlags(InBuildFlags, UE::Landscape::EBuildFlags::WriteFinalLog))
|
|
{
|
|
UE_LOGFMT_LOC(LogLandscape, Log, "BuildPhysicalMaterialFinalLog", "Build Physical Materials: {NumProxies} landscape {NumProxies}|plural(one=proxy,other=proxies) built", ("NumProxies", NumBuilt));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Deprecated
|
|
void FLandscapePhysicalMaterialBuilder::Rebuild()
|
|
{
|
|
Build(UE::Landscape::EBuildFlags::ForceRebuild);
|
|
}
|
|
|
|
int32 FLandscapePhysicalMaterialBuilder::GetOudatedPhysicalMaterialComponentsCount()
|
|
{
|
|
if (World)
|
|
{
|
|
OudatedPhysicalMaterialComponentsCount = 0;
|
|
for (TActorIterator<ALandscapeProxy> ProxyIt(World); ProxyIt; ++ProxyIt)
|
|
{
|
|
OudatedPhysicalMaterialComponentsCount += ProxyIt->GetOudatedPhysicalMaterialComponentsCount();
|
|
}
|
|
}
|
|
return OudatedPhysicalMaterialComponentsCount;
|
|
}
|
|
|
|
int32 ALandscapeProxy::GetOudatedPhysicalMaterialComponentsCount() const
|
|
{
|
|
int32 OudatedPhysicalMaterialComponentsCount = 0;
|
|
UpdatePhysicalMaterialTasksStatus(nullptr, &OudatedPhysicalMaterialComponentsCount);
|
|
return OudatedPhysicalMaterialComponentsCount;
|
|
}
|
|
|
|
UE::Landscape::EOutdatedDataFlags ALandscapeProxy::GetOutdatedDataFlags() const
|
|
{
|
|
UE::Landscape::EOutdatedDataFlags OutdatedDataFlags = UE::Landscape::EOutdatedDataFlags::None;
|
|
|
|
if (GetOutdatedGrassMapCount() > 0)
|
|
{
|
|
OutdatedDataFlags |= UE::Landscape::EOutdatedDataFlags::GrassMaps;
|
|
}
|
|
|
|
if (GetOudatedPhysicalMaterialComponentsCount() > 0)
|
|
{
|
|
OutdatedDataFlags |= UE::Landscape::EOutdatedDataFlags::PhysicalMaterials;
|
|
}
|
|
|
|
if (!IsNaniteMeshUpToDate())
|
|
{
|
|
OutdatedDataFlags |= UE::Landscape::EOutdatedDataFlags::NaniteMeshes;
|
|
}
|
|
|
|
if (ULandscapeInfo* Info = GetLandscapeInfo())
|
|
{
|
|
if (Info->IsPackageModified(GetPackage()))
|
|
{
|
|
OutdatedDataFlags |= UE::Landscape::EOutdatedDataFlags::PackageModified;
|
|
}
|
|
}
|
|
|
|
return OutdatedDataFlags;
|
|
}
|
|
|
|
void ALandscapeProxy::ClearNaniteTransactional()
|
|
{
|
|
for (ULandscapeNaniteComponent* NaniteComponent : NaniteComponents)
|
|
{
|
|
if (NaniteComponent)
|
|
{
|
|
NaniteComponent->ClearFlags(RF_Transactional);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::UpdateNaniteSharedPropertiesFromActor()
|
|
{
|
|
for (ULandscapeNaniteComponent* NaniteComponent : NaniteComponents)
|
|
{
|
|
if (NaniteComponent)
|
|
{
|
|
NaniteComponent->UpdatedSharedPropertiesFromActor();
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<ULandscapeComponent*> ALandscapeProxy::GatherSourceComponentsForNaniteComponent(int32 NaniteComponentIndex) const
|
|
{
|
|
const int32 StartComponentIndex = NaniteComponentIndex * NaniteMaxComponents;
|
|
const int32 EndComponentIndex = FMath::Min(LandscapeComponents.Num(), (NaniteComponentIndex + 1) * NaniteMaxComponents);
|
|
const int32 NumComponents = EndComponentIndex - StartComponentIndex;
|
|
|
|
if (NumComponents <= 0 || !LandscapeComponents.IsValidIndex(StartComponentIndex))
|
|
{
|
|
return TArray<ULandscapeComponent*>();
|
|
}
|
|
|
|
TArrayView<const TObjectPtr<ULandscapeComponent>> ComponentsView(&LandscapeComponents[StartComponentIndex], LandscapeComponents.Num() - StartComponentIndex);
|
|
TArray<ULandscapeComponent*> Result(ComponentsView);
|
|
|
|
ULandscapeComponent**const MinComponent = Algo::MinElementBy(Result,
|
|
[](const ULandscapeComponent* Component) { return Component->GetSectionBase(); },
|
|
[](const FIntPoint& A, const FIntPoint& B) { return (A.Y == B.Y) ? (A.X < B.X) : (A.Y < B.Y); }
|
|
);
|
|
check(MinComponent);
|
|
|
|
Algo::Sort(Result, FCompareULandscapeComponentClosest((*MinComponent)->GetSectionBase()));
|
|
Result.SetNum(NumComponents);
|
|
return Result;
|
|
}
|
|
|
|
void ALandscapeProxy::InvalidatePhysicalMaterial()
|
|
{
|
|
for (ULandscapeComponent* Component : LandscapeComponents)
|
|
{
|
|
Component->PhysicalMaterialHash = 0;
|
|
}
|
|
}
|
|
|
|
bool ALandscapeProxy::BuildPhysicalMaterial()
|
|
{
|
|
if (!HasAnyFlags(RF_ClassDefaultObject))
|
|
{
|
|
const bool bShouldMarkDirty = true;
|
|
return UpdatePhysicalMaterialTasks(bShouldMarkDirty);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void ALandscapeProxy::UpdatePhysicalMaterialTasksStatus(TSet<ULandscapeComponent*>* OutdatedComponents, int32* OutdatedComponentsCount) const
|
|
{
|
|
int32 OutdatedCount = 0;
|
|
for (ULandscapeComponent* Component : LandscapeComponents)
|
|
{
|
|
uint32 Hash = Component->CalculatePhysicalMaterialTaskHash();
|
|
if (Component->PhysicalMaterialHash != Hash || Component->PhysicalMaterialTask.IsValid())
|
|
{
|
|
OutdatedCount++;
|
|
if (OutdatedComponents)
|
|
{
|
|
OutdatedComponents->Add(Component);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (OutdatedCount == 0)
|
|
{
|
|
for (ULandscapeComponent* Component : LandscapeComponents)
|
|
{
|
|
const bool bIsDirty = Component->GetPackage()->IsDirty();
|
|
if (Component->LastSavedPhysicalMaterialHash != Component->PhysicalMaterialHash && !bIsDirty)
|
|
{
|
|
OutdatedCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (OutdatedComponentsCount)
|
|
{
|
|
*OutdatedComponentsCount = OutdatedCount;
|
|
}
|
|
}
|
|
|
|
bool ALandscapeProxy::UpdatePhysicalMaterialTasks(bool bInShouldMarkDirty)
|
|
{
|
|
TSet<ULandscapeComponent*> OutdatedComponents;
|
|
int32 PendingComponentsToBeSaved = 0;
|
|
UpdatePhysicalMaterialTasksStatus(&OutdatedComponents, &PendingComponentsToBeSaved);
|
|
for (ULandscapeComponent* Component : OutdatedComponents)
|
|
{
|
|
Component->UpdatePhysicalMaterialTasks();
|
|
}
|
|
if (bInShouldMarkDirty && PendingComponentsToBeSaved > 0)
|
|
{
|
|
MarkPackageDirty();
|
|
}
|
|
|
|
return (PendingComponentsToBeSaved > 0);
|
|
}
|
|
|
|
void ALandscapeProxy::RemoveNaniteComponents()
|
|
{
|
|
for (ULandscapeNaniteComponent* NaniteComponent : NaniteComponents)
|
|
{
|
|
if (NaniteComponent)
|
|
{
|
|
// Don't call modify when detaching the nanite component, this is non-transactional "derived data", regenerated any time the source landscape data changes. This prevents needlessly dirtying the package :
|
|
NaniteComponent->DetachFromComponent(FDetachmentTransformRules(EDetachmentRule::KeepRelative, /*bInCallModify = */false));
|
|
NaniteComponent->DestroyComponent();
|
|
}
|
|
}
|
|
|
|
NaniteComponents.Empty();
|
|
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
void ALandscapeProxy::EnableNaniteComponents(bool bInNaniteActive)
|
|
{
|
|
for (ULandscapeNaniteComponent* NaniteComponent : NaniteComponents)
|
|
{
|
|
if (NaniteComponent)
|
|
{
|
|
NaniteComponent->SetEnabled(bInNaniteActive);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
bool ALandscapeProxy::HasLayer(ULandscapeLayerInfoObject* LayerInfoObject) const
|
|
{
|
|
return TargetLayers.FindKey(FLandscapeTargetLayerSettings(LayerInfoObject)) == nullptr;
|
|
}
|
|
|
|
bool ALandscapeProxy::RemoveTargetLayer(const FName& Name, bool bPostEditChange)
|
|
{
|
|
Modify();
|
|
|
|
int32 NumItemsRemoved = TargetLayers.Remove(Name);
|
|
if (FProperty* Property = StaticClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(ALandscapeProxy, TargetLayers)); Property != nullptr && bPostEditChange)
|
|
{
|
|
FPropertyChangedEvent PropertyChangedEvent(Property);
|
|
PostEditChangeProperty(PropertyChangedEvent);
|
|
}
|
|
|
|
return NumItemsRemoved > 0;
|
|
}
|
|
|
|
FLandscapeTargetLayerSettings& ALandscapeProxy::AddTargetLayer()
|
|
{
|
|
int32 StartIndex = GetTargetLayers().Num();
|
|
FName NewName;
|
|
do
|
|
{
|
|
NewName = FName(FString::Format(TEXT("Layer_{0}"), { StartIndex++ } ));
|
|
} while (HasTargetLayer(NewName));
|
|
|
|
return AddTargetLayer(NewName, FLandscapeTargetLayerSettings());
|
|
}
|
|
|
|
FLandscapeTargetLayerSettings& ALandscapeProxy::AddTargetLayer(const FName& Name, const FLandscapeTargetLayerSettings& TargetLayerSettings, bool bPostEditChange)
|
|
{
|
|
Modify();
|
|
|
|
FLandscapeTargetLayerSettings& Settings = TargetLayers.Add(Name, TargetLayerSettings);
|
|
if (FProperty* Property = StaticClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(ALandscapeProxy, TargetLayers)); Property != nullptr && bPostEditChange)
|
|
{
|
|
FPropertyChangedEvent PropertyChangedEvent(Property);
|
|
PostEditChangeProperty(PropertyChangedEvent);
|
|
}
|
|
|
|
return Settings;
|
|
}
|
|
|
|
bool ALandscapeProxy::UpdateTargetLayer(const FName& Name, const FLandscapeTargetLayerSettings& InTargetLayerSettings, bool bPostEditChange)
|
|
{
|
|
FLandscapeTargetLayerSettings* TargetLayerSettings = TargetLayers.Find(Name);
|
|
|
|
check(TargetLayerSettings);
|
|
if (TargetLayerSettings)
|
|
{
|
|
Modify();
|
|
*TargetLayerSettings = InTargetLayerSettings;
|
|
|
|
if (FProperty* Property = StaticClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(ALandscapeProxy, TargetLayers)); Property != nullptr && bPostEditChange)
|
|
{
|
|
FPropertyChangedEvent PropertyChangedEvent(Property);
|
|
PostEditChangeProperty(PropertyChangedEvent);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ALandscapeProxy::HasTargetLayer(const FName& Name) const
|
|
{
|
|
return TargetLayers.Find(Name) != nullptr;
|
|
}
|
|
|
|
bool ALandscapeProxy::HasTargetLayer(const FLandscapeTargetLayerSettings& TargetLayerSettings) const
|
|
{
|
|
return TargetLayers.FindKey(TargetLayerSettings) != nullptr;
|
|
}
|
|
|
|
bool ALandscapeProxy::HasTargetLayer(const ULandscapeLayerInfoObject* LayerInfoObject) const
|
|
{
|
|
for (const auto& It : TargetLayers)
|
|
{
|
|
if (It.Value.LayerInfoObj == LayerInfoObject)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const TMap<FName, FLandscapeTargetLayerSettings>& ALandscapeProxy::GetTargetLayers() const
|
|
{
|
|
return TargetLayers;
|
|
}
|
|
#endif
|
|
|
|
bool ALandscapeProxy::AreNaniteComponentsValid(const FGuid& InProxyContentId) const
|
|
{
|
|
if (NaniteComponents.IsEmpty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (const ULandscapeNaniteComponent* NaniteComponent : NaniteComponents)
|
|
{
|
|
if (!NaniteComponent)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (NaniteComponent->GetProxyContentId() != InProxyContentId)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
TSet<FPrimitiveComponentId> ALandscapeProxy::GetNanitePrimitiveComponentIds() const
|
|
{
|
|
TSet<FPrimitiveComponentId> PrimitiveComponentIds;
|
|
for (const ULandscapeNaniteComponent* NaniteComponent : NaniteComponents)
|
|
{
|
|
if (NaniteComponent && NaniteComponent->SceneProxy)
|
|
{
|
|
PrimitiveComponentIds.Add(NaniteComponent->SceneProxy->GetPrimitiveComponentId());
|
|
}
|
|
|
|
}
|
|
return PrimitiveComponentIds;
|
|
}
|
|
|
|
FGuid ALandscapeProxy::GetNaniteComponentContentId() const
|
|
{
|
|
if (NaniteComponents.IsEmpty())
|
|
{
|
|
return FGuid();
|
|
}
|
|
|
|
FGuid ContentId = NaniteComponents[0] ? NaniteComponents[0]->GetProxyContentId() : FGuid();
|
|
return ContentId;
|
|
}
|
|
|
|
bool ALandscapeProxy::AuditNaniteMaterials() const
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(ALandscapeProxy::AuditMaterials);
|
|
for (const ULandscapeNaniteComponent* NaniteComponent : NaniteComponents)
|
|
{
|
|
if (!NaniteComponent)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Nanite::FMaterialAudit NaniteMaterials;
|
|
Nanite::AuditMaterials(NaniteComponent, NaniteMaterials);
|
|
|
|
const bool bIsMaskingAllowed = Nanite::IsMaskingAllowed(GetWorld(), NaniteComponent->bForceNaniteForMasked);
|
|
if (!NaniteMaterials.IsValid(bIsMaskingAllowed))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ALandscapeProxy::InvalidateGeneratedComponentData(bool bInvalidateLightingCache)
|
|
{
|
|
InvalidateGeneratedComponentData(LandscapeComponents, bInvalidateLightingCache);
|
|
}
|
|
|
|
void ALandscapeProxy::InvalidateGeneratedComponentData(const TArray<ULandscapeComponent*>& Components, bool bInvalidateLightingCache)
|
|
{
|
|
TMap<ALandscapeProxy*, TSet<ULandscapeComponent*>> ByProxy;
|
|
for (auto Iter = Components.CreateConstIterator(); Iter; ++Iter)
|
|
{
|
|
ULandscapeComponent* Component = *Iter;
|
|
if (bInvalidateLightingCache)
|
|
{
|
|
Component->InvalidateLightingCache();
|
|
}
|
|
ByProxy.FindOrAdd(Component->GetLandscapeProxy()).Add(Component);
|
|
}
|
|
|
|
for (auto Iter = ByProxy.CreateConstIterator(); Iter; ++Iter)
|
|
{
|
|
ALandscapeProxy* Proxy = Iter.Key();
|
|
Proxy->FlushGrassComponents(&Iter.Value());
|
|
|
|
#if WITH_EDITOR
|
|
ULandscapeSubsystem* Subsystem = Proxy->GetWorld()->GetSubsystem<ULandscapeSubsystem>();
|
|
if (Subsystem->IsLiveNaniteRebuildEnabled())
|
|
{
|
|
Proxy->GetAsyncWorkMonitor().SetDelayedUpdateTimer(FAsyncWorkMonitor::EAsyncWorkType::BuildNaniteMeshes, LandscapeNaniteBuildLag);
|
|
}
|
|
else
|
|
{
|
|
Proxy->InvalidateOrUpdateNaniteRepresentation(/* bInCheckContentId = */true, /*InTargetPlatform = */nullptr);
|
|
}
|
|
|
|
FLandscapeProxyComponentDataChangedParams ChangeParams(Iter.Value());
|
|
Subsystem->GetDelegateAccess().OnLandscapeProxyComponentDataChangedDelegate.Broadcast(Iter.Key(), ChangeParams);
|
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
Proxy->OnComponentDataChanged.Broadcast(Iter.Key(), ChangeParams);
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
#endif // WITH_EDITOR
|
|
|
|
Proxy->UpdateRenderingMethod();
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::InvalidateGeneratedComponentData(const TSet<ULandscapeComponent*>& Components, bool bInvalidateLightingCache)
|
|
{
|
|
InvalidateGeneratedComponentData(Components.Array(), bInvalidateLightingCache);
|
|
}
|
|
|
|
void ALandscapeProxy::UpdateRenderingMethod()
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(ALandscapeProxy::UpdateRenderingMethod);
|
|
if (LandscapeComponents.Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool bNaniteActive = false;
|
|
if ((CVarRenderNaniteLandscape.GetValueOnGameThread() != 0) && HasNaniteComponents())
|
|
{
|
|
bNaniteActive = UseNanite(GShaderPlatformForFeatureLevel[GEngine->GetDefaultWorldFeatureLevel()]);
|
|
#if WITH_EDITOR
|
|
if (ALandscape* LandscapeActor = GetLandscapeActor())
|
|
{
|
|
if (UWorld* World = LandscapeActor->GetWorld())
|
|
{
|
|
bNaniteActive = UseNanite(GShaderPlatformForFeatureLevel[World->GetFeatureLevel()]);
|
|
}
|
|
}
|
|
#endif //WITH_EDITOR
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (bNaniteActive)
|
|
{
|
|
bNaniteActive = GetNaniteComponentContentId() == GetNaniteContentId();
|
|
}
|
|
#endif //WITH_EDITOR
|
|
|
|
if (bNaniteActive)
|
|
{
|
|
bNaniteActive = AuditNaniteMaterials();
|
|
}
|
|
|
|
for (ULandscapeComponent* Component : LandscapeComponents)
|
|
{
|
|
if (Component)
|
|
{
|
|
Component->SetNaniteActive(bNaniteActive);
|
|
}
|
|
}
|
|
|
|
EnableNaniteComponents(bNaniteActive);
|
|
}
|
|
|
|
ULandscapeLODStreamingProxy_DEPRECATED::ULandscapeLODStreamingProxy_DEPRECATED(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
FLandscapeProxyComponentDataChangedParams::FLandscapeProxyComponentDataChangedParams(const TSet<ULandscapeComponent*>& InComponents)
|
|
: Components(InComponents.Array())
|
|
{
|
|
}
|
|
|
|
void FLandscapeProxyComponentDataChangedParams::ForEachComponent(TFunctionRef<void(const ULandscapeComponent*)> Func) const
|
|
{
|
|
for (ULandscapeComponent* Component : Components)
|
|
{
|
|
Func(Component);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool FAsyncWorkMonitor::CheckIfUpdateTriggeredAndClear(EAsyncWorkType WorkType)
|
|
{
|
|
bool& bUpdateTriggered = WorkTypeInfos[static_cast<uint32>(WorkType)].bUpdateTriggered;
|
|
|
|
bool bReturn = bUpdateTriggered;
|
|
bUpdateTriggered = false;
|
|
return bReturn;
|
|
}
|
|
|
|
void FAsyncWorkMonitor::SetDelayedUpdateTimer(EAsyncWorkType WorkType, float InSecondsUntilDelayedUpdateTrigger)
|
|
{
|
|
FAsyncWorkTypeInfo& Info = WorkTypeInfos[static_cast<uint32>(WorkType)];
|
|
Info.SecondsUntilDelayedUpdateTrigger = InSecondsUntilDelayedUpdateTrigger;
|
|
}
|
|
|
|
void FAsyncWorkMonitor::Tick(float Detaltime)
|
|
{
|
|
for (FAsyncWorkTypeInfo& Info : WorkTypeInfos)
|
|
{
|
|
if (Info.SecondsUntilDelayedUpdateTrigger > 0.0f)
|
|
{
|
|
Info.SecondsUntilDelayedUpdateTrigger -= Detaltime;
|
|
|
|
if (Info.SecondsUntilDelayedUpdateTrigger <= 0.0f)
|
|
{
|
|
Info.SecondsUntilDelayedUpdateTrigger = 0.0f;
|
|
Info.bUpdateTriggered = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
#undef LOCTEXT_NAMESPACE
|