// 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 "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 "LandscapeRender.h" #include "LandscapeRenderMobile.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 "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 "LandscapeSplineActor.h" #include "LandscapeSplinesComponent.h" #include "EngineGlobals.h" #include "Engine/Engine.h" #include "Engine/TextureRenderTarget2D.h" #include "EngineUtils.h" #include "ComponentRecreateRenderStateContext.h" #include "LandscapeWeightmapUsage.h" #include "LandscapeSubsystem.h" #include "Streaming/LandscapeMeshMobileUpdate.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" #if WITH_EDITOR #include "LandscapeEdit.h" #include "MaterialUtilities.h" #include "Editor.h" #include "Algo/Transform.h" #include "Algo/BinarySearch.h" #include "Engine/Texture2D.h" #endif #include "LandscapeVersion.h" #include "UObject/FortniteMainBranchObjectVersion.h" #include "LandscapeDataAccess.h" #include "UObject/EditorObjectVersion.h" #include "Algo/BinarySearch.h" #include "WorldPartition/WorldPartition.h" #include "WorldPartition/WorldPartitionHelpers.h" #include "WorldPartition/WorldPartitionHandle.h" #include "WorldPartition/Landscape/LandscapeActorDesc.h" #include "WorldPartition/Landscape/LandscapeSplineActorDesc.h" #if WITH_EDITOR #include "Rendering/StaticLightingSystemInterface.h" #include "Misc/ScopedSlowTask.h" #endif /** Landscape stats */ DEFINE_STAT(STAT_LandscapeDynamicDrawTime); DEFINE_STAT(STAT_LandscapeStaticDrawLODTime); DEFINE_STAT(STAT_LandscapeVFDrawTimeVS); DEFINE_STAT(STAT_LandscapeInitViewCustomData); DEFINE_STAT(STAT_LandscapePostInitViewCustomData); DEFINE_STAT(STAT_LandscapeComputeCustomMeshBatchLOD); DEFINE_STAT(STAT_LandscapeComputeCustomShadowMeshBatchLOD); DEFINE_STAT(STAT_LandscapeVFDrawTimePS); DEFINE_STAT(STAT_LandscapeComponentRenderPasses); DEFINE_STAT(STAT_LandscapeTessellatedShadowCascade); DEFINE_STAT(STAT_LandscapeTessellatedComponents); DEFINE_STAT(STAT_LandscapeComponentUsingSubSectionDrawCalls); DEFINE_STAT(STAT_LandscapeDrawCalls); DEFINE_STAT(STAT_LandscapeTriangles); DEFINE_STAT(STAT_LandscapeLayersRegenerate_RenderThread); DEFINE_STAT(STAT_LandscapeLayersRegenerateDrawCalls); DEFINE_STAT(STAT_LandscapeLayersRegenerateHeightmaps); DEFINE_STAT(STAT_LandscapeLayersResolveHeightmaps); DEFINE_STAT(STAT_LandscapeLayersResolveTexture); DEFINE_STAT(STAT_LandscapeLayersUpdateMaterialInstance); DEFINE_STAT(STAT_LandscapeLayersReallocateWeightmaps); DEFINE_STAT(STAT_LandscapeLayersResolveWeightmaps); DEFINE_STAT(STAT_LandscapeLayersRegenerateWeightmaps); DEFINE_STAT(STAT_LandscapeVertexMem); DEFINE_STAT(STAT_LandscapeHoleMem); DEFINE_STAT(STAT_LandscapeComponentMem); #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 // Set this to 0 to disable landscape cooking and thus disable it on device. #define ENABLE_LANDSCAPE_COOKING 1 static bool UseMobileLandscapeMesh(const ITargetPlatform* TargetPlatform) { return TargetPlatform->SupportsFeature(ETargetPlatformFeatures::MobileLandscapeMesh); } #define LOCTEXT_NAMESPACE "Landscape" static void PrintNumLandscapeShadows() { int32 NumComponents = 0; int32 NumShadowCasters = 0; for (TObjectIterator It; It; ++It) { ULandscapeComponent* LC = *It; NumComponents++; if (LC->CastShadow && LC->bCastDynamicShadow) { NumShadowCasters++; } } UE_LOG(LogConsoleResponse, Display, TEXT("%d/%d landscape components cast shadows"), NumShadowCasters, NumComponents); } FAutoConsoleCommand CmdPrintNumLandscapeShadows( TEXT("ls.PrintNumLandscapeShadows"), TEXT("Prints the number of landscape components that cast shadows."), FConsoleCommandDelegate::CreateStatic(PrintNumLandscapeShadows) ); int32 RenderCaptureNextHeightmapRenders = 0; static FAutoConsoleVariableRef CVarRenderCaptureNextHeightmapRenders( TEXT("landscape.RenderCaptureNextHeightmapRenders"), RenderCaptureNextHeightmapRenders, TEXT("Trigger a render capture during the next N RenderHeightmap draws")); ULandscapeComponent::ULandscapeComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) #if WITH_EDITORONLY_DATA , CachedEditingLayerData(nullptr) , LayerUpdateFlagPerMode(0) , bPendingCollisionDataUpdate(false) , bPendingLayerCollisionDataUpdate(false) , WeightmapsHash(0) , SplineHash(0) , PhysicalMaterialHash(0) #endif , GrassData(MakeShareable(new 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; LODStreamingProxy = CreateDefaultSubobject(TEXT("LandscapeLODStreamingProxy")); } 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]; } 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 (UseMobileLandscapeMesh(TargetPlatform) && !HasAnyFlags(RF_ClassDefaultObject)) { CheckGenerateLandscapePlatformData(true, TargetPlatform); } } void ALandscapeProxy::CheckGenerateLandscapePlatformData(bool bIsCooking, const ITargetPlatform* TargetPlatform) { for (ULandscapeComponent* Component : LandscapeComponents) { Component->CheckGenerateLandscapePlatformData(bIsCooking, TargetPlatform); } } void ULandscapeComponent::CheckGenerateLandscapePlatformData(bool bIsCooking, const ITargetPlatform* TargetPlatform) { #if ENABLE_LANDSCAPE_COOKING // Regenerate platform data only when it's missing or there is a valid hash-mismatch. FBufferArchive ComponentStateAr; SerializeStateHashes(ComponentStateAr); if (bIsCooking && TargetPlatform && TargetPlatform->SupportsFeature(ETargetPlatformFeatures::LandscapeMeshLODStreaming)) { int32 MaxLODClamp = GetLandscapeProxy()->MaxLODLevel; MaxLODClamp = MaxLODClamp < 0 ? INT32_MAX : MaxLODClamp; ComponentStateAr << MaxLODClamp; } else { int32 DummyMaxLODClamp = INDEX_NONE; ComponentStateAr << DummyMaxLODClamp; } // Serialize the version guid as part of the hash so we can invalidate DDC data if needed FString Version = FDevSystemGuids::GetSystemGuid(FDevSystemGuids::Get().LANDSCAPE_MOBILE_COOK_VERSION).ToString(); ComponentStateAr << Version; 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 bMissingVertexData = !PlatformData.HasValidPlatformData(); bool bMissingPixelData = MobileMaterialInterfaces.Num() == 0 || MobileWeightmapTextures.Num() == 0 || MaterialPerLOD.Num() == 0; bool bRegenerateVertexData = bMissingVertexData || bMissingPixelData || bHashMismatch; if (bRegenerateVertexData) { if (bIsCooking) { // The DDC is only useful when cooking (see else). COOK_STAT(auto Timer = LandscapeCookStats::UsageStats.TimeSyncWork()); // Temporarily disabling DDC use. See FORT-317076. // if (PlatformData.LoadFromDDC(NewSourceHash, this)) // { // COOK_STAT(Timer.AddHit(PlatformData.GetPlatformDataSize())); // } // else { GeneratePlatformVertexData(TargetPlatform); // PlatformData.SaveToDDC(NewSourceHash, this); COOK_STAT(Timer.AddMiss(PlatformData.GetPlatformDataSize())); } } else { // When not cooking (e.g. mobile preview) DDC data isn't sufficient to // display correctly, so the platform vertex data must be regenerated. GeneratePlatformVertexData(TargetPlatform); } } bool bRegeneratePixelData = bMissingPixelData || bHashMismatch; if (bRegeneratePixelData) { GeneratePlatformPixelData(bIsCooking, TargetPlatform); } MobileDataSourceHash = NewSourceHash; #endif } #endif 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(ForcedLOD, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1); } else { ForcedLOD = -1; } } else { int32 MaxLOD = FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1; LODBias = FMath::Clamp(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::Serialize(FArchive& Ar) { LLM_SCOPE(ELLMTag::Landscape); Ar.UsingCustomVersion(FRenderingObjectVersion::GUID); Ar.UsingCustomVersion(FFortniteMainBranchObjectVersion::GUID); Ar.UsingCustomVersion(FEditorObjectVersion::GUID); #if WITH_EDITOR if (Ar.IsCooking() && !HasAnyFlags(RF_ClassDefaultObject) && UseMobileLandscapeMesh(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 CheckGenerateLandscapePlatformData in serialize // the new cooker clears the texture source data before calling serialize, causing GeneratePlatformVertexData to crash, so we have to do CheckGenerateLandscapePlatformData in BeginCacheForCookedPlatformData CheckGenerateLandscapePlatformData(true, Ar.CookingTarget()); } // 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 TexturesAndMaterials; TexturesAndMaterials.Add((UObject**)&HeightmapTexture); TexturesAndMaterials.Add((UObject**)&XYOffsetmapTexture); for (TObjectPtr& WeightmapTexture : WeightmapTextures) { TexturesAndMaterials.Add((UObject**)&static_cast(WeightmapTexture)); } for (TObjectPtr& MobileWeightmapTexture : MobileWeightmapTextures) { TexturesAndMaterials.Add((UObject**)&static_cast(MobileWeightmapTexture)); } for (auto& ItPair : LayersData) { FLandscapeLayerComponentData& LayerComponentData = ItPair.Value; TexturesAndMaterials.Add((UObject**)&LayerComponentData.HeightmapData.Texture); for (UE_TRANSITIONAL_OBJECT_PTR(UTexture2D)& WeightmapTexture : LayerComponentData.WeightmapData.Textures) { TexturesAndMaterials.Add((UObject**)&static_cast(WeightmapTexture)); } } for (TObjectPtr& MaterialInstance : MaterialInstances) { TexturesAndMaterials.Add((UObject**)&static_cast(MaterialInstance)); } for (TObjectPtr& MobileMaterialInterface : MobileMaterialInterfaces) { TexturesAndMaterials.Add((UObject**)(&static_cast(MobileMaterialInterface))); } for (TObjectPtr& MobileCombinationMaterialInstance : MobileCombinationMaterialInstances) { TexturesAndMaterials.Add((UObject**)&static_cast(MobileCombinationMaterialInstance)); } if (Ar.IsSaving()) { TArray 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)) { const bool bUseMobileLandscapeMesh = UseMobileLandscapeMesh(Ar.CookingTarget()); if (bUseMobileLandscapeMesh && !Ar.CookingTarget()->SupportsFeature(ETargetPlatformFeatures::DeferredRendering)) { // These are used for SM5 rendering or if MobileLandscapeMesh is disabled UTexture2D* BackupHeightmapTexture = nullptr; UTexture2D* BackupXYOffsetmapTexture = nullptr; TArray BackupMaterialInstances; TArray BackupWeightmapTextures; Exchange(HeightmapTexture, BackupHeightmapTexture); Exchange(BackupXYOffsetmapTexture, XYOffsetmapTexture); Exchange(BackupMaterialInstances, MaterialInstances); Exchange(BackupWeightmapTextures, WeightmapTextures); Super::Serialize(Ar); Exchange(HeightmapTexture, BackupHeightmapTexture); Exchange(BackupXYOffsetmapTexture, XYOffsetmapTexture); Exchange(BackupMaterialInstances, MaterialInstances); Exchange(BackupWeightmapTextures, WeightmapTextures); } else if (!bUseMobileLandscapeMesh) { // These properties are only when MobileLandscapeMesh is enabled so we back them up and clear them before serializing them. TArray BackupMobileMaterialInterfaces; TArray BackupMobileWeightmapTextures; Exchange(MobileMaterialInterfaces, BackupMobileMaterialInterfaces); Exchange(MobileWeightmapTextures, BackupMobileWeightmapTextures); Super::Serialize(Ar); Exchange(MobileMaterialInterfaces, BackupMobileMaterialInterfaces); Exchange(MobileWeightmapTextures, BackupMobileWeightmapTextures); } else { // Serialize both mobile landscape mesh and heightmap properties Super::Serialize(Ar); } } else #endif { Super::Serialize(Ar); } if (Ar.IsLoading() && Ar.CustomVer(FRenderingObjectVersion::GUID) < FRenderingObjectVersion::MapBuildDataSeparatePackage) { FMeshMapBuildData* LegacyMapBuildData = new FMeshMapBuildData(); Ar << LegacyMapBuildData->LightMap; Ar << LegacyMapBuildData->ShadowMap; LegacyMapBuildData->IrrelevantLights = IrrelevantLights_DEPRECATED; FMeshMapBuildLegacyData LegacyComponentData; LegacyComponentData.Data.Emplace(MapBuildDataId, LegacyMapBuildData); GComponentsWithLegacyLightmaps.AddAnnotation(this, MoveTemp(LegacyComponentData)); } if (Ar.IsLoading() && Ar.CustomVer(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::NewLandscapeMaterialPerLOD) { if (MobileMaterialInterface_DEPRECATED != nullptr) { MobileMaterialInterfaces.AddUnique(MobileMaterialInterface_DEPRECATED); } #if WITH_EDITORONLY_DATA if (MobileCombinationMaterialInstance_DEPRECATED != nullptr) { MobileCombinationMaterialInstances.AddUnique(MobileCombinationMaterialInstance_DEPRECATED); } #endif } 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*)GrassDataPointer; } } else { 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 ENABLE_LANDSCAPE_COOKING if (bCooked) { bool bCookedMobileData = Ar.IsCooking() && UseMobileLandscapeMesh(Ar.CookingTarget()); Ar << bCookedMobileData; // Saving for cooking path if (bCookedMobileData) { if (Ar.IsCooking()) { check(PlatformData.HasValidPlatformData()); } PlatformData.Serialize(Ar, this); } } #endif #if WITH_EDITOR if (Ar.GetPortFlags() & PPF_DuplicateForPIE) { PlatformData.Serialize(Ar, this); } #endif #if WITH_EDITOR if (Ar.IsSaving() && Ar.IsPersistent()) { //Update the last saved Guid for GI texture LastBakedTextureMaterialGuid = BakedTextureMaterialGuid; //Update the last saved Hash for physical material LastSavedPhysicalMaterialHash = PhysicalMaterialHash; } #endif } void ULandscapeComponent::GetResourceSizeEx(FResourceSizeEx& CumulativeResourceSize) { Super::GetResourceSizeEx(CumulativeResourceSize); CumulativeResourceSize.AddDedicatedSystemMemoryBytes(GrassData->GetAllocatedSize()); } #if WITH_EDITOR 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; } bool ULandscapeComponent::IsLandscapeHoleMaterialValid() const { UMaterialInterface* HoleMaterial = GetLandscapeHoleMaterial(); if (!HoleMaterial) { HoleMaterial = GetLandscapeMaterial(); } return HoleMaterial ? HoleMaterial->GetMaterial()->HasAnyExpressionsInMaterialAndFunctionsOfType() : false; } bool ULandscapeComponent::ComponentHasVisibilityPainted() const { for (const FWeightmapLayerAllocationInfo& Allocation : WeightmapLayerAllocations) { if (Allocation.LayerInfo == ALandscapeProxy::VisibilityLayer) { return true; } } return false; } 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& LayerStruct = *It; if (LayerStruct.DebugColorChannel > 0 && LayerStruct.LayerInfoObj) { const TArray& ComponentWeightmapLayerAllocations = GetWeightmapLayerAllocations(); for (int32 LayerIdx = 0; LayerIdx < ComponentWeightmapLayerAllocations.Num(); LayerIdx++) { if (ComponentWeightmapLayerAllocations[LayerIdx].LayerInfo == LayerStruct.LayerInfoObj) { if (LayerStruct.DebugColorChannel & 1) // R { R = (ComponentWeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex * 4 + ComponentWeightmapLayerAllocations[LayerIdx].WeightmapTextureChannel); } if (LayerStruct.DebugColorChannel & 2) // G { G = (ComponentWeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex * 4 + ComponentWeightmapLayerAllocations[LayerIdx].WeightmapTextureChannel); } if (LayerStruct.DebugColorChannel & 4) // B { B = (ComponentWeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex * 4 + ComponentWeightmapLayerAllocations[LayerIdx].WeightmapTextureChannel); } break; } } } } } } #endif //WITH_EDITOR ULandscapeInfo::ULandscapeInfo(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , 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(); } 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; bRenderCustomDepth = LandscapeProxy->bRenderCustomDepth; CustomDepthStencilWriteMask = LandscapeProxy->CustomDepthStencilWriteMask; CustomDepthStencilValue = LandscapeProxy->CustomDepthStencilValue; SetCullDistance(LandscapeProxy->LDMaxDrawDistance); LightingChannels = LandscapeProxy->LightingChannels; UpdateNavigationRelevance(); UpdateRejectNavmeshUnderneath(); } void ULandscapeComponent::PostLoad() { Super::PostLoad(); 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::RoundToInt(LocalRelativeLocation.X) + LandscapeProxy->LandscapeSectionOffset.X; int32 CheckSectionBaseY = FMath::RoundToInt(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 WITH_EDITOR 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 && HeightmapTexture->HasAnyFlags(RF_Standalone)) { HeightmapTexture->ClearFlags(RF_Standalone); } for (int32 Idx = 0; Idx < WeightmapTextures.Num(); Idx++) { if (WeightmapTextures[Idx] && WeightmapTextures[Idx]->HasAnyFlags(RF_Standalone)) { WeightmapTextures[Idx]->ClearFlags(RF_Standalone); } } if (GIBakedBaseColorTexture) { if (GIBakedBaseColorTexture->GetOutermost() != GetOutermost()) { // The GIBakedBaseColorTexture property was never intended to be reassigned, but it was previously editable so we need to null any invalid values // it will get recreated by ALandscapeProxy::UpdateBakedTextures() GIBakedBaseColorTexture = nullptr; BakedTextureMaterialGuid = FGuid(); } else { // Remove public flag from GI textures to stop them being visible in the content browser. // Previous version of landscape set these flags on creation. if (GIBakedBaseColorTexture->HasAnyFlags(RF_Public)) { GIBakedBaseColorTexture->ClearFlags(RF_Public); } } } LastBakedTextureMaterialGuid = BakedTextureMaterialGuid; 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; } #endif // WITH_EDITOR #if WITH_EDITORONLY_DATA // Handle old MaterialInstance if (MaterialInstance_DEPRECATED) { MaterialInstances.Empty(1); MaterialInstances.Add(MaterialInstance_DEPRECATED); MaterialInstance_DEPRECATED = nullptr; #if WITH_EDITOR if (GIsEditor && MaterialInstances.Num() > 0 && MaterialInstances[0] != nullptr) { MaterialInstances[0]->ConditionalPostLoad(); UpdateMaterialInstances(); } #endif // WITH_EDITOR } #endif #if WITH_EDITOR auto ReparentObject = [this](UObject* Object) { if (Object && !Object->HasAllFlags(RF_Public | RF_Standalone) && (Object->GetOuter() != GetOuter()) && (Object->GetOutermost() == GetOutermost())) { Object->Rename(nullptr, GetOuter(), REN_ForceNoResetLoaders); return true; } return false; }; ReparentObject(HeightmapTexture); ReparentObject(XYOffsetmapTexture); for (UTexture2D* WeightmapTexture : WeightmapTextures) { ReparentObject(WeightmapTexture); } for (UTexture2D* MobileWeightmapTexture : MobileWeightmapTextures) { ReparentObject(MobileWeightmapTexture); } 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 FLandscapeLayer* EditLayer = GetLandscapeActor() ? GetLandscapeActor()->GetLayer(ItPair.Key) : nullptr) { if (LayerComponentData.DebugName != EditLayer->Name) { LayerComponentData.DebugName = EditLayer->Name; } } } for (UMaterialInstance* MaterialInstance : MaterialInstances) { ULandscapeMaterialInstanceConstant* CurrentMIC = Cast(MaterialInstance); while (ReparentObject(CurrentMIC)) { CurrentMIC = Cast(MaterialInstance->Parent); } } for (UMaterialInterface* MobileMaterialInterface : MobileMaterialInterfaces) { while (ReparentObject(MobileMaterialInterface)) { MobileMaterialInterface = Cast(MobileMaterialInterface) ? Cast(((UMaterialInstance*)MobileMaterialInterface)->Parent) : nullptr; } } for (UMaterialInstance* MobileCombinationMaterialInstance : MobileCombinationMaterialInstances) { while (ReparentObject(MobileCombinationMaterialInstance)) { MobileCombinationMaterialInstance = Cast(MobileCombinationMaterialInstance->Parent); } } #endif #if !UE_BUILD_SHIPPING // This will fix the data in case there is mismatch between save of asset/maps int8 MaxLOD = FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1; TArray 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 WITH_EDITOR 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 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_ForceNoResetLoaders | REN_NonTransactional); } } } } #endif #if !UE_BUILD_SHIPPING if (MobileCombinationMaterialInstances.Num() == 0) { if (GIsEditor) { UpdateMaterialInstances(); } else { if (UseMobileLandscapeMesh(GMaxRHIShaderPlatform)) { 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 if (!HasAnyFlags(RF_ClassDefaultObject)) { UWorld* World = GetWorld(); ERHIFeatureLevel::Type FeatureLevel = ((GEngine->GetDefaultWorldFeatureLevel() == ERHIFeatureLevel::ES3_1) || (World && (World->FeatureLevel <= 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() && UseMobileLandscapeMesh(GShaderPlatformForFeatureLevel[FeatureLevel])) { CheckGenerateLandscapePlatformData(false, nullptr); } } GrassData->ConditionalDiscardDataOnLoad(); } #endif // WITH_EDITOR #if WITH_EDITORONLY_DATA TArray ALandscapeProxy::LandscapeProxies; #endif ALandscapeProxy::ALandscapeProxy(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) #if WITH_EDITORONLY_DATA , TargetDisplayOrder(ELandscapeLayerDisplayMode::Default) #endif // WITH_EDITORONLY_DATA #if !WITH_EDITORONLY_DATA , LandscapeMaterialCached(nullptr) , LandscapeGrassTypes() , GrassMaxDiscardDistance(0.0f) #endif , bHasLandscapeGrass(true) { bReplicates = false; NetUpdateFrequency = 10.0f; SetHidden(false); SetReplicatingMovement(false); SetCanBeDamaged(false); CastShadow = true; bCastDynamicShadow = true; bCastStaticShadow = true; bCastContactShadow = true; bCastFarShadow = true; bCastHiddenShadow = false; bCastShadowAsTwoSided = false; bAffectDistanceFieldLighting = true; 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; bIsMovingToLevel = false; #endif // WITH_EDITORONLY_DATA ComponentScreenSizeToUseSubSections = 0.65f; LOD0ScreenSize = 0.5f; LOD0DistributionSetting = 1.25f; LODDistributionSetting = 3.0f; bCastStaticShadow = true; bUsedForNavigation = true; bFillCollisionUnderLandscapeForNavmesh = false; CollisionThickness = 16; BodyInstance.SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName); bGenerateOverlapEvents = false; #if WITH_EDITORONLY_DATA MaxPaintedLayersPerComponent = 0; bHasLayersContent = false; #endif #if WITH_EDITOR NumComponentsNeedingGrassMapRender = 0; NumTexturesToStreamForVisibleGrassMapRender = 0; NumComponentsNeedingTextureBaking = 0; if (VisibilityLayer == nullptr) { // Structure to hold one-time initialization struct FConstructorStatics { ConstructorHelpers::FObjectFinderOptional DataLayer; FConstructorStatics() : DataLayer(TEXT("LandscapeLayerInfoObject'/Engine/EditorLandscapeResources/DataLayer.DataLayer'")) { } }; static FConstructorStatics ConstructorStatics; VisibilityLayer = ConstructorStatics.DataLayer.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; bLandscapeLayersAreUsingLocalMerge = false; 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 LandscapeActor.Get(); } ALandscape* ALandscapeStreamingProxy::GetLandscapeActor() { return LandscapeActor.Get(); } ULandscapeInfo* ALandscapeProxy::CreateLandscapeInfo(bool bMapCheck) { ULandscapeInfo* LandscapeInfo = ULandscapeInfo::FindOrCreate(GetWorld(), LandscapeGuid); LandscapeInfo->RegisterActor(this, bMapCheck); 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; } static TArray GetLODScreenSizeArray(const ALandscapeProxy* InLandscapeProxy, const int32 InNumLODLevels) { static TConsoleVariableData* CVarSMLODDistanceScale = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.StaticMeshLODDistanceScale")); static IConsoleVariable* CVarLSLOD0DistributionScale = IConsoleManager::Get().FindConsoleVariable(TEXT("r.LandscapeLOD0DistributionScale")); float CurrentScreenSize = InLandscapeProxy->LOD0ScreenSize / CVarSMLODDistanceScale->GetValueOnGameThread(); const float ScreenSizeMult = 1.f / FMath::Max(InLandscapeProxy->LOD0DistributionSetting * CVarLSLOD0DistributionScale->GetFloat(), 1.01f); TArray Result; Result.Empty(InNumLODLevels); for (int32 Idx = 0; Idx < InNumLODLevels; ++Idx) { Result.Add(CurrentScreenSize); CurrentScreenSize *= ScreenSizeMult; } return Result; } TArray ALandscapeProxy::GetLODScreenSizeArray() const { const int32 MaxPossibleLOD = FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1; const int32 MaxLOD = MaxLODLevel != INDEX_NONE ? FMath::Min(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 ULandscapeComponent::GetGeneratedTextures() const { TArray 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 OutMaterials; for (UMaterialInstance* MaterialInstance : MaterialInstances) { for (ULandscapeMaterialInstanceConstant* CurrentMIC = Cast(MaterialInstance); CurrentMIC; CurrentMIC = Cast(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 ULandscapeComponent::GetGeneratedMaterialInstances() const { TArray OutMaterials; for (UMaterialInstance* MaterialInstance : MaterialInstances) { for (ULandscapeMaterialInstanceConstant* CurrentMIC = Cast(MaterialInstance); CurrentMIC; CurrentMIC = Cast(CurrentMIC->Parent)) { OutMaterials.Add(CurrentMIC); } } for (UMaterialInstanceConstant* MaterialInstance : MobileCombinationMaterialInstances) { for (ULandscapeMaterialInstanceConstant* CurrentMIC = Cast(MaterialInstance); CurrentMIC; CurrentMIC = Cast(CurrentMIC->Parent)) { OutMaterials.Add(CurrentMIC); } } return OutMaterials; } void ULandscapeComponent::GetGeneratedTexturesAndMaterialInstances(TArray& OutTexturesAndMaterials) const { TArray LocalTextures = GetGeneratedTextures(); TArray LocalMaterialInstances = GetGeneratedMaterialInstances(); OutTexturesAndMaterials.Reserve(LocalTextures.Num() + LocalMaterialInstances.Num()); OutTexturesAndMaterials.Append(LocalTextures); OutTexturesAndMaterials.Append(LocalMaterialInstances); } #endif ALandscapeProxy* ULandscapeComponent::GetLandscapeProxy() const { return CastChecked(GetOuter()); } 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) { ULevel* ActiveLightingScenario = OwnerLevel->OwningWorld->GetActiveLightingScenario(); UMapBuildDataRegistry* MapBuildData = NULL; if (ActiveLightingScenario && ActiveLightingScenario->MapBuildData) { MapBuildData = ActiveLightingScenario->MapBuildData; } else if (OwnerLevel->MapBuildData) { MapBuildData = OwnerLevel->MapBuildData; } if (MapBuildData) { return MapBuildData->GetMeshBuildData(MapBuildDataId); } } } return NULL; } bool ULandscapeComponent::IsPrecomputedLightingValid() const { return GetMeshMapBuildData() != NULL; } void ULandscapeComponent::PropagateLightingScenarioChange() { FComponentRecreateRenderStateContext Context(this); } TArray 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 (LODStreamingProxy != nullptr) { LODStreamingProxy->UnlinkStreaming(); } #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* 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() { check(LODStreamingProxy); LODStreamingProxy->ClearStreamingResourceState(); LODStreamingProxy->UnlinkStreaming(); const auto FeatureLevel = GetWorld()->FeatureLevel; FPrimitiveSceneProxy* Proxy = nullptr; if (FeatureLevel >= ERHIFeatureLevel::SM5 || !UseMobileLandscapeMesh(GShaderPlatformForFeatureLevel[FeatureLevel])) { Proxy = new FLandscapeComponentSceneProxy(this); } else // i.e. (FeatureLevel <= ERHIFeatureLevel::ES3_1) { if (PlatformData.HasValidRuntimeData()) { Proxy = new FLandscapeComponentSceneProxyMobile(this); LODStreamingProxy->InitResourceStateForMobileStreaming(); LODStreamingProxy->LinkStreaming(); } } return Proxy; } 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 CVarStaticMeshLODDistanceScale; static float LastValue = 1.0f; if (LastValue != CVarStaticMeshLODDistanceScale.GetValueOnAnyThread()) { LastValue = CVarStaticMeshLODDistanceScale.GetValueOnAnyThread(); for (auto* LandscapeComponent : TObjectRange(RF_ClassDefaultObject | RF_ArchetypeObject, true, EInternalObjectFlags::Garbage)) { LandscapeComponent->MarkRenderStateDirty(); } } } FAutoConsoleVariableSink OnStaticMeshLODDistanceScaleChangedSink(FConsoleCommandDelegate::CreateStatic(&OnStaticMeshLODDistanceScaleChanged)); void ULandscapeComponent::OnRegister() { Super::OnRegister(); if (GetLandscapeProxy()) { // Generate MID representing the MIC if (GetLandscapeProxy()->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 = GetLandscapeProxy()->GetWorld(); if (World) { ULandscapeInfo* Info = GetLandscapeInfo(); if (Info) { Info->RegisterActorComponent(this); } } } } void ULandscapeComponent::OnUnregister() { Super::OnUnregister(); #if WITH_EDITOR PhysicalMaterialTask.Release(); #endif if (GetLandscapeProxy()) { // Generate MID representing the MIC if (GetLandscapeProxy()->bUseDynamicMaterialInstance) { MaterialInstancesDynamic.Empty(); } // AActor::GetWorld checks for Unreachable and BeginDestroyed UWorld* World = GetLandscapeProxy()->GetWorld(); // Game worlds don't have landscape infos if (World && !World->IsGameWorld()) { ULandscapeInfo* Info = GetLandscapeInfo(); if (Info) { Info->UnregisterActorComponent(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& ULandscapeComponent::GetWeightmapTextures(bool InReturnEditingWeightmap) const { #if WITH_EDITORONLY_DATA if (InReturnEditingWeightmap) { if (const FLandscapeLayerComponentData* EditingLayer = GetEditingLayer()) { return EditingLayer->WeightmapData.Textures; } } #endif return WeightmapTextures; } TArray& ULandscapeComponent::GetWeightmapTextures(bool InReturnEditingWeightmap) { #if WITH_EDITORONLY_DATA if (InReturnEditingWeightmap) { if (FLandscapeLayerComponentData* EditingLayer = GetEditingLayer()) { return EditingLayer->WeightmapData.Textures; } } #endif return WeightmapTextures; } const TArray& 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& 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& ULandscapeComponent::GetWeightmapLayerAllocations(bool InReturnEditingWeightmap) const { #if WITH_EDITORONLY_DATA if (InReturnEditingWeightmap) { if (const FLandscapeLayerComponentData* EditingLayer = GetEditingLayer()) { return EditingLayer->WeightmapData.LayerAllocations; } } #endif return WeightmapLayerAllocations; } TArray& 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& 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& ULandscapeComponent::GetWeightmapLayerAllocations(bool InReturnEditingWeightmap) { #if WITH_EDITORONLY_DATA if (InReturnEditingWeightmap) { if (FLandscapeLayerComponentData* EditingLayer = GetEditingLayer()) { return EditingLayer->WeightmapData.LayerAllocations; } } #endif return WeightmapLayerAllocations; } #if WITH_EDITOR void ULandscapeComponent::SetEditingLayer(const FGuid& InEditingLayer) { LandscapeEditingLayer = InEditingLayer; } FLandscapeLayerComponentData* ULandscapeComponent::GetEditingLayer() { if (CachedEditingLayer != LandscapeEditingLayer) { CachedEditingLayer = LandscapeEditingLayer; CachedEditingLayerData = CachedEditingLayer.IsValid() ? LayersData.Find(CachedEditingLayer) : nullptr; } return CachedEditingLayerData; } const FLandscapeLayerComponentData* ULandscapeComponent::GetEditingLayer() const { if (CachedEditingLayer != LandscapeEditingLayer) { CachedEditingLayer = LandscapeEditingLayer; CachedEditingLayerData = CachedEditingLayer.IsValid() ? const_cast&>(LayersData).Find(CachedEditingLayer) : nullptr; } return CachedEditingLayerData; } void ULandscapeComponent::CopyFinalLayerIntoEditingLayer(FLandscapeEditDataInterface& DataInterface, TSet& ProcessedHeightmaps) { Modify(); GetLandscapeProxy()->Modify(); // Heightmap UTexture2D* EditingTexture = GetHeightmap(true); if (!ProcessedHeightmaps.Contains(EditingTexture)) { DataInterface.CopyTextureFromHeightmap(EditingTexture, this, 0); ProcessedHeightmaps.Add(EditingTexture); } // Weightmap const TArray& FinalWeightmapLayerAllocations = GetWeightmapLayerAllocations(); TArray& 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); } } const bool bEditingWeighmaps = true; const bool bSaveToTransactionBuffer = true; ReallocateWeightmaps(&DataInterface, bEditingWeighmaps, bSaveToTransactionBuffer); const TArray& 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 Fn) { for (auto& Pair : LayersData) { Fn(Pair.Key, Pair.Value); } } void ULandscapeComponent::AddLayerData(const FGuid& InLayerGuid, const FLandscapeLayerComponentData& InData) { Modify(); check(!LandscapeEditingLayer.IsValid()); FLandscapeLayerComponentData& Data = LayersData.FindOrAdd(InLayerGuid); Data = InData; CachedEditingLayer.Invalidate(); CachedEditingLayerData = nullptr; } void ULandscapeComponent::AddDefaultLayerData(const FGuid& InLayerGuid, const TArray& InComponentsUsingHeightmap, TMap& InOutCreatedHeightmapTextures) { Modify(); UTexture2D* ComponentHeightmap = GetHeightmap(); // Compute per layer data FLandscapeLayerComponentData* LayerData = GetLayerData(InLayerGuid); if (LayerData == nullptr || !LayerData->IsInitialized()) { const FLandscapeLayer* EditLayer = GetLandscapeActor() ? GetLandscapeActor()->GetLayer(InLayerGuid) : nullptr; FLandscapeLayerComponentData NewData(EditLayer ? EditLayer->Name : FName()); // Setup Heightmap data UTexture2D** LayerHeightmap = InOutCreatedHeightmapTextures.Find(ComponentHeightmap); if (LayerHeightmap == nullptr) { UTexture2D* NewLayerHeightmap = GetLandscapeProxy()->CreateLandscapeTexture(ComponentHeightmap->Source.GetSizeX(), ComponentHeightmap->Source.GetSizeY(), TEXTUREGROUP_Terrain_Heightmap, ComponentHeightmap->Source.GetFormat()); LayerHeightmap = &InOutCreatedHeightmapTextures.Add(ComponentHeightmap, NewLayerHeightmap); ULandscapeComponent::CreateEmptyTextureMips(NewLayerHeightmap, true); // Init Mip0 to be at 32768 which is equal to "0" FColor* Mip0Data = (FColor*)NewLayerHeightmap->Source.LockMip(0); for (ULandscapeComponent* ComponentUsingHeightmap : InComponentsUsingHeightmap) { int32 HeightmapComponentOffsetX = FMath::RoundToInt((float)NewLayerHeightmap->Source.GetSizeX() * ComponentUsingHeightmap->HeightmapScaleBias.Z); int32 HeightmapComponentOffsetY = FMath::RoundToInt((float)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.0f; Mip0Data[HeightTexDataIdx].A = 0.0f; } } } } } NewLayerHeightmap->Source.UnlockMip(0); NewLayerHeightmap->UpdateResource(); } NewData.HeightmapData.Texture = *LayerHeightmap; // Nothing to do for Weightmap by default AddLayerData(InLayerGuid, MoveTemp(NewData)); } } void ULandscapeComponent::RemoveLayerData(const FGuid& InLayerGuid) { Modify(); check(!LandscapeEditingLayer.IsValid()); LayersData.Remove(InLayerGuid); CachedEditingLayer.Invalidate(); CachedEditingLayerData = nullptr; } void ULandscapeComponent::SetHeightmap(UTexture2D* NewHeightmap) { check(NewHeightmap != nullptr); HeightmapTexture = NewHeightmap; } void ULandscapeComponent::SetWeightmapTextures(const TArray& InNewWeightmapTextures, bool InApplyToEditingWeightmap) { #if WITH_EDITORONLY_DATA FLandscapeLayerComponentData* EditingLayer = GetEditingLayer(); if (InApplyToEditingWeightmap && EditingLayer != nullptr) { EditingLayer->WeightmapData.Textures.Reset(InNewWeightmapTextures.Num()); EditingLayer->WeightmapData.Textures.Append(InNewWeightmapTextures); } else #endif { WeightmapTextures = InNewWeightmapTextures; } } void ULandscapeComponent::SetWeightmapLayerAllocations(const TArray& InNewWeightmapLayerAllocations) { WeightmapLayerAllocations = InNewWeightmapLayerAllocations; } TArray& ULandscapeComponent::GetWeightmapTexturesUsage(bool InReturnEditingWeightmap) { #if WITH_EDITORONLY_DATA if (InReturnEditingWeightmap) { if (FLandscapeLayerComponentData* EditingLayer = GetEditingLayer()) { return EditingLayer->WeightmapData.TextureUsages; } } #endif return WeightmapTexturesUsage; } const TArray& ULandscapeComponent::GetWeightmapTexturesUsage(bool InReturnEditingWeightmap) const { #if WITH_EDITORONLY_DATA if (InReturnEditingWeightmap) { if (const FLandscapeLayerComponentData* EditingLayer = GetEditingLayer()) { return EditingLayer->WeightmapData.TextureUsages; } } #endif return WeightmapTexturesUsage; } TArray& ULandscapeComponent::GetWeightmapTexturesUsage(const FGuid& InLayerGuid) { #if WITH_EDITORONLY_DATA if (InLayerGuid.IsValid()) { if (FLandscapeLayerComponentData* LayerData = GetLayerData(InLayerGuid)) { return LayerData->WeightmapData.TextureUsages; } } #endif return WeightmapTexturesUsage; } const TArray& ULandscapeComponent::GetWeightmapTexturesUsage(const FGuid& InLayerGuid) const { #if WITH_EDITORONLY_DATA if (InLayerGuid.IsValid()) { if (const FLandscapeLayerComponentData* LayerData = GetLayerData(InLayerGuid)) { return LayerData->WeightmapData.TextureUsages; } } #endif return WeightmapTexturesUsage; } void ULandscapeComponent::SetWeightmapTexturesUsage(const TArray& InNewWeightmapTexturesUsage, bool InApplyToEditingWeightmap) { #if WITH_EDITORONLY_DATA FLandscapeLayerComponentData* EditingLayer = GetEditingLayer(); if (InApplyToEditingWeightmap && EditingLayer != nullptr) { EditingLayer->WeightmapData.TextureUsages.Reset(InNewWeightmapTexturesUsage.Num()); EditingLayer->WeightmapData.TextureUsages.Append(InNewWeightmapTexturesUsage); } else #endif { WeightmapTexturesUsage = InNewWeightmapTexturesUsage; } } #endif void ALandscapeProxy::PostRegisterAllComponents() { Super::PostRegisterAllComponents(); ULandscapeInfo* LandscapeInfo = nullptr; if (!IsPendingKillPending()) { // Duplicated Landscapes don't have a valid guid until PostEditImport is called, we'll register then if (LandscapeGuid.IsValid()) { #if WITH_EDITOR if (GIsEditor && !GetWorld()->IsGameWorld()) { // 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 (LandscapeComponents.Num() > 0 && LandscapeComponents[0] == nullptr) { LandscapeComponents.Empty(); } UpdateCachedHasLayersContent(true); // Cache the value at this point as CreateLandscapeInfo (-> RegisterActor) might create/destroy layers content if there was a mismatch between landscape & proxy // Check the actual flag here not HasLayersContent() which could return true if the LandscapeActor is valid. bool bHasLayersContentBefore = bHasLayersContent; LandscapeInfo = CreateLandscapeInfo(true); FixupWeightmaps(); const bool bNeedOldDataMigration = !bHasLayersContentBefore && CanHaveLayersContent(); if (bNeedOldDataMigration && LandscapeInfo->LandscapeActor.IsValid() && LandscapeInfo->LandscapeActor.Get()->HasLayersContent()) { LandscapeInfo->LandscapeActor.Get()->CopyOldDataToDefaultLayer(this); } } else #endif // WITH_EDITOR { LandscapeInfo = CreateLandscapeInfo(true); } } if (UWorld* OwningWorld = GetWorld()) { if (ULandscapeSubsystem* LandscapeSubsystem = OwningWorld->GetSubsystem()) { LandscapeSubsystem->RegisterActor(this); } } } #if WITH_EDITOR // Game worlds don't have landscape infos if (!GetWorld()->IsGameWorld() && !IsPendingKillPending()) { if (LandscapeGuid.IsValid()) { LandscapeInfo->FixupProxiesTransform(); } } #endif } void ALandscapeProxy::UnregisterAllComponents(const bool bForReregister) { // Game worlds don't have landscape infos // 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()) { 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 FArchive& operator<<(FArchive& Ar, FLandscapeLayerStruct*& L) { if (L) { Ar << L->LayerInfoObj; #if WITH_EDITORONLY_DATA return Ar << L->ThumbnailMIC; #else return Ar; #endif // WITH_EDITORONLY_DATA } return Ar; } 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() { if (!LandscapeGuid.IsValid()) { LandscapeGuid = FGuid::NewGuid(); } else { #if WITH_EDITOR UWorld* CurrentWorld = GetWorld(); for (ALandscape* Landscape : TObjectRange(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 } #if WITH_EDITOR for (FLandscapeLayer& Layer : LandscapeLayers) { // For now, only Layer reserved for Landscape Spline uses AlphaBlend Layer.BlendMode = (Layer.Guid == LandscapeSplinesTargetLayerGuid) ? LSBM_AlphaBlend : LSBM_AdditiveBlend; for (FLandscapeLayerBrush& Brush : Layer.Brushes) { Brush.SetOwner(this); } } #endif Super::PostLoad(); } FBox ALandscape::GetLoadedBounds() const { return GetLandscapeInfo()->GetLoadedBounds(); } // ---------------------------------------------------------------------------------- // This shader allows to render parts of the heightmaps (all pixels except the redundant ones on the right/bottom edges) in an atlas render target (uncompressed height) class FLandscapeMergeHeightmapsPS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FLandscapeMergeHeightmapsPS); SHADER_USE_PARAMETER_STRUCT(FLandscapeMergeHeightmapsPS, FGlobalShader); public: BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER(FUintVector4, InHeightmapAtlasSubregion) SHADER_PARAMETER(FUintVector4, InHeightmapSubregion) SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D, InHeightmap) 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("MERGE_HEIGHTMAP"), 1); } static void MergeHeightmap(FRDGBuilder& GraphBuilder, FParameters* InParameters, const FIntRect& InRenderTargetArea) { FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(GMaxRHIFeatureLevel); TShaderMapRef PixelShader(ShaderMap); FPixelShaderUtils::AddFullscreenPass( GraphBuilder, ShaderMap, RDG_EVENT_NAME("LandscapeLayers_MergeHeightmap"), PixelShader, InParameters, InRenderTargetArea); } }; IMPLEMENT_GLOBAL_SHADER(FLandscapeMergeHeightmapsPS, "/Engine/Private/Landscape/LandscapeMergeHeightmapsPS.usf", "MergeHeightmap", SF_Pixel); // ---------------------------------------------------------------------------------- // This shader allows to resample the heightmap (bilinear interpolation) from a given atlas usually heightmap produced by FLandscapeMergeHeightmapsPS : // The output heightmap's heights 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 FLandscapeResampleHeightmapsPS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FLandscapeResampleHeightmapsPS); SHADER_USE_PARAMETER_STRUCT(FLandscapeResampleHeightmapsPS, FGlobalShader); public: BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER(FMatrix44f, InOutputUVToMergedHeightmapUV) SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D, InMergedHeightmap) SHADER_PARAMETER_SAMPLER(SamplerState, InMergedHeightmapSampler) SHADER_PARAMETER(FUintVector2, InRenderAreaSize) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() class FCompressHeight : SHADER_PERMUTATION_BOOL("COMPRESS_HEIGHT"); using FPermutationDomain = TShaderPermutationDomain; static FPermutationDomain GetPermutationVector(bool bCompressHeight) { FPermutationDomain PermutationVector; PermutationVector.Set(bCompressHeight); return PermutationVector; } static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& InParameters) { return true; } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& InParameters, FShaderCompilerEnvironment& OutEnvironment) { OutEnvironment.SetDefine(TEXT("RESAMPLE_HEIGHTMAP"), 1); } static void ResampleHeightmap(FRDGBuilder& GraphBuilder, FParameters* InParameters, bool bCompressHeight) { FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(GMaxRHIFeatureLevel); const FLandscapeResampleHeightmapsPS::FPermutationDomain PixelPermutationVector = FLandscapeResampleHeightmapsPS::GetPermutationVector(bCompressHeight); TShaderMapRef PixelShader(ShaderMap, PixelPermutationVector); FPixelShaderUtils::AddFullscreenPass( GraphBuilder, ShaderMap, RDG_EVENT_NAME("LandscapeLayers_ResampleHeightmap"), PixelShader, InParameters, FIntRect(0, 0, InParameters->InRenderAreaSize.X, InParameters->InRenderAreaSize.Y)); } }; IMPLEMENT_GLOBAL_SHADER(FLandscapeResampleHeightmapsPS, "/Engine/Private/Landscape/LandscapeMergeHeightmapsPS.usf", "ResampleHeightmap", SF_Pixel); // Render-thread version of the data / functions we need for the local merge of edit layers : namespace RenderMergedLandscape_RenderThread { struct FHeightmapRenderInfo { // 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 OutputUVToMergedHeightmapUV; FBox RenderAreaExtents; // TODO [jonathan.bard] : remove ? This is only used to compute the temporary render target size FIntPoint SubsectionSizeQuads; int32 NumSubsections = 1; bool bCompressHeight = false; TMap ComponentHeightmapsToRender; }; void RenderMergedHeightmap(const FHeightmapRenderInfo& InRenderInfo, FRDGBuilder& GraphBuilder, FRDGTextureRef OutputTexture) { // Find the total area that those components need to be rendered to : FIntRect ComponentKeyRect; for (auto Iter = InRenderInfo.ComponentHeightmapsToRender.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 heightmaps. Use PF_G16 (decoded height) as this will be resampled using bilinear sampling : FRDGTextureDesc Desc = FRDGTextureDesc::Create2D(RenderTargetSize, PF_G16, FClearValueBinding::Black, TexCreate_RenderTargetable | TexCreate_ShaderResource); FRDGTextureRef AtlasTexture = GraphBuilder.CreateTexture(Desc, TEXT("LandscapeHeightmapAtlas")); FRDGTextureSRVRef AtlasTextureSRV = GraphBuilder.CreateSRV(FRDGTextureSRVDesc::Create(AtlasTexture)); FRenderTargetBinding AtlasTextureRT(AtlasTexture, ERenderTargetLoadAction::ELoad); TMap HeightmapTextureSRVs; // 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* HeightmapResourceSubregion = InRenderInfo.ComponentHeightmapsToRender.Find(LandscapeComponentKey)) { FIntPoint SubsectionSubregionSize = HeightmapResourceSubregion->Subregion.Size() / InRenderInfo.NumSubsections; FRDGTextureSRVRef* Heightmap = HeightmapTextureSRVs.Find(HeightmapResourceSubregion->Texture); if (Heightmap == nullptr) { FString* DebugString = GraphBuilder.AllocObject(HeightmapResourceSubregion->Texture->GetTextureName().ToString()); FRDGTextureRef TextureRef = GraphBuilder.RegisterExternalTexture(CreateRenderTarget(HeightmapResourceSubregion->Texture->TextureRHI, **DebugString)); Heightmap = &HeightmapTextureSRVs.Add(HeightmapResourceSubregion->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 HeightmapAtlasSubregion; HeightmapAtlasSubregion.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 HeightmapSubregion to align with the region we're writing, which would get // messy in cases of different mip levels. HeightmapAtlasSubregion.Max = HeightmapAtlasSubregion.Min + InRenderInfo.SubsectionSizeQuads + 1; FIntRect HeightmapSubregion; HeightmapSubregion.Min = HeightmapResourceSubregion->Subregion.Min + SubsectionLocalKey * SubsectionSubregionSize; HeightmapSubregion.Max = HeightmapSubregion.Min + SubsectionSubregionSize; FLandscapeMergeHeightmapsPS::FParameters* MergeHeightmapsPSParams = GraphBuilder.AllocParameters(); MergeHeightmapsPSParams->InHeightmapAtlasSubregion = FUintVector4(HeightmapAtlasSubregion.Min.X, HeightmapAtlasSubregion.Min.Y, HeightmapAtlasSubregion.Max.X, HeightmapAtlasSubregion.Max.Y); MergeHeightmapsPSParams->InHeightmapSubregion = FUintVector4(HeightmapSubregion.Min.X, HeightmapSubregion.Min.Y, HeightmapSubregion.Max.X, HeightmapSubregion.Max.Y); MergeHeightmapsPSParams->InHeightmap = *Heightmap; MergeHeightmapsPSParams->RenderTargets[0] = AtlasTextureRT; FLandscapeMergeHeightmapsPS::MergeHeightmap(GraphBuilder, MergeHeightmapsPSParams, HeightmapAtlasSubregion); } } } else { // We can clear all subsections of a given component at once : FIntRect HeightmapAtlasSubregion; HeightmapAtlasSubregion.Min = LandscapeComponentKey * ComponentSizeQuads; HeightmapAtlasSubregion.Max = HeightmapAtlasSubregion.Min + ComponentSizeQuads; FRDGTextureClearInfo ClearInfo; ClearInfo.Viewport = HeightmapAtlasSubregion; AddClearRenderTargetPass(GraphBuilder, AtlasTexture, ClearInfo); } } } { FIntVector RenderAreaSize = OutputTexture->Desc.GetSize(); FLandscapeResampleHeightmapsPS::FParameters* ResampleHeightmapsPSParams = GraphBuilder.AllocParameters(); ResampleHeightmapsPSParams->InOutputUVToMergedHeightmapUV = FMatrix44f(InRenderInfo.OutputUVToMergedHeightmapUV); ResampleHeightmapsPSParams->InMergedHeightmap = AtlasTextureSRV; ResampleHeightmapsPSParams->InMergedHeightmapSampler = TStaticSamplerState::GetRHI(); ResampleHeightmapsPSParams->InRenderAreaSize = FUintVector2((uint32)RenderAreaSize.X, (uint32)RenderAreaSize.Y); ResampleHeightmapsPSParams->RenderTargets[0] = FRenderTargetBinding(OutputTexture, ERenderTargetLoadAction::ENoAction);; // We now need to resample the atlas texture where the render area is : FLandscapeResampleHeightmapsPS::ResampleHeightmap(GraphBuilder, ResampleHeightmapsPSParams, InRenderInfo.bCompressHeight); } } } 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; } // TODO [jonathan.bard] This does not support being called at runtime yet because ULandscapeInfo::LandscapeActor is not valid at runtime or in non-editor worlds (e.g. PIE), for no good reason : void ALandscape::RenderHeightmap(const FTransform& InRenderAreaWorldTransform, const FBox2D& InRenderAreaExtents, UTextureRenderTarget2D* 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 RenderHeightmap would just use that function. TRACE_CPUPROFILER_EVENT_SCOPE(Landscape_RenderMergedHeightmap); RenderCaptureInterface::FScopedCapture RenderCapture((RenderCaptureNextHeightmapRenders != 0), TEXT("RenderHeightmapCapture")); RenderCaptureNextHeightmapRenders = FMath::Max(RenderCaptureNextHeightmapRenders - 1, 0); ULandscapeInfo* Info = GetLandscapeInfo(); if (Info == nullptr) { UE_LOG(LogLandscape, Error, TEXT("RenderHeightmap : Cannot render anything if there's no associated landscape info with this landscape (%s)"), *GetFullName()); return; } // Check render target validity : if (OutRenderTarget == nullptr) { UE_LOG(LogLandscape, Error, TEXT("RenderHeightmap : Missing render target")); return; } bool bCompressHeight = false; if (!IsValidRenderTargetFormatHeightmap(OutRenderTarget->GetFormat(), bCompressHeight)) { UE_LOG(LogLandscape, Warning, TEXT("RenderHeightmap : invalid render target format for rendering heightmap (%s)"), GetPixelFormatString(OutRenderTarget->GetFormat())); return; } // 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(InRenderAreaExtents.Min, 0.0), FVector(InRenderAreaExtents.Max, 0.0)), InRenderAreaWorldTransform.ToMatrixWithScale(), FColor::Blue, TEXT("")); // Don't do anything if this render area overlaps with no landscape component : TMap OverlappedComponents; FIntRect ComponentIndicesBoundingRect; if (!Info->GetOverlappedComponents(InRenderAreaWorldTransform, InRenderAreaExtents, OverlappedComponents, ComponentIndicesBoundingRect)) { UE_LOG(LogLandscape, Log, TEXT("RenderHeightmap : no heightmap to render")); return; } const FTransform& LandscapeTransform = GetTransform(); RenderMergedLandscape_RenderThread::FHeightmapRenderInfo HeightmapMergeRenderInfo; // For now, merge the heightmap at max resolution : HeightmapMergeRenderInfo.SubsectionSizeQuads = SubsectionSizeQuads; HeightmapMergeRenderInfo.NumSubsections = NumSubsections; HeightmapMergeRenderInfo.bCompressHeight = bCompressHeight; for (auto It : OverlappedComponents) { ULandscapeComponent* Component = It.Value; FIntPoint ComponentKey = It.Key; UTexture2D* ComponentHeightmap = Component->GetHeightmap(); // Get the subregion of the heightmap that this component uses (differs due to texture sharing). // HeightmapScaleBias ZW values give us the offset of the component in a shared texture. You might think that XY would // give the portion of the texture it occupies, but no, XY are 1/size, for some reason. Just calculate the subregion // size ourselves. int32 ComponentSize = Component->NumSubsections * (Component->SubsectionSizeQuads + 1); FIntPoint HeightmapOffset(0, 0); FTextureResource* HeightmapResource = ComponentHeightmap->GetResource(); if (ensure(HeightmapResource)) { // We get the overall heightmap size via the resource instead of direct GetSizeX/Y calls because apparently // the latter are unreliable while the texture is being built. HeightmapOffset = FIntPoint( FMath::RoundToDouble(Component->HeightmapScaleBias.Z * HeightmapResource->GetSizeX()), FMath::RoundToDouble(Component->HeightmapScaleBias.W * HeightmapResource->GetSizeY())); } // When mips are partially loaded, we need to take that into consideration when merging the heightmap : uint32 MipBias = ComponentHeightmap->GetNumMips() - ComponentHeightmap->GetNumResidentMips(); // Theoretically speaking, all of our component heightmaps should be powers of two when we include the duplicated // rows/columns across subsections, so we shouldn't get weird truncation results here... HeightmapOffset.X >>= MipBias; HeightmapOffset.Y >>= MipBias; ComponentSize >>= MipBias; // Effective area of the texture affecting this component (because of texture sharing) : FIntRect HeightmapSubregion(HeightmapOffset, HeightmapOffset + ComponentSize); HeightmapMergeRenderInfo.ComponentHeightmapsToRender.Add(ComponentKey, FTexture2DResourceSubregion(ComponentHeightmap->GetResource()->GetTexture2DResource(), HeightmapSubregion)); } // Create the transform that will go from output target UVs to world space: FVector OutputUVOrigin = InRenderAreaWorldTransform.TransformPosition(FVector(InRenderAreaExtents.Min.X, InRenderAreaExtents.Min.Y, 0.0)); FVector OutputUVScale = InRenderAreaWorldTransform.GetScale3D() * FVector(InRenderAreaExtents.GetSize(), 1.0); FTransform OutputUVToWorld(InRenderAreaWorldTransform.GetRotation(), OutputUVOrigin, OutputUVScale); // Create the transform that will go from merged heightmap 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 heightmap in world coordinates is actually one quad bigger in each direction. check((ComponentIndicesBoundingRect.Min.X < ComponentIndicesBoundingRect.Max.X) && (ComponentIndicesBoundingRect.Min.Y < ComponentIndicesBoundingRect.Max.Y)); FVector MergedHeightmapScale = (FVector(ComponentIndicesBoundingRect.Max - ComponentIndicesBoundingRect.Min) * static_cast(ComponentSizeQuads) + 1) * LandscapeTransform.GetScale3D(); MergedHeightmapScale.Z = 1.0f; FVector MergedHeightmapUVOrigin = LandscapeTransform.TransformPosition(FVector(ComponentIndicesBoundingRect.Min) * (double)ComponentSizeQuads - FVector(0.5,0.5,0)); FTransform MergedHeightmapUVToWorld(LandscapeTransform.GetRotation(), MergedHeightmapUVOrigin, MergedHeightmapScale); HeightmapMergeRenderInfo.OutputUVToMergedHeightmapUV = OutputUVToWorld.ToMatrixWithScale() * MergedHeightmapUVToWorld.ToInverseMatrixWithScale(); // Extract the render thread version of the render target : FTextureRenderTarget2DResource* OutputRenderTargetResource = OutRenderTarget->GameThread_GetRenderTargetResource()->GetTextureRenderTarget2DResource(); check(OutputRenderTargetResource != nullptr); ENQUEUE_RENDER_COMMAND(RenderHeightmap)([HeightmapMergeRenderInfo, OutputRenderTargetResource](FRHICommandListImmediate& RHICmdList) { FMemMark Mark(FMemStack::Get()); FRDGBuilder GraphBuilder(RHICmdList, RDG_EVENT_NAME("RenderHeightmap")); FRDGTextureRef OutputHeightmap = GraphBuilder.RegisterExternalTexture(CreateRenderTarget(OutputRenderTargetResource->GetTextureRHI(), TEXT("MergedHeightmap"))); RenderMergedLandscape_RenderThread::RenderMergedHeightmap(HeightmapMergeRenderInfo, GraphBuilder, OutputHeightmap); GraphBuilder.Execute(); }); } #if WITH_EDITOR FBox ALandscape::GetCompleteBounds() const { if (ensure(GetLandscapeInfo())) { return GetLandscapeInfo()->GetCompleteBounds(); } else { return FBox(EForceInit::ForceInit); } } void ALandscapeProxy::OnFeatureLevelChanged(ERHIFeatureLevel::Type NewFeatureLevel) { FlushGrassComponents(); UpdateAllComponentMaterialInstances(); if (UseMobileLandscapeMesh(GShaderPlatformForFeatureLevel[NewFeatureLevel])) { for (ULandscapeComponent* Component : LandscapeComponents) { if (Component != nullptr) { Component->CheckGenerateLandscapePlatformData(false, nullptr); } } } } #endif void ALandscapeProxy::PreSave(const class ITargetPlatform* TargetPlatform) { PRAGMA_DISABLE_DEPRECATION_WARNINGS; Super::PreSave(TargetPlatform); PRAGMA_ENABLE_DEPRECATION_WARNINGS; } void ALandscapeProxy::PreSave(FObjectPreSaveContext ObjectSaveContext) { Super::PreSave(ObjectSaveContext); #if WITH_EDITOR // Work out whether we have grass or not for the next game run BuildGrassMaps(); //Update the baked textures before saving BuildGIBakedTextures(); for (ULandscapeComponent* Component : LandscapeComponents) { // Reset flag Component->GrassData->bIsDirty = false; } 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 FLandscapeLayer* EditLayer = Landscape->GetLayer(LayerGuid)) { LayerData.DebugName = EditLayer->Name; } }); } } #endif // WITH_EDITOR } void ALandscapeProxy::Serialize(FArchive& Ar) { Super::Serialize(Ar); Ar.UsingCustomVersion(FLandscapeCustomVersion::GUID); Ar.UsingCustomVersion(FEditorObjectVersion::GUID); #if WITH_EDITORONLY_DATA if (Ar.IsLoading() && Ar.CustomVer(FLandscapeCustomVersion::GUID) < FLandscapeCustomVersion::MigrateOldPropertiesToNewRenderingProperties) { 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)]; } } } #endif // WITH_EDITORONLY_DATA } void ALandscapeProxy::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector) { ALandscapeProxy* This = CastChecked(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; } FLandscapeEditorLayerSettings& FLandscapeInfoLayerSettings::GetEditorSettings() const { check(LayerInfoObj); ULandscapeInfo* LandscapeInfo = Owner->GetLandscapeInfo(); return LandscapeInfo->GetLayerEditorSettings(LayerInfoObj); } FLandscapeEditorLayerSettings& ULandscapeInfo::GetLayerEditorSettings(ULandscapeLayerInfoObject* LayerInfo) const { ALandscapeProxy* Proxy = GetLandscapeProxy(); FLandscapeEditorLayerSettings* EditorLayerSettings = Proxy->EditorLayerSettings.FindByKey(LayerInfo); if (EditorLayerSettings) { return *EditorLayerSettings; } else { int32 Index = Proxy->EditorLayerSettings.Add(FLandscapeEditorLayerSettings(LayerInfo)); return Proxy->EditorLayerSettings[Index]; } } void ULandscapeInfo::CreateLayerEditorSettingsFor(ULandscapeLayerInfoObject* LayerInfo) { ForAllLandscapeProxies([LayerInfo](ALandscapeProxy* Proxy) { FLandscapeEditorLayerSettings* EditorLayerSettings = Proxy->EditorLayerSettings.FindByKey(LayerInfo); if (!EditorLayerSettings) { Proxy->Modify(); Proxy->EditorLayerSettings.Add(FLandscapeEditorLayerSettings(LayerInfo)); } }); } 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, bool bInvalidate) { TRACE_CPUPROFILER_EVENT_SCOPE(ULandscapeInfo::UpdateLayerInfoMapInternal); bool bHasCollision = false; if (GIsEditor) { if (Proxy) { if (bInvalidate) { // this is a horribly dangerous combination of parameters... for (int32 i = 0; i < Layers.Num(); i++) { if (Layers[i].Owner == Proxy) { Layers.RemoveAt(i--); } } } else // Proxy && !bInvalidate { TArray LayerNames = Proxy->GetLayersFromMaterial(); // Validate any existing layer infos owned by this proxy for (int32 i = 0; i < Layers.Num(); i++) { if (Layers[i].Owner == Proxy) { Layers[i].bValid = LayerNames.Contains(Layers[i].GetLayerName()); } } // Add placeholders for any unused material layers for (int32 i = 0; i < LayerNames.Num(); i++) { int32 LayerInfoIndex = GetLayerInfoIndex(LayerNames[i]); if (LayerInfoIndex == INDEX_NONE) { FLandscapeInfoLayerSettings LayerSettings(LayerNames[i], Proxy); LayerSettings.bValid = true; Layers.Add(LayerSettings); } } // Populate from layers used in components for (int32 ComponentIndex = 0; ComponentIndex < Proxy->LandscapeComponents.Num(); ComponentIndex++) { ULandscapeComponent* Component = Proxy->LandscapeComponents[ComponentIndex]; // Add layers from per-component override materials if (Component->OverrideMaterial != nullptr) { TArray ComponentLayerNames = Proxy->GetLayersFromMaterial(Component->OverrideMaterial); for (int32 i = 0; i < ComponentLayerNames.Num(); i++) { int32 LayerInfoIndex = GetLayerInfoIndex(ComponentLayerNames[i]); if (LayerInfoIndex == INDEX_NONE) { FLandscapeInfoLayerSettings LayerSettings(ComponentLayerNames[i], Proxy); LayerSettings.bValid = true; Layers.Add(LayerSettings); } } } const TArray& ComponentWeightmapLayerAllocations = Component->GetWeightmapLayerAllocations(); for (int32 AllocationIndex = 0; AllocationIndex < ComponentWeightmapLayerAllocations.Num(); AllocationIndex++) { ULandscapeLayerInfoObject* LayerInfo = ComponentWeightmapLayerAllocations[AllocationIndex].LayerInfo; if (LayerInfo) { int32 LayerInfoIndex = GetLayerInfoIndex(LayerInfo); bool bValid = LayerNames.Contains(LayerInfo->LayerName); if (bValid) { //LayerInfo->IsReferencedFromLoadedData = true; } if (LayerInfoIndex != INDEX_NONE) { FLandscapeInfoLayerSettings& LayerSettings = Layers[LayerInfoIndex]; // Valid layer infos take precedence over invalid ones // Landscape Actors take precedence over Proxies if ((bValid && !LayerSettings.bValid) || (bValid == LayerSettings.bValid && Proxy->IsA())) { LayerSettings.Owner = Proxy; LayerSettings.bValid = bValid; LayerSettings.ThumbnailMIC = nullptr; } } else { // handle existing placeholder layers LayerInfoIndex = GetLayerInfoIndex(LayerInfo->LayerName); if (LayerInfoIndex != INDEX_NONE) { FLandscapeInfoLayerSettings& LayerSettings = Layers[LayerInfoIndex]; //if (LayerSettings.Owner == Proxy) { LayerSettings.Owner = Proxy; LayerSettings.LayerInfoObj = LayerInfo; LayerSettings.bValid = bValid; LayerSettings.ThumbnailMIC = nullptr; } } else { FLandscapeInfoLayerSettings LayerSettings(LayerInfo, Proxy); LayerSettings.bValid = bValid; Layers.Add(LayerSettings); } } } } } // Add any layer infos cached in the actor Proxy->EditorLayerSettings.RemoveAll([](const FLandscapeEditorLayerSettings& Settings) { return Settings.LayerInfoObj == nullptr; }); for (int32 i = 0; i < Proxy->EditorLayerSettings.Num(); i++) { FLandscapeEditorLayerSettings& EditorLayerSettings = Proxy->EditorLayerSettings[i]; if (LayerNames.Contains(EditorLayerSettings.LayerInfoObj->LayerName)) { // intentionally using the layer name here so we don't add layer infos from // the cache that have the same name as an actual assignment from a component above int32 LayerInfoIndex = GetLayerInfoIndex(EditorLayerSettings.LayerInfoObj->LayerName); if (LayerInfoIndex != INDEX_NONE) { FLandscapeInfoLayerSettings& LayerSettings = Layers[LayerInfoIndex]; if (LayerSettings.LayerInfoObj == nullptr) { LayerSettings.Owner = Proxy; LayerSettings.LayerInfoObj = EditorLayerSettings.LayerInfoObj; LayerSettings.bValid = true; } } } else { Proxy->Modify(); Proxy->EditorLayerSettings.RemoveAt(i--); } } } } else // !Proxy { Layers.Empty(); if (!bInvalidate) { ForAllLandscapeProxies([this](ALandscapeProxy* EachProxy) { if (!EachProxy->IsPendingKillPending()) { checkSlow(EachProxy->GetLandscapeInfo() == this); UpdateLayerInfoMapInternal(EachProxy, false); } }); } } //if (GCallbackEvent) //{ // GCallbackEvent->Send( CALLBACK_EditorPostModal ); //} } return bHasCollision; } bool ULandscapeInfo::UpdateLayerInfoMap(ALandscapeProxy* Proxy /*= nullptr*/, bool bInvalidate /*= false*/) { bool bResult = UpdateLayerInfoMapInternal(Proxy, bInvalidate); if (GIsEditor) { ALandscape* Landscape = LandscapeActor.Get(); if (Landscape && Landscape->HasLayersContent()) { Landscape->RequestLayersInitialization(/*bInRequestContentUpdate*/false); } } return bResult; } #endif // WITH_EDITOR void ALandscapeProxy::PostLoad() { Super::PostLoad(); // 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); } if ((GetLinker() && (GetLinker()->UEVer() < VER_UE4_LANDSCAPE_COMPONENT_LAZY_REFERENCES)) || LandscapeComponents.Num() != CollisionComponents.Num() || LandscapeComponents.ContainsByPredicate([](ULandscapeComponent* Comp) { return ((Comp != nullptr) && !Comp->CollisionComponent.IsValid()); })) { CreateLandscapeInfo(); } #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(); } PRAGMA_ENABLE_DEPRECATION_WARNINGS; if (GIsEditor && GetWorld() && !GetWorld()->IsGameWorld()) { if ((GetLinker() && (GetLinker()->UEVer() < VER_UE4_LANDSCAPE_COMPONENT_LAZY_REFERENCES)) || LandscapeComponents.Num() != CollisionComponents.Num() || LandscapeComponents.ContainsByPredicate([](ULandscapeComponent* Comp) { return ((Comp != nullptr) && !Comp->CollisionComponent.IsValid()); })) { // Need to clean up invalid collision components RecreateCollisionComponents(); } } EditorLayerSettings.RemoveAll([](const FLandscapeEditorLayerSettings& Settings) { return Settings.LayerInfoObj == nullptr; }); if (EditorCachedLayerInfos_DEPRECATED.Num() > 0) { for (int32 i = 0; i < EditorCachedLayerInfos_DEPRECATED.Num(); i++) { EditorLayerSettings.Add(FLandscapeEditorLayerSettings(EditorCachedLayerInfos_DEPRECATED[i])); } EditorCachedLayerInfos_DEPRECATED.Empty(); } bool bFixedUpInvalidMaterialInstances = false; for (ULandscapeComponent* Comp : LandscapeComponents) { if (Comp) { // Validate the layer combination and store it in the MaterialInstanceConstantMap if (UMaterialInstance* MaterialInstance = Comp->GetMaterialInstance(0, false)) { UMaterialInstanceConstant* CombinationMaterialInstance = Cast(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())) { 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("LandscapeName"), FText::FromString(GetPathName())); Arguments.Add(TEXT("ProxyPackage"), FText::FromString(GetOutermost()->GetName())); FMessageLog("MapCheck").Info() ->AddToken(FTextToken::Create(FText::Format(LOCTEXT("MapCheck_Message_FixedUpInvalidLandscapeMaterialInstances", "{LandscapeName} : Fixed up invalid landscape material instances. Please re-save {ProxyPackage}."), Arguments))) ->AddToken(FMapErrorToken::Create(FMapErrors::FixedUpInvalidLandscapeMaterialInstances)); } // track feature level change to flush grass cache if (GetWorld()) { FOnFeatureLevelChanged::FDelegate FeatureLevelChangedDelegate = FOnFeatureLevelChanged::FDelegate::CreateUObject(this, &ALandscapeProxy::OnFeatureLevelChanged); FeatureLevelChangedDelegateHandle = GetWorld()->AddOnFeatureLevelChangedHandler(FeatureLevelChangedDelegate); } RepairInvalidTextures(); #endif // WITH_EDITOR } FIntPoint ALandscapeProxy::GetSectionBaseOffset() const { return LandscapeSectionOffset; } #if WITH_EDITOR void ALandscapeProxy::Destroyed() { Super::Destroyed(); UWorld* World = GetWorld(); if (GIsEditor && !World->IsGameWorld()) { ULandscapeInfo::RecreateLandscapeInfo(World, false); if (SplineComponent) { SplineComponent->ModifySplines(); } TotalComponentsNeedingGrassMapRender -= NumComponentsNeedingGrassMapRender; NumComponentsNeedingGrassMapRender = 0; TotalTexturesToStreamForVisibleGrassMapRender -= NumTexturesToStreamForVisibleGrassMapRender; NumTexturesToStreamForVisibleGrassMapRender = 0; } // unregister feature level changed handler for grass if (FeatureLevelChangedDelegateHandle.IsValid()) { World->RemoveOnFeatureLevelChangedHandler(FeatureLevelChangedDelegateHandle); FeatureLevelChangedDelegateHandle.Reset(); } } void ALandscapeProxy::GetSharedProperties(ALandscapeProxy* Landscape) { if (GIsEditor && Landscape) { Modify(); LandscapeGuid = Landscape->LandscapeGuid; //@todo UE merge, landscape, this needs work RootComponent->SetRelativeScale3D(Landscape->GetRootComponent()->GetComponentToWorld().GetScale3D()); //PrePivot = Landscape->PrePivot; StaticLightingResolution = Landscape->StaticLightingResolution; CastShadow = Landscape->CastShadow; bCastDynamicShadow = Landscape->bCastDynamicShadow; bCastStaticShadow = Landscape->bCastStaticShadow; bCastContactShadow = Landscape->bCastContactShadow; bCastFarShadow = Landscape->bCastFarShadow; bCastHiddenShadow = Landscape->bCastHiddenShadow; bCastShadowAsTwoSided = Landscape->bCastShadowAsTwoSided; bAffectDistanceFieldLighting = Landscape->bAffectDistanceFieldLighting; LightingChannels = Landscape->LightingChannels; bRenderCustomDepth = Landscape->bRenderCustomDepth; CustomDepthStencilWriteMask = Landscape->CustomDepthStencilWriteMask; CustomDepthStencilValue = Landscape->CustomDepthStencilValue; LDMaxDrawDistance = Landscape->LDMaxDrawDistance; ComponentSizeQuads = Landscape->ComponentSizeQuads; NumSubsections = Landscape->NumSubsections; SubsectionSizeQuads = Landscape->SubsectionSizeQuads; MaxLODLevel = Landscape->MaxLODLevel; LODDistanceFactor_DEPRECATED = Landscape->LODDistanceFactor_DEPRECATED; LODFalloff_DEPRECATED = Landscape->LODFalloff_DEPRECATED; ComponentScreenSizeToUseSubSections = Landscape->ComponentScreenSizeToUseSubSections; LODDistributionSetting = Landscape->LODDistributionSetting; LOD0DistributionSetting = Landscape->LOD0DistributionSetting; LOD0ScreenSize = Landscape->LOD0ScreenSize; NegativeZBoundsExtension = Landscape->NegativeZBoundsExtension; PositiveZBoundsExtension = Landscape->PositiveZBoundsExtension; CollisionMipLevel = Landscape->CollisionMipLevel; bBakeMaterialPositionOffsetIntoCollision = Landscape->bBakeMaterialPositionOffsetIntoCollision; RuntimeVirtualTextures = Landscape->RuntimeVirtualTextures; VirtualTextureLodBias = Landscape->VirtualTextureLodBias; VirtualTextureNumLods = Landscape->VirtualTextureNumLods; VirtualTextureRenderPassType = Landscape->VirtualTextureRenderPassType; if (!LandscapeMaterial) { LandscapeMaterial = Landscape->LandscapeMaterial; PerLODOverrideMaterials = Landscape->PerLODOverrideMaterials; } if (!LandscapeHoleMaterial) { LandscapeHoleMaterial = Landscape->LandscapeHoleMaterial; } if (LandscapeMaterial == Landscape->LandscapeMaterial) { EditorLayerSettings = Landscape->EditorLayerSettings; } if (!DefaultPhysMaterial) { DefaultPhysMaterial = Landscape->DefaultPhysMaterial; } LightmassSettings = Landscape->LightmassSettings; } } void ALandscapeProxy::FixupSharedData(ALandscape* Landscape) { if (Landscape == nullptr) { return; } bool bUpdated = false; if (MaxLODLevel != Landscape->MaxLODLevel) { MaxLODLevel = Landscape->MaxLODLevel; bUpdated = true; } if (ComponentScreenSizeToUseSubSections != Landscape->ComponentScreenSizeToUseSubSections) { ComponentScreenSizeToUseSubSections = Landscape->ComponentScreenSizeToUseSubSections; bUpdated = true; } if (LODDistributionSetting != Landscape->LODDistributionSetting) { LODDistributionSetting = Landscape->LODDistributionSetting; bUpdated = true; } if (LOD0DistributionSetting != Landscape->LOD0DistributionSetting) { LOD0DistributionSetting = Landscape->LOD0DistributionSetting; bUpdated = true; } if (LOD0ScreenSize != Landscape->LOD0ScreenSize) { LOD0ScreenSize = Landscape->LOD0ScreenSize; bUpdated = true; } if (TargetDisplayOrder != Landscape->TargetDisplayOrder) { TargetDisplayOrder = Landscape->TargetDisplayOrder; bUpdated = true; } if (TargetDisplayOrderList != Landscape->TargetDisplayOrderList) { TargetDisplayOrderList = Landscape->TargetDisplayOrderList; bUpdated = true; } TSet LayerGuids; Algo::Transform(Landscape->LandscapeLayers, LayerGuids, [](const FLandscapeLayer& Layer) { return Layer.Guid; }); bUpdated |= RemoveObsoleteLayers(LayerGuids); for (const FLandscapeLayer& Layer : Landscape->LandscapeLayers) { bUpdated |= AddLayer(Layer.Guid); } if (bUpdated) { MarkPackageDirty(); } } 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 Fn) { // Batch component render state recreation TArray ComponentRecreateRenderStates; ComponentRecreateRenderStates.Reserve(LandscapeComponents.Num()); for (int32 ComponentIndex = 0; ComponentIndex < LandscapeComponents.Num(); ComponentIndex++) { ULandscapeComponent* Comp = LandscapeComponents[ComponentIndex]; if (Comp) { Fn(Comp); ComponentRecreateRenderStates.Emplace(Comp); } } } 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 (LandscapeActor != nullptr) { return LandscapeActor->GetLandscapeMaterial(InLODIndex); } return UMaterial::GetDefaultMaterial(MD_Surface); } UMaterialInterface* ALandscapeStreamingProxy::GetLandscapeHoleMaterial() const { if (LandscapeHoleMaterial) { return LandscapeHoleMaterial; } else if (ALandscape* Landscape = LandscapeActor.Get()) { return Landscape->GetLandscapeHoleMaterial(); } return nullptr; } void ALandscape::PreSave(const class ITargetPlatform* TargetPlatform) { PRAGMA_DISABLE_DEPRECATION_WARNINGS; Super::PreSave(TargetPlatform); PRAGMA_ENABLE_DEPRECATION_WARNINGS; } void ALandscape::PreSave(FObjectPreSaveContext ObjectSaveContext) { Super::PreSave(ObjectSaveContext); //ULandscapeInfo* Info = GetLandscapeInfo(); //if (GIsEditor && Info && !ObjectSaveContext.IsProceduralSave()) //{ // for (TSet::TIterator It(Info->Proxies); It; ++It) // { // ALandscapeProxy* Proxy = *It; // if (!ensure(Proxy->LandscapeActor == this)) // { // Proxy->LandscapeActor = this; // Proxy->GetSharedProperties(this); // } // } //} } ALandscapeProxy* ULandscapeInfo::GetLandscapeProxyForLevel(ULevel* Level) const { ALandscapeProxy* LandscapeProxy = nullptr; ForAllLandscapeProxies([&LandscapeProxy, Level](ALandscapeProxy* Proxy) { if (Proxy->GetLevel() == Level) { LandscapeProxy = Proxy; } }); return LandscapeProxy; } ALandscapeProxy* ULandscapeInfo::GetCurrentLevelLandscapeProxy(bool bRegistered) const { ALandscapeProxy* LandscapeProxy = nullptr; ForAllLandscapeProxies([&LandscapeProxy, bRegistered](ALandscapeProxy* Proxy) { if (!bRegistered || Proxy->GetRootComponent()->IsRegistered()) { UWorld* ProxyWorld = Proxy->GetWorld(); if (ProxyWorld && ProxyWorld->GetCurrentLevel() == Proxy->GetOuter()) { LandscapeProxy = Proxy; } } }); 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(); if (Landscape != nullptr && Landscape->GetRootComponent()->IsRegistered()) { return Landscape; } } // prefer current level proxy ALandscapeProxy* Proxy = GetCurrentLevelLandscapeProxy(true); if (Proxy != nullptr) { return Proxy; } // any proxy in the world for (auto It = Proxies.CreateConstIterator(); It; ++It) { Proxy = (*It); if (Proxy != nullptr && Proxy->GetRootComponent()->IsRegistered()) { return Proxy; } } return nullptr; } void ULandscapeInfo::Reset() { LandscapeActor.Reset(); Proxies.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()->IsRegistered() == false) { return; } // Make sure section offset of all proxies is multiple of ALandscapeProxy::ComponentSizeQuads for (auto It = Proxies.CreateConstIterator(); It; ++It) { ALandscapeProxy* Proxy = *It; 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 (auto It = Proxies.CreateConstIterator(); It; ++It) { ALandscapeProxy* Proxy = *It; 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() { ForAllLandscapeProxies([](ALandscapeProxy* Proxy) { for (ULandscapeComponent* Comp : Proxy->LandscapeComponents) { Comp->UpdateLayerAllowListFromPaintedLayers(); } }); } void ULandscapeInfo::RecreateLandscapeInfo(UWorld* InWorld, bool bMapCheck) { 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(); LandscapeInfo->Reset(); } } TMap> ValidLandscapesMap; // Gather all valid landscapes in the world for (ALandscapeProxy* Proxy : TActorRange(InWorld)) { if (Proxy->GetLevel() && Proxy->GetLevel()->bIsVisible && !Proxy->HasAnyFlags(RF_BeginDestroyed) && IsValid(Proxy) && !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) { 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 ULandscapeInfo* ULandscapeInfo::Find(UWorld* InWorld, const FGuid& LandscapeGuid) { ULandscapeInfo* LandscapeInfo = nullptr; check(LandscapeGuid.IsValid()); if (InWorld != nullptr) { 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(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::ForAllLandscapeProxies(TFunctionRef Fn) const { ALandscape* Landscape = LandscapeActor.Get(); if (Landscape) { Fn(Landscape); } for (ALandscapeProxy* LandscapeProxy : Proxies) { Fn(LandscapeProxy); } } void ULandscapeInfo::RegisterActor(ALandscapeProxy* Proxy, bool bMapCheck) { UWorld* OwningWorld = Proxy->GetWorld(); // do not pass here invalid actors checkSlow(Proxy); check(Proxy->GetLandscapeGuid().IsValid()); check(LandscapeGuid.IsValid()); #if WITH_EDITOR if (!OwningWorld->IsGameWorld()) { // 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; DrawScale = Proxy->GetRootComponent() != nullptr ? Proxy->GetRootComponent()->GetRelativeScale3D() : FVector(100.0f); } // check that passed actor matches all shared parameters check(LandscapeGuid == Proxy->GetLandscapeGuid()); check(ComponentSizeQuads == Proxy->ComponentSizeQuads); check(ComponentNumSubsections == Proxy->NumSubsections); check(SubsectionSizeQuads == Proxy->SubsectionSizeQuads); if (Proxy->GetRootComponent() != nullptr && !DrawScale.Equals(Proxy->GetRootComponent()->GetRelativeScale3D())) { UE_LOG(LogLandscape, Warning, TEXT("Landscape proxy (%s) scale (%s) does not match to main actor scale (%s)."), *Proxy->GetName(), *Proxy->GetRootComponent()->GetRelativeScale3D().ToCompactString(), *DrawScale.ToCompactString()); } // register if (ALandscape* Landscape = Cast(Proxy)) { checkf(!LandscapeActor || LandscapeActor == Landscape, TEXT("Multiple landscapes with the same GUID detected: %s vs %s"), *LandscapeActor->GetPathName(), *Landscape->GetPathName()); LandscapeActor = Landscape; // 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); // update proxies reference actor for (ALandscapeStreamingProxy* StreamingProxy : Proxies) { StreamingProxy->LandscapeActor = LandscapeActor; StreamingProxy->FixupSharedData(Landscape); } } else { auto LamdbdaLowerBound = [](ALandscapeProxy* A, ALandscapeProxy* 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 for generating deterministic results in the Layer system ALandscapeStreamingProxy* StreamingProxy = CastChecked(Proxy); if (!Proxies.Contains(Proxy)) { uint32 InsertIndex = Algo::LowerBound(Proxies, Proxy, LamdbdaLowerBound); Proxies.Insert(StreamingProxy, InsertIndex); } StreamingProxy->LandscapeActor = LandscapeActor; StreamingProxy->FixupSharedData(LandscapeActor.Get()); } UpdateLayerInfoMap(Proxy); UpdateAllAddCollisions(); RegisterSplineActor(Proxy); } #endif // // 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 WITH_EDITOR if (!OwningWorld->IsGameWorld()) { if (ALandscape* Landscape = Cast(Proxy)) { // Note: UnregisterActor sometimes gets triggered twice, e.g. it has been observed to happen during 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 (ALandscapeStreamingProxy* StreamingProxy : Proxies) { StreamingProxy->LandscapeActor = LandscapeActor; } } else { ALandscapeStreamingProxy* StreamingProxy = CastChecked(Proxy); Proxies.Remove(StreamingProxy); StreamingProxy->LandscapeActor = nullptr; } UnregisterSplineActor(Proxy); } #endif // 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 if (!OwningWorld->IsGameWorld()) { UpdateLayerInfoMap(); UpdateAllAddCollisions(); } #endif } #if WITH_EDITOR ALandscapeSplineActor* ULandscapeInfo::CreateSplineActor(const FVector& Location) { check(LandscapeActor.Get()); UWorld* World = LandscapeActor->GetWorld(); check(World); FActorSpawnParameters SpawnParams; SpawnParams.OverrideLevel = World->PersistentLevel; SpawnParams.bNoFail = true; SpawnParams.ObjectFlags |= RF_Transactional; ALandscapeSplineActor* SplineActor = World->SpawnActor(Location, FRotator::ZeroRotator, SpawnParams); SplineActor->GetSharedProperties(this); SplineActor->GetSplinesComponent()->ShowSplineEditorMesh(true); SplineActor->SetIsSpatiallyLoaded(AreNewLandscapeActorsSpatiallyLoaded()); RegisterSplineActor(SplineActor); return SplineActor; } void ULandscapeInfo::ForAllSplineActors(TFunctionRef)> Fn) const { for (const TScriptInterface& SplineActor : SplineActors) { Fn(SplineActor); } } TArray> ULandscapeInfo::GetSplineActors() const { TArray> CopySplineActors(SplineActors); return MoveTemp(CopySplineActors); } void ULandscapeInfo::RegisterSplineActor(TScriptInterface 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& A, const TScriptInterface& B) { return Cast(A.GetInterface())->GetPathName() < Cast(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); } if (SplineActor->GetSplinesComponent()) { RequestSplineLayerUpdate(); } } void ULandscapeInfo::UnregisterSplineActor(TScriptInterface SplineActor) { Modify(); SplineActors.Remove(SplineActor); if (SplineActor->GetSplinesComponent()) { RequestSplineLayerUpdate(); } } void ULandscapeInfo::RequestSplineLayerUpdate() { if (LandscapeActor) { LandscapeActor->RequestSplineLayerUpdate(); } } void ULandscapeInfo::ForceLayersFullUpdate() { if (LandscapeActor) { 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] This does not support being called at runtime yet because ULandscapeInfo::LandscapeActor // is not valid at runtime or in non-editor worlds (e.g. PIE), for no good reason, and also XYtoComponentMap is // described as being valid only in editor. bool ULandscapeInfo::GetOverlappedComponents(const FTransform& InAreaWorldTransform, const FBox2D& InAreaExtents, TMap& OutOverlappedComponents, FIntRect& OutComponentIndicesBoundingRect) { if (!LandscapeActor.IsValid()) { return false; } // 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::FloorToInt(LocalExtents.Min.X / ComponentSizeQuads), FMath::FloorToInt(LocalExtents.Min.Y / ComponentSizeQuads)); // The max here is meant to be an exclusive bound, hence the +1 BoundingIndices.Max = FIntPoint(FMath::FloorToInt(LocalExtents.Max.X / ComponentSizeQuads), FMath::FloorToInt(LocalExtents.Max.Y / ComponentSizeQuads)) + FIntPoint(1); // There could be missing components, so the effective area is actually a subset of this area : FIntRect EffectiveBoundingIndices; // 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; auto 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)) ->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; auto 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 auto& 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 LoadedProxies; for (auto It = XYtoComponentMap.CreateConstIterator(); It; ++It) { if (!It.Value()) { continue; } if (ALandscapeProxy* Proxy = Cast(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); if (UWorldPartition* WorldPartition = Landscape->GetWorld()->GetWorldPartition()) { FWorldPartitionHelpers::ForEachActorDesc(WorldPartition, [this, &Bounds, Landscape](const FWorldPartitionActorDesc* ActorDesc) { FLandscapeActorDesc* LandscapeActorDesc = (FLandscapeActorDesc*)ActorDesc; if (LandscapeActorDesc->GridGuid == LandscapeGuid) { ALandscapeProxy* LandscapeProxy = Cast(ActorDesc->GetActor()); // Skip owning landscape actor if (LandscapeProxy != Landscape) { if (LandscapeProxy) { // Prioritize loaded bounds, as the bounds in the actor desc might not be up-to-date LandscapeInfoBoundsHelper::AccumulateBounds(LandscapeProxy, Bounds); } else { Bounds += ActorDesc->GetBounds(); } } } return true; }); } return Bounds; } #endif void ULandscapeComponent::PostInitProperties() { Super::PostInitProperties(); // Create a new guid in case this is a newly created component // If not, this guid will be overwritten when serialized FPlatformMisc::CreateGuid(StateId); // Initialize MapBuildDataId to something unique, in case this is a new ULandscapeComponent MapBuildDataId = FGuid::NewGuid(); } void ULandscapeComponent::PostDuplicate(bool bDuplicateForPIE) { if (!bDuplicateForPIE) { // Reset the StateId on duplication since it needs to be unique for each capture. // PostDuplicate covers direct calls to StaticDuplicateObject, but not actor duplication (see PostEditImport) FPlatformMisc::CreateGuid(StateId); } } ULandscapeWeightmapUsage::ULandscapeWeightmapUsage(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { ClearUsage(); } // Generate a new guid to force a recache of all landscape derived data #define LANDSCAPE_FULL_DERIVEDDATA_VER TEXT("3000901CF3B24F028854C2DB986E5B3B") FString FLandscapeComponentDerivedData::GetDDCKeyString(const FGuid& StateId) { return FDerivedDataCacheInterface::BuildCacheKey(TEXT("LS_FULL"), LANDSCAPE_FULL_DERIVEDDATA_VER, *StateId.ToString()); } void FLandscapeComponentDerivedData::InitializeFromUncompressedData(const TArray& UncompressedData, const TArray>& StreamingLODs) { int32 UncompressedSize = UncompressedData.Num() * UncompressedData.GetTypeSize(); TArray TempCompressedMemory; // Compressed can be slightly larger than uncompressed TempCompressedMemory.Empty(UncompressedSize * 4 / 3); TempCompressedMemory.AddUninitialized(UncompressedSize * 4 / 3); int32 CompressedSize = TempCompressedMemory.Num() * TempCompressedMemory.GetTypeSize(); verify(FCompression::CompressMemory( NAME_Zlib, TempCompressedMemory.GetData(), CompressedSize, UncompressedData.GetData(), UncompressedSize, COMPRESS_BiasMemory)); // Note: change LANDSCAPE_FULL_DERIVEDDATA_VER when modifying the serialization layout FMemoryWriter FinalArchive(CompressedLandscapeData, true); FinalArchive << UncompressedSize; FinalArchive << CompressedSize; FinalArchive.Serialize(TempCompressedMemory.GetData(), CompressedSize); const int32 NumStreamingLODs = StreamingLODs.Num(); StreamingLODDataArray.Empty(NumStreamingLODs); for (int32 Idx = 0; Idx < NumStreamingLODs; ++Idx) { const TArray& SrcData = StreamingLODs[Idx]; const int32 NumSrcBytes = SrcData.Num(); FByteBulkData& LODData = StreamingLODDataArray[StreamingLODDataArray.AddDefaulted()]; if (NumSrcBytes > 0) { LODData.ResetBulkDataFlags(BULKDATA_Force_NOT_InlinePayload); LODData.Lock(LOCK_READ_WRITE); void* Dest = LODData.Realloc(NumSrcBytes); FMemory::Memcpy(Dest, SrcData.GetData(), NumSrcBytes); LODData.Unlock(); } } } void FLandscapeComponentDerivedData::Serialize(FArchive& Ar, UObject* Owner) { Ar << CompressedLandscapeData; int32 NumStreamingLODs = StreamingLODDataArray.Num(); Ar << NumStreamingLODs; if (Ar.IsLoading()) { StreamingLODDataArray.Empty(NumStreamingLODs); StreamingLODDataArray.AddDefaulted(NumStreamingLODs); } PRAGMA_DISABLE_DEPRECATION_WARNINGS CachedLODDataPackagePath.Empty(); CachedLODDataPackageSegment = EPackageSegment::Header; PRAGMA_ENABLE_DEPRECATION_WARNINGS for (int32 Idx = 0; Idx < NumStreamingLODs; ++Idx) { FByteBulkData& LODData = StreamingLODDataArray[Idx]; LODData.Serialize(Ar, Owner, Idx); PRAGMA_DISABLE_DEPRECATION_WARNINGS if (CachedLODDataPackagePath.IsEmpty() && !!(LODData.GetBulkDataFlags() & BULKDATA_Force_NOT_InlinePayload) && LODData.IsUsingIODispatcher() == false) { CachedLODDataPackagePath = LODData.GetPackagePath(); CachedLODDataPackageSegment = LODData.GetPackageSegment(); } PRAGMA_ENABLE_DEPRECATION_WARNINGS } } bool FLandscapeComponentDerivedData::LoadFromDDC(const FGuid& StateId, UObject* Component) { TArray Bytes; if (GetDerivedDataCacheRef().GetSynchronous(*GetDDCKeyString(StateId), Bytes, Component->GetPathName())) { FMemoryReader Ar(Bytes, true); Serialize(Ar, Component); return true; } return false; } void FLandscapeComponentDerivedData::SaveToDDC(const FGuid& StateId, UObject* Component) { check(CompressedLandscapeData.Num() > 0); TArray Bytes; FMemoryWriter Ar(Bytes, true); Serialize(Ar, Component); GetDerivedDataCacheRef().Put(*GetDDCKeyString(StateId), Bytes, Component->GetPathName()); } PRAGMA_DISABLE_DEPRECATION_WARNINGS ALandscapeProxy::~ALandscapeProxy() { for (int32 Index = 0; Index < AsyncFoliageTasks.Num(); Index++) { FAsyncTask* Task = AsyncFoliageTasks[Index]; Task->EnsureCompletion(true); FAsyncGrassTask& Inner = Task->GetTask(); delete Task; } AsyncFoliageTasks.Empty(); #if WITH_EDITOR TotalComponentsNeedingGrassMapRender -= NumComponentsNeedingGrassMapRender; NumComponentsNeedingGrassMapRender = 0; TotalTexturesToStreamForVisibleGrassMapRender -= NumTexturesToStreamForVisibleGrassMapRender; NumTexturesToStreamForVisibleGrassMapRender = 0; #endif #if WITH_EDITORONLY_DATA LandscapeProxies.Remove(this); #endif } PRAGMA_ENABLE_DEPRECATION_WARNINGS // // ALandscapeMeshProxyActor // ALandscapeMeshProxyActor::ALandscapeMeshProxyActor(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SetCanBeDamaged(false); LandscapeMeshProxyComponent = CreateDefaultSubobject(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::InitializeForLandscape(ALandscapeProxy* Landscape, int8 InProxyLOD) { LandscapeGuid = Landscape->GetLandscapeGuid(); for (ULandscapeComponent* Component : Landscape->LandscapeComponents) { if (Component) { ProxyComponentBases.Add(Component->GetSectionBase() / Component->ComponentSizeQuads); } } if (InProxyLOD != INDEX_NONE) { ProxyLOD = FMath::Clamp(InProxyLOD, 0, FMath::CeilLogTwo(Landscape->SubsectionSizeQuads + 1) - 1); } } #if WITH_EDITOR 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 = HeightmapTexture->Source.GetId(); Ar << HeightmapGuid; for (auto WeightmapTexture : WeightmapTextures) { FGuid WeightmapGuid = WeightmapTexture->Source.GetId(); Ar << WeightmapGuid; } bool bMeshHoles = GetLandscapeProxy()->bMeshHoles; uint8 MeshHolesMaxLod = GetLandscapeProxy()->MeshHolesMaxLod; Ar << bMeshHoles << MeshHolesMaxLod; // 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); } FLandscapeGIBakedTextureBuilder::FLandscapeGIBakedTextureBuilder(UWorld* InWorld) :World(InWorld) ,OutdatedGIBakedTextureComponentsCount(0) ,GIBakedTexturesLastCheckTime(0) { } void FLandscapeGIBakedTextureBuilder::Build() { if (World) { for (TActorIterator ProxyIt(World); ProxyIt; ++ProxyIt) { ProxyIt->BuildGIBakedTextures(); } //Force update the outdated count when using the build menu option. OutdatedGIBakedTextureComponentsCount = 0; GIBakedTexturesLastCheckTime = FPlatformTime::Seconds(); } } int32 FLandscapeGIBakedTextureBuilder::GetOutdatedGIBakedTextureComponentsCount(bool bInForceUpdate) const { if (World) { bool bUpdate = bInForceUpdate; double GIBakedTexturesTimeNow = FPlatformTime::Seconds(); if (!bUpdate) { // Recheck every 20 secs if ((GIBakedTexturesTimeNow - GIBakedTexturesLastCheckTime) > 20) { bUpdate = true; } } if (bUpdate) { GIBakedTexturesLastCheckTime = GIBakedTexturesTimeNow; OutdatedGIBakedTextureComponentsCount = 0; for (TActorIterator ProxyIt(World); ProxyIt; ++ProxyIt) { OutdatedGIBakedTextureComponentsCount += ProxyIt->GetOutdatedGIBakedTextureComponentsCount(); } } } return OutdatedGIBakedTextureComponentsCount; } void ALandscapeProxy::BuildGIBakedTextures(struct FScopedSlowTask* InSlowTask /* = nullptr */) { if (!HasAnyFlags(RF_ClassDefaultObject)) { const bool bShouldMarkDirty = true; UpdateGIBakedTextureData(bShouldMarkDirty); } } int32 ALandscapeProxy::GetOutdatedGIBakedTextureComponentsCount() const { int32 OutdatedGITextureComponentsCount = 0; UpdateGIBakedTextureStatus(nullptr, nullptr, &OutdatedGITextureComponentsCount); return OutdatedGITextureComponentsCount; } void ALandscapeProxy::UpdateGIBakedTextureStatus(bool* bOutGenerateLandscapeGIData, TMap* OutComponentsNeedBakingByHeightmap, int32* OutdatedComponentsCount) const { int32 OutdatedComponents = 0; int32 ComponentsNeedToBeCleared = 0; int32 ComponentsNeedToBeBaked = 0; //@todo - remove Landscape GI Data if (true) { if (bOutGenerateLandscapeGIData) { *bOutGenerateLandscapeGIData = false; } for (ULandscapeComponent* Component : LandscapeComponents) { if (Component != nullptr && Component->GIBakedBaseColorTexture != nullptr) { ComponentsNeedToBeCleared++; } } OutdatedComponents += ComponentsNeedToBeCleared; } else { // Stores the components and their state hash data for a single atlas struct FGIBakeTextureStateBuilder { // pointer as FMemoryWriter caches the address of the FBufferArchive, and this struct could be relocated on a realloc. TUniquePtr ComponentStateAr; TArray Components; FGIBakeTextureStateBuilder() { ComponentStateAr = MakeUnique(); } }; TMap ComponentsByHeightmap; for (ULandscapeComponent* Component : LandscapeComponents) { if (Component == nullptr) { continue; } FGIBakeTextureStateBuilder& Info = ComponentsByHeightmap.FindOrAdd(Component->GetHeightmap()); Info.Components.Add(Component); Component->SerializeStateHashes(*Info.ComponentStateAr); } for (auto It = ComponentsByHeightmap.CreateIterator(); It; ++It) { FGIBakeTextureStateBuilder& Info =It.Value(); // Calculate a combined Guid-like ID we can use for this component uint32 Hash[5]; FSHA1::HashBuffer(Info.ComponentStateAr->GetData(), Info.ComponentStateAr->Num(), (uint8*)Hash); FGuid CombinedStateId = FGuid(Hash[0] ^ Hash[4], Hash[1], Hash[2], Hash[3]); if (Info.Components[0]->BakedTextureMaterialGuid != CombinedStateId) { ComponentsNeedToBeBaked += Info.Components.Num(); if (OutComponentsNeedBakingByHeightmap) { FGIBakedTextureState& GIBakedTextureState = OutComponentsNeedBakingByHeightmap->FindOrAdd(It.Key()); GIBakedTextureState.Components = MoveTemp(Info.Components); GIBakedTextureState.CombinedStateId = CombinedStateId; } } } OutdatedComponents += ComponentsNeedToBeBaked; } if (OutdatedComponentsCount) { if (OutdatedComponents == 0) { for (auto Component : LandscapeComponents) { const bool bIsDirty = Component->GetPackage()->IsDirty(); if (Component->LastBakedTextureMaterialGuid != Component->BakedTextureMaterialGuid && !bIsDirty) { OutdatedComponents++; } } } *OutdatedComponentsCount = OutdatedComponents; } } void ALandscapeProxy::UpdateGIBakedTextureData(bool bInShouldMarkDirty) { const bool bBakeAllGITextures = true; UpdateGIBakedTextures(bBakeAllGITextures); if (bInShouldMarkDirty && GetOutdatedGIBakedTextureComponentsCount() > 0) { MarkPackageDirty(); } } void ALandscapeProxy::UpdateGIBakedTextures(bool bBakeAllGITextures) { // See if we can render UWorld* World = GetWorld(); if (!GIsEditor || GUsingNullRHI || !World || World->IsGameWorld() || World->FeatureLevel < ERHIFeatureLevel::SM5) { return; } if (!bBakeAllGITextures && UpdateBakedTexturesCountdown-- > 0) { return; } bool bGenerateLandscapeGIData = true; TMap ComponentsToBeBakedByHeightmap; UpdateGIBakedTextureStatus(&bGenerateLandscapeGIData, &ComponentsToBeBakedByHeightmap); if (!bGenerateLandscapeGIData) { // Clear out any existing GI textures for (ULandscapeComponent* Component : LandscapeComponents) { if (Component != nullptr && Component->GIBakedBaseColorTexture != nullptr) { Component->BakedTextureMaterialGuid.Invalidate(); Component->GIBakedBaseColorTexture = nullptr; Component->MarkRenderStateDirty(); } } // Don't check if we need to update anything for another 60 frames UpdateBakedTexturesCountdown = 60; return; } TotalComponentsNeedingTextureBaking -= NumComponentsNeedingTextureBaking; NumComponentsNeedingTextureBaking = 0; int32 NumGenerated = 0; for (auto It = ComponentsToBeBakedByHeightmap.CreateConstIterator(); It; ++It) { const FGIBakedTextureState& Info = It.Value(); bool bCanBake = true; for (ULandscapeComponent* Component : Info.Components) { // not registered; ignore this component if (!Component->SceneProxy) { continue; } // Check we can render the material UMaterialInstance* MaterialInstance = Component->GetMaterialInstance(0, false); if (!MaterialInstance) { // Cannot render this component yet as it doesn't have a material; abandon the atlas for this heightmap bCanBake = false; break; } FMaterialResource* MaterialResource = MaterialInstance->GetMaterialResource(World->FeatureLevel); if (!MaterialResource || !MaterialResource->HasValidGameThreadShaderMap()) { // Cannot render this component yet as its shaders aren't compiled; abandon the atlas for this heightmap bCanBake = false; break; } } if (bCanBake) { // We throttle, baking only one atlas per frame if bBakeAllGITextures is false. if (!bBakeAllGITextures && NumGenerated > 0) { NumComponentsNeedingTextureBaking += Info.Components.Num(); } else { UTexture2D* HeightmapTexture = It.Key(); // 1/8 the res of the heightmap FIntPoint AtlasSize(HeightmapTexture->Source.GetSizeX() >> 3, HeightmapTexture->Source.GetSizeY() >> 3); TArray AtlasSamples; AtlasSamples.AddZeroed(AtlasSize.X * AtlasSize.Y); for (ULandscapeComponent* Component : Info.Components) { // not registered; ignore this component if (!Component->SceneProxy) { continue; } int32 ComponentSamples = (SubsectionSizeQuads + 1) * NumSubsections; check(FMath::IsPowerOfTwo(ComponentSamples)); int32 BakeSize = ComponentSamples >> 3; TArray Samples; if (FMaterialUtilities::ExportBaseColor(Component, BakeSize, Samples)) { int32 AtlasOffsetX = FMath::RoundToInt(Component->HeightmapScaleBias.Z * (float)HeightmapTexture->Source.GetSizeX()) >> 3; int32 AtlasOffsetY = FMath::RoundToInt(Component->HeightmapScaleBias.W * (float)HeightmapTexture->Source.GetSizeY()) >> 3; for (int32 y = 0; y < BakeSize; y++) { FMemory::Memcpy(&AtlasSamples[(y + AtlasOffsetY) * AtlasSize.X + AtlasOffsetX], &Samples[y * BakeSize], sizeof(FColor) * BakeSize); } NumGenerated++; } } UTexture2D* AtlasTexture = FMaterialUtilities::CreateTexture(GetOutermost(), HeightmapTexture->GetName() + TEXT("_BaseColor"), AtlasSize, AtlasSamples, TC_Default, TEXTUREGROUP_World, RF_NoFlags, true, Info.CombinedStateId); for (ULandscapeComponent* Component : Info.Components) { Component->BakedTextureMaterialGuid = Info.CombinedStateId; Component->GIBakedBaseColorTexture = AtlasTexture; Component->MarkRenderStateDirty(); } } } } TotalComponentsNeedingTextureBaking += NumComponentsNeedingTextureBaking; if (NumGenerated == 0) { // Don't check if we need to update anything for another 60 frames UpdateBakedTexturesCountdown = 60; } } FLandscapePhysicalMaterialBuilder::FLandscapePhysicalMaterialBuilder(UWorld* InWorld) :World(InWorld) ,OudatedPhysicalMaterialComponentsCount(0) { } void FLandscapePhysicalMaterialBuilder::Build() { if (World) { for (TActorIterator ProxyIt(World); ProxyIt; ++ProxyIt) { ProxyIt->BuildPhysicalMaterial(); } } } int32 FLandscapePhysicalMaterialBuilder::GetOudatedPhysicalMaterialComponentsCount() { if (World) { OudatedPhysicalMaterialComponentsCount = 0; for (TActorIterator ProxyIt(World); ProxyIt; ++ProxyIt) { OudatedPhysicalMaterialComponentsCount += ProxyIt->GetOudatedPhysicalMaterialComponentsCount(); } } return OudatedPhysicalMaterialComponentsCount; } int32 ALandscapeProxy::GetOudatedPhysicalMaterialComponentsCount() const { int32 OudatedPhysicalMaterialComponentsCount = 0; UpdatePhysicalMaterialTasksStatus(nullptr, &OudatedPhysicalMaterialComponentsCount); return OudatedPhysicalMaterialComponentsCount; } void ALandscapeProxy::BuildPhysicalMaterial(struct FScopedSlowTask* InSlowTask) { if (!HasAnyFlags(RF_ClassDefaultObject)) { const bool bShouldMarkDirty = true; UpdatePhysicalMaterialTasks(bShouldMarkDirty); } } void ALandscapeProxy::UpdatePhysicalMaterialTasksStatus(TSet* OutdatedComponents, int32* OutdatedComponentsCount) const { int32 OutdatedCount = 0; for (auto Component : LandscapeComponents) { uint32 Hash = Component->CalculatePhysicalMaterialTaskHash(); if (Component->PhysicalMaterialHash != Hash || Component->PhysicalMaterialTask.IsValid()) { OutdatedCount++; if (OutdatedComponents) { OutdatedComponents->Add(Component); } } } if (OutdatedCount == 0) { for (auto Component : LandscapeComponents) { const bool bIsDirty = Component->GetPackage()->IsDirty(); if (Component->LastSavedPhysicalMaterialHash != Component->PhysicalMaterialHash && !bIsDirty) { OutdatedCount++; } } } if (OutdatedComponentsCount) { *OutdatedComponentsCount = OutdatedCount; } } void ALandscapeProxy::UpdatePhysicalMaterialTasks(bool bInShouldMarkDirty) { TSet OutdatedComponents; int32 PendingComponentsToBeSaved = 0; UpdatePhysicalMaterialTasksStatus(&OutdatedComponents, &PendingComponentsToBeSaved); for (ULandscapeComponent* Component : OutdatedComponents) { Component->UpdatePhysicalMaterialTasks(); } if (bInShouldMarkDirty && PendingComponentsToBeSaved >0) { MarkPackageDirty(); } } #endif void ALandscapeProxy::InvalidateGeneratedComponentData(bool bInvalidateLightingCache) { InvalidateGeneratedComponentData(LandscapeComponents, bInvalidateLightingCache); } void ALandscapeProxy::InvalidateGeneratedComponentData(const TArray& Components, bool bInvalidateLightingCache) { TMap> ByProxy; for (auto Iter = Components.CreateConstIterator(); Iter; ++Iter) { ULandscapeComponent* Component = *Iter; if (bInvalidateLightingCache) { Component->InvalidateLightingCache(); } Component->BakedTextureMaterialGuid.Invalidate(); ByProxy.FindOrAdd(Component->GetLandscapeProxy()).Add(Component); } for (auto Iter = ByProxy.CreateConstIterator(); Iter; ++Iter) { Iter.Key()->FlushGrassComponents(&Iter.Value()); #if WITH_EDITOR FLandscapeProxyComponentDataChangedParams ChangeParams(Iter.Value()); Iter.Key()->OnComponentDataChanged.Broadcast(Iter.Key(), ChangeParams); #endif } } void ALandscapeProxy::InvalidateGeneratedComponentData(const TSet& Components, bool bInvalidateLightingCache) { InvalidateGeneratedComponentData(Components.Array(), bInvalidateLightingCache); } ULandscapeLODStreamingProxy::ULandscapeLODStreamingProxy(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { LandscapeComponent = Cast(GetOuter()); } int32 ULandscapeLODStreamingProxy::CalcCumulativeLODSize(int32 NumLODs) const { check(LandscapeComponent); const int32 NumStreamingLODs = LandscapeComponent->PlatformData.StreamingLODDataArray.Num(); const int32 LastLODIdx = NumStreamingLODs - NumLODs + 1; int64 Result = 0; for (int32 Idx = NumStreamingLODs - 1; Idx >= LastLODIdx; --Idx) { Result += LandscapeComponent->PlatformData.StreamingLODDataArray[Idx].GetBulkDataSize(); } return (int32)Result; } bool ULandscapeLODStreamingProxy::GetMipDataFilename(const int32 MipIndex, FString& OutBulkDataFilename) const { FPackagePath PackagePath; EPackageSegment PackageSegment; PRAGMA_DISABLE_DEPRECATION_WARNINGS const bool bResult = GetMipDataPackagePath(MipIndex, PackagePath, PackageSegment); PRAGMA_ENABLE_DEPRECATION_WARNINGS if (bResult) { OutBulkDataFilename = PackagePath.GetLocalFullPath(PackageSegment); return true; } return false; } bool ULandscapeLODStreamingProxy::GetMipDataPackagePath(const int32 MipIndex, FPackagePath& OutPackagePath, EPackageSegment& OutPackageSegment) const { check(LandscapeComponent); const int32 NumStreamingLODs = LandscapeComponent->PlatformData.StreamingLODDataArray.Num(); if (MipIndex >= 0 && MipIndex < NumStreamingLODs) { PRAGMA_DISABLE_DEPRECATION_WARNINGS OutPackagePath = LandscapeComponent->PlatformData.CachedLODDataPackagePath; OutPackageSegment = LandscapeComponent->PlatformData.CachedLODDataPackageSegment; PRAGMA_ENABLE_DEPRECATION_WARNINGS return true; } return false; } FIoFilenameHash ULandscapeLODStreamingProxy::GetMipIoFilenameHash(const int32 MipIndex) const { if (LandscapeComponent && LandscapeComponent->PlatformData.StreamingLODDataArray.IsValidIndex(MipIndex)) { return LandscapeComponent->PlatformData.StreamingLODDataArray[MipIndex].GetIoFilenameHash(); } else { return INVALID_IO_FILENAME_HASH; } } bool ULandscapeLODStreamingProxy::StreamOut(int32 NewMipCount) { check(IsInGameThread()); if (!HasPendingInitOrStreaming() && CachedSRRState.StreamOut(NewMipCount)) { PendingUpdate = new FLandscapeMeshMobileStreamOut(this); return !PendingUpdate->IsCancelled(); } return false; } bool ULandscapeLODStreamingProxy::StreamIn(int32 NewMipCount, bool bHighPrio) { check(IsInGameThread()); if (!HasPendingInitOrStreaming() && CachedSRRState.StreamIn(NewMipCount)) { #if WITH_EDITOR if (FPlatformProperties::HasEditorOnlyData()) { PendingUpdate = new FLandscapeMeshMobileStreamIn_GPUDataOnly(this); } else #endif { PendingUpdate = new FLandscapeMeshMobileStreamIn_IO_AsyncReallocate(this, bHighPrio); } return !PendingUpdate->IsCancelled(); } return false; } TArray ULandscapeLODStreamingProxy::GetLODScreenSizeArray() const { check(LandscapeComponent); return ::GetLODScreenSizeArray(LandscapeComponent->GetLandscapeProxy(), CachedSRRState.MaxNumLODs); } TSharedPtr ULandscapeLODStreamingProxy::GetRenderData() const { check(LandscapeComponent); return LandscapeComponent->PlatformData.CachedRenderData; } FByteBulkData& ULandscapeLODStreamingProxy::GetStreamingLODBulkData(int32 LODIdx) const { check(LandscapeComponent); return LandscapeComponent->PlatformData.StreamingLODDataArray[LODIdx]; } void ULandscapeLODStreamingProxy::CancelAllPendingStreamingActions() { FlushRenderingCommands(); for (TObjectIterator It; It; ++It) { ULandscapeLODStreamingProxy* StaticMesh = *It; StaticMesh->CancelPendingStreamingRequest(); } FlushRenderingCommands(); } bool ULandscapeLODStreamingProxy::HasPendingRenderResourceInitialization() const { return LandscapeComponent && LandscapeComponent->PlatformData.CachedRenderData && !LandscapeComponent->PlatformData.CachedRenderData->bReadyForStreaming; } void ULandscapeLODStreamingProxy::ClearStreamingResourceState() { CachedSRRState.Clear(); } void ULandscapeLODStreamingProxy::InitResourceStateForMobileStreaming() { check(LandscapeComponent); const int32 NumLODs = LandscapeComponent->PlatformData.StreamingLODDataArray.Num() + 1; const bool bHasValidRenderData = LandscapeComponent->PlatformData.CachedRenderData.IsValid(); CachedSRRState.Clear(); CachedSRRState.bSupportsStreaming = !NeverStream && NumLODs > 1 && bHasValidRenderData; CachedSRRState.NumNonStreamingLODs = 1; CachedSRRState.NumNonOptionalLODs = NumLODs; CachedSRRState.MaxNumLODs = NumLODs; CachedSRRState.NumResidentLODs = bHasValidRenderData ? (NumLODs - LandscapeComponent->PlatformData.CachedRenderData->CurrentFirstLODIdx) : NumLODs; CachedSRRState.NumRequestedLODs = CachedSRRState.NumResidentLODs; // Set bHasPendingInitHint so that HasPendingRenderResourceInitialization() gets called. CachedSRRState.bHasPendingInitHint = true; } #if WITH_EDITOR FLandscapeProxyComponentDataChangedParams::FLandscapeProxyComponentDataChangedParams(const TSet& InComponents) : Components(InComponents.Array()) { } void FLandscapeProxyComponentDataChangedParams::ForEachComponent(TFunctionRef Func) const { for (ULandscapeComponent* Component : Components) { Func(Component); } } #endif #undef LOCTEXT_NAMESPACE