Files
UnrealEngineUWP/Engine/Source/Runtime/Landscape/Private/LandscapeEdit.cpp
luc eygasier 31bba2a82d Fixes include warning
#preflight none
#rb trivial
#jira UE-155142

#ROBOMERGE-AUTHOR: luc.eygasier
#ROBOMERGE-SOURCE: CL 20395885 via CL 20395901 via CL 20395913 via CL 20395933
#ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v949-20362246)

[CL 20398763 by luc eygasier in ue5-main branch]
2022-05-27 16:51:38 -04:00

7691 lines
283 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
LandscapeEdit.cpp: Landscape editing
=============================================================================*/
#include "LandscapeEdit.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "Misc/MessageDialog.h"
#include "Misc/Paths.h"
#include "Misc/FeedbackContext.h"
#include "Modules/ModuleManager.h"
#include "UObject/UObjectIterator.h"
#include "UObject/Package.h"
#include "Misc/PackageName.h"
#include "Landscape.h"
#include "LandscapeEditReadback.h"
#include "LandscapeStreamingProxy.h"
#include "LandscapeInfo.h"
#include "LandscapeComponent.h"
#include "LandscapeLayerInfoObject.h"
#include "Materials/Material.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Materials/MaterialExpressionLandscapeVisibilityMask.h"
#include "Materials/MaterialExpressionLandscapeLayerWeight.h"
#include "Materials/MaterialExpressionLandscapeLayerSample.h"
#include "Materials/MaterialExpressionLandscapeLayerBlend.h"
#include "Materials/MaterialExpressionLandscapeLayerSwitch.h"
#include "LandscapeDataAccess.h"
#include "LandscapeRender.h"
#include "LandscapeRenderMobile.h"
#include "Materials/MaterialInstanceConstant.h"
#include "LandscapeMaterialInstanceConstant.h"
#include "LandscapeHeightfieldCollisionComponent.h"
#include "LandscapeMeshCollisionComponent.h"
#include "LandscapeGizmoActiveActor.h"
#include "InstancedFoliageActor.h"
#include "LevelUtils.h"
#include "Logging/TokenizedMessage.h"
#include "Logging/MessageLog.h"
#include "Misc/MapErrors.h"
#include "LandscapeSplinesComponent.h"
#include "Serialization/MemoryWriter.h"
#include "MaterialCachedData.h"
#if WITH_EDITOR
#include "Engine/World.h"
#include "LandscapeSubsystem.h"
#include "StaticMeshAttributes.h"
#include "MeshUtilitiesCommon.h"
#include "Misc/ScopedSlowTask.h"
#include "EngineModule.h"
#include "EngineUtils.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "LandscapeEditorModule.h"
#include "LandscapeFileFormatInterface.h"
#include "ComponentRecreateRenderStateContext.h"
#include "ComponentReregisterContext.h"
#include "Interfaces/ITargetPlatform.h"
#include "Engine/TextureRenderTarget2D.h"
#include "ScopedTransaction.h"
#include "Editor.h"
#include "PhysicalMaterials/PhysicalMaterial.h"
#include "WorldPartition/WorldPartition.h"
#include "WorldPartition/WorldPartitionHandle.h"
#include "WorldPartition/WorldPartitionHelpers.h"
#include "WorldPartition/WorldPartitionActorDesc.h"
#include "WorldPartition/Landscape/LandscapeActorDesc.h"
#include "WorldPartition/Landscape/LandscapeSplineActorDesc.h"
#include "ActorPartition/ActorPartitionSubsystem.h"
#include "LandscapeUtils.h"
#include "LandscapeSplineActor.h"
#endif
#include "Algo/Count.h"
#include "Serialization/MemoryWriter.h"
#include "Engine/Canvas.h"
DEFINE_LOG_CATEGORY(LogLandscape);
DEFINE_LOG_CATEGORY(LogLandscapeBP);
#define LOCTEXT_NAMESPACE "Landscape"
int32 GMobileCompressLandscapeWeightMaps = 0;
FAutoConsoleVariableRef CVarMobileCompressLanscapeWeightMaps(
TEXT("r.Mobile.CompressLandscapeWeightMaps"),
GMobileCompressLandscapeWeightMaps,
TEXT("Whether to compress the terrain weight maps for mobile."),
ECVF_ReadOnly
);
#if WITH_EDITOR
// Used to temporarily disable material instance updates (typically used for cases where multiple updates are called on sample component)
// Instead, one call per component is done at the end
LANDSCAPE_API bool GDisableUpdateLandscapeMaterialInstances = false;
// Channel remapping
extern const size_t ChannelOffsets[4];
ULandscapeLayerInfoObject* ALandscapeProxy::VisibilityLayer = nullptr;
void ULandscapeComponent::Init(int32 InBaseX, int32 InBaseY, int32 InComponentSizeQuads, int32 InNumSubsections, int32 InSubsectionSizeQuads)
{
ALandscapeProxy* LandscapeProxy = GetLandscapeProxy();
check(LandscapeProxy && !LandscapeProxy->LandscapeComponents.Contains(this));
LandscapeProxy->LandscapeComponents.Add(this);
SetSectionBase(FIntPoint(InBaseX, InBaseY));
SetRelativeLocation(FVector(GetSectionBase() - GetLandscapeProxy()->LandscapeSectionOffset));
ComponentSizeQuads = InComponentSizeQuads;
NumSubsections = InNumSubsections;
SubsectionSizeQuads = InSubsectionSizeQuads;
check(NumSubsections * SubsectionSizeQuads == ComponentSizeQuads);
AttachToComponent(LandscapeProxy->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
const int32 ComponentVerts = (SubsectionSizeQuads + 1) * NumSubsections;
WeightmapScaleBias = FVector4(1.0f / (float)ComponentVerts, 1.0f / (float)ComponentVerts, 0.5f / (float)ComponentVerts, 0.5f / (float)ComponentVerts);
WeightmapSubsectionOffset = (float)(SubsectionSizeQuads + 1) / (float)ComponentVerts;
UpdatedSharedPropertiesFromActor();
}
void ULandscapeComponent::UpdateCachedBounds(bool bInApproximateBounds)
{
// Update local-space bounding box
CachedLocalBox.Init();
if (bInApproximateBounds && GetLandscapeProxy()->HasLayersContent())
{
FVector MinBox(0, 0, LandscapeDataAccess::GetLocalHeight(0));
FVector MaxBox(ComponentSizeQuads + 1, ComponentSizeQuads + 1, LandscapeDataAccess::GetLocalHeight(UINT16_MAX));
CachedLocalBox = FBox(MinBox, MaxBox);
}
else
{
const int32 MipLevel = 0;
const bool bWorkOnEditingLayer = false; // We never want to compute bounds based on anything else that final landscape layer's height data
FLandscapeComponentDataInterface CDI(this, MipLevel, bWorkOnEditingLayer);
for (int32 y = 0; y < ComponentSizeQuads + 1; y++)
{
for (int32 x = 0; x < ComponentSizeQuads + 1; x++)
{
CachedLocalBox += CDI.GetLocalVertex(x, y);
}
}
}
if (CachedLocalBox.GetExtent().Z == 0)
{
// expand bounds to avoid flickering issues with zero-size bounds
CachedLocalBox = CachedLocalBox.ExpandBy(FVector(0, 0, 1));
}
// Update collision component bounds
ULandscapeHeightfieldCollisionComponent* HFCollisionComponent = CollisionComponent.Get();
if (HFCollisionComponent)
{
// In Landscape Layers the Collision Component is slave and doesn't need to be transacted
if (!GetLandscapeProxy()->HasLayersContent())
{
HFCollisionComponent->Modify();
}
HFCollisionComponent->CachedLocalBox = CachedLocalBox;
HFCollisionComponent->UpdateComponentToWorld();
}
}
void ULandscapeComponent::UpdateNavigationRelevance()
{
ALandscapeProxy* Proxy = GetLandscapeProxy();
if (CollisionComponent && Proxy)
{
CollisionComponent->SetCanEverAffectNavigation(Proxy->bUsedForNavigation);
}
}
void ULandscapeComponent::UpdateRejectNavmeshUnderneath()
{
ALandscapeProxy* Proxy = GetLandscapeProxy();
if (CollisionComponent && Proxy)
{
CollisionComponent->bFillCollisionUnderneathForNavmesh = Proxy->bFillCollisionUnderLandscapeForNavmesh;
}
}
ULandscapeMaterialInstanceConstant* ALandscapeProxy::GetLayerThumbnailMIC(UMaterialInterface* LandscapeMaterial, FName LayerName, UTexture2D* ThumbnailWeightmap, UTexture2D* ThumbnailHeightmap, ALandscapeProxy* Proxy)
{
if (!LandscapeMaterial)
{
LandscapeMaterial = Proxy ? Proxy->GetLandscapeMaterial() : UMaterial::GetDefaultMaterial(MD_Surface);
}
FlushRenderingCommands();
ULandscapeMaterialInstanceConstant* MaterialInstance = NewObject<ULandscapeMaterialInstanceConstant>(GetTransientPackage());
MaterialInstance->bIsLayerThumbnail = true;
MaterialInstance->bMobile = false;
MaterialInstance->SetParentEditorOnly(LandscapeMaterial, false);
FStaticParameterSet StaticParameters;
MaterialInstance->GetStaticParameterValues(StaticParameters);
// Customize that material instance to only enable our terrain layer's weightmap :
StaticParameters.EditorOnly.TerrainLayerWeightParameters.Add(FStaticTerrainLayerWeightParameter(LayerName, /*InWeightmapIndex = */0, /*bInWeightBasedBlend = */false));
// Don't recreate the render state of everything, only update the materials context
{
FMaterialUpdateContext MaterialUpdateContext(FMaterialUpdateContext::EOptions::Default & ~FMaterialUpdateContext::EOptions::RecreateRenderStates);
MaterialInstance->UpdateStaticPermutation(StaticParameters, &MaterialUpdateContext);
}
FLinearColor Mask(1.0f, 0.0f, 0.0f, 0.0f);
MaterialInstance->SetVectorParameterValueEditorOnly(FName(*FString::Printf(TEXT("LayerMask_%s"), *LayerName.ToString())), Mask);
MaterialInstance->SetTextureParameterValueEditorOnly(FName(TEXT("Weightmap0")), ThumbnailWeightmap);
MaterialInstance->SetTextureParameterValueEditorOnly(FName(TEXT("Heightmap")), ThumbnailHeightmap);
MaterialInstance->PostEditChange();
return MaterialInstance;
}
bool ULandscapeComponent::ValidateCombinationMaterial(UMaterialInstanceConstant* InCombinationMaterial) const
{
if (InCombinationMaterial == nullptr)
{
return false;
}
const TArray<FStaticTerrainLayerWeightParameter>& TerrainLayerWeightParameters = InCombinationMaterial->GetEditorOnlyStaticParameters().TerrainLayerWeightParameters;
const TArray<FWeightmapLayerAllocationInfo>& ComponentWeightmapAllocations = GetWeightmapLayerAllocations();
if (TerrainLayerWeightParameters.Num() != ComponentWeightmapAllocations.Num())
{
UE_LOG(LogLandscape, Display, TEXT("Material instance %s in landscape component %s doesn't match the expected shader combination: different number of allocations (expected: %i, found: %i)"),
*InCombinationMaterial->GetName(), *GetPathName(), ComponentWeightmapAllocations.Num(), TerrainLayerWeightParameters.Num());
return false;
}
for (const FWeightmapLayerAllocationInfo& Allocation : ComponentWeightmapAllocations)
{
if (Allocation.LayerInfo == nullptr)
{
UE_LOG(LogLandscape, Display, TEXT("Material instance %s in landscape component %s doesn't match the expected shader combination: invalid layer info"),
*InCombinationMaterial->GetName(), *GetPathName(), ComponentWeightmapAllocations.Num(), TerrainLayerWeightParameters.Num());
return false;
}
// Each weightmap allocation must have its equivalent in the material's TerrainLayerWeightParameters :
if (!TerrainLayerWeightParameters.FindByPredicate([&](const FStaticTerrainLayerWeightParameter& TerrainLayerWeightParameter)
{
return ((TerrainLayerWeightParameter.LayerName == Allocation.LayerInfo->LayerName)
&& (TerrainLayerWeightParameter.WeightmapIndex == Allocation.WeightmapTextureIndex)
&& (TerrainLayerWeightParameter.bWeightBasedBlend == !Allocation.LayerInfo->bNoWeightBlend));
}))
{
UE_LOG(LogLandscape, Display, TEXT("Material instance %s in landscape component %s doesn't match the expected shader combination: missing layer %s"),
*InCombinationMaterial->GetName(), *GetPathName(), *Allocation.LayerInfo->LayerName.ToString());
return false;
}
}
return true;
}
/**
* Generate a key for this component's layer allocations to use with MaterialInstanceConstantMap.
*/
FString ULandscapeComponent::GetLayerAllocationKey(const TArray<FWeightmapLayerAllocationInfo>& Allocations, UMaterialInterface* LandscapeMaterial, bool bMobile /*= false*/)
{
if (!LandscapeMaterial)
{
return FString();
}
FString Result = LandscapeMaterial->GetPathName();
// Generate a string to describe each allocation
TArray<FString> LayerStrings;
for (int32 LayerIdx = 0; LayerIdx < Allocations.Num(); LayerIdx++)
{
const bool bNoWeightBlend = Allocations[LayerIdx].LayerInfo && Allocations[LayerIdx].LayerInfo->bNoWeightBlend;
LayerStrings.Add(FString::Printf(TEXT("_%s_%s%d"), *Allocations[LayerIdx].GetLayerName().ToString(), bNoWeightBlend ? TEXT("n") : TEXT("w"), Allocations[LayerIdx].WeightmapTextureIndex));
}
// Sort them alphabetically so we can share across components even if the order is different
LayerStrings.Sort(TGreater<FString>());
for (int32 LayerIdx = 0; LayerIdx < LayerStrings.Num(); LayerIdx++)
{
Result += LayerStrings[LayerIdx];
}
if (bMobile)
{
Result += TEXT("M");
}
return Result;
}
UMaterialInstanceConstant* ULandscapeComponent::GetCombinationMaterial(FMaterialUpdateContext* InMaterialUpdateContext, const TArray<FWeightmapLayerAllocationInfo>& Allocations, int8 InLODIndex, bool bMobile /*= false*/) const
{
const bool bComponentHasHoles = ComponentHasVisibilityPainted();
UMaterialInterface* const LandscapeMaterial = GetLandscapeMaterial(InLODIndex);
UMaterialInterface* const HoleMaterial = bComponentHasHoles ? GetLandscapeHoleMaterial() : nullptr;
UMaterialInterface* const MaterialToUse = bComponentHasHoles && HoleMaterial ? HoleMaterial : LandscapeMaterial;
bool bOverrideBlendMode = bComponentHasHoles && !HoleMaterial && LandscapeMaterial->GetBlendMode() == BLEND_Opaque;
if (bOverrideBlendMode)
{
UMaterial* Material = LandscapeMaterial->GetMaterial();
if (Material && Material->bUsedAsSpecialEngineMaterial)
{
bOverrideBlendMode = false;
static TWeakPtr<SNotificationItem> ExistingNotification;
if (!ExistingNotification.IsValid())
{
// let the user know why they are not seeing holes
FNotificationInfo Info(LOCTEXT("AssignLandscapeMaterial", "You must assign a regular, non-engine material to your landscape in order to see holes created with the visibility tool."));
Info.ExpireDuration = 5.0f;
Info.bUseSuccessFailIcons = true;
ExistingNotification = TWeakPtr<SNotificationItem>(FSlateNotificationManager::Get().AddNotification(Info));
}
return nullptr;
}
}
if (ensure(MaterialToUse != nullptr))
{
ALandscapeProxy* Proxy = GetLandscapeProxy();
FString LayerKey = GetLayerAllocationKey(Allocations, MaterialToUse, bMobile);
// Find or set a matching MIC in the Landscape's map.
UMaterialInstanceConstant* CombinationMaterialInstance = Proxy->MaterialInstanceConstantMap.FindRef(*LayerKey);
if (CombinationMaterialInstance == nullptr || CombinationMaterialInstance->Parent != MaterialToUse || GetOuter() != CombinationMaterialInstance->GetOuter())
{
FlushRenderingCommands();
ULandscapeMaterialInstanceConstant* LandscapeCombinationMaterialInstance = NewObject<ULandscapeMaterialInstanceConstant>(GetOuter());
LandscapeCombinationMaterialInstance->bMobile = bMobile;
CombinationMaterialInstance = LandscapeCombinationMaterialInstance;
UE_LOG(LogLandscape, Verbose, TEXT("Looking for key %s, making new combination %s"), *LayerKey, *CombinationMaterialInstance->GetName());
Proxy->MaterialInstanceConstantMap.Add(*LayerKey, CombinationMaterialInstance);
CombinationMaterialInstance->SetParentEditorOnly(MaterialToUse, false);
CombinationMaterialInstance->BasePropertyOverrides.bOverride_BlendMode = bOverrideBlendMode;
if (bOverrideBlendMode)
{
CombinationMaterialInstance->BasePropertyOverrides.BlendMode = bComponentHasHoles ? BLEND_Masked : BLEND_Opaque;
}
FStaticParameterSet StaticParameters;
CombinationMaterialInstance->GetStaticParameterValues(StaticParameters);
for (const FWeightmapLayerAllocationInfo& Allocation : Allocations)
{
if (Allocation.LayerInfo)
{
FName LayerName = Allocation.GetLayerName();
check(LayerName != NAME_None);
StaticParameters.EditorOnly.TerrainLayerWeightParameters.Add(FStaticTerrainLayerWeightParameter(LayerName, Allocation.WeightmapTextureIndex, !Allocation.LayerInfo->bNoWeightBlend));
}
}
CombinationMaterialInstance->UpdateStaticPermutation(StaticParameters, InMaterialUpdateContext);
CombinationMaterialInstance->PostEditChange();
}
return CombinationMaterialInstance;
}
return nullptr;
}
void ULandscapeComponent::UpdateMaterialInstances_Internal(FMaterialUpdateContext& Context)
{
int32 MaxLOD = FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1;
decltype(MaterialPerLOD) NewMaterialPerLOD;
LODIndexToMaterialIndex.SetNumUninitialized(MaxLOD+1);
int8 LastLODIndex = INDEX_NONE;
UMaterialInterface* BaseMaterial = GetLandscapeMaterial();
UMaterialInterface* LOD0Material = GetLandscapeMaterial(0);
for (int32 LODIndex = 0; LODIndex <= MaxLOD; ++LODIndex)
{
UMaterialInterface* CurrentMaterial = GetLandscapeMaterial(LODIndex);
// if we have a LOD0 override, do not let the base material override it, it should override everything!
if (CurrentMaterial == BaseMaterial && BaseMaterial != LOD0Material)
{
CurrentMaterial = LOD0Material;
}
const int8* MaterialLOD = NewMaterialPerLOD.Find(CurrentMaterial);
if (MaterialLOD != nullptr)
{
LODIndexToMaterialIndex[LODIndex] = *MaterialLOD > LastLODIndex ? *MaterialLOD : LastLODIndex;
}
else
{
int32 AddedIndex = NewMaterialPerLOD.Num();
NewMaterialPerLOD.Add(CurrentMaterial, LODIndex);
LODIndexToMaterialIndex[LODIndex] = AddedIndex;
LastLODIndex = AddedIndex;
}
}
MaterialPerLOD = NewMaterialPerLOD;
MaterialInstances.SetNumZeroed(MaterialPerLOD.Num() * 2); // over allocate in case we are using tessellation
int8 MaterialIndex = 0;
const TArray<FWeightmapLayerAllocationInfo>& WeightmapBaseLayerAllocation = GetWeightmapLayerAllocations();
const TArray<UTexture2D*>& WeightmapBaseTexture = GetWeightmapTextures();
UTexture2D* BaseHeightmap = GetHeightmap();
for (auto It = MaterialPerLOD.CreateConstIterator(); It; ++It)
{
const int8 MaterialLOD = It.Value();
// Find or set a matching MIC in the Landscape's map.
UMaterialInstanceConstant* CombinationMaterialInstance = GetCombinationMaterial(&Context, WeightmapBaseLayerAllocation, MaterialLOD, false);
if (CombinationMaterialInstance != nullptr)
{
// Create the instance for this component, that will use the layer combination instance.
UMaterialInstanceConstant* MaterialInstance = NewObject<ULandscapeMaterialInstanceConstant>(GetOuter());
MaterialInstances[MaterialIndex] = MaterialInstance;
// Material Instances don't support Undo/Redo (the shader map goes out of sync and crashes happen)
// so we call UpdateMaterialInstances() from ULandscapeComponent::PostEditUndo instead
//MaterialInstance->SetFlags(RF_Transactional);
//MaterialInstance->Modify();
MaterialInstance->SetParentEditorOnly(CombinationMaterialInstance);
MaterialInstance->ClearParameterValuesEditorOnly();
Context.AddMaterialInstance(MaterialInstance); // must be done after SetParent
FLinearColor Masks[4] = { FLinearColor(1.0f, 0.0f, 0.0f, 0.0f), FLinearColor(0.0f, 1.0f, 0.0f, 0.0f), FLinearColor(0.0f, 0.0f, 1.0f, 0.0f), FLinearColor(0.0f, 0.0f, 0.0f, 1.0f) };
// Set the layer mask
for (int32 AllocIdx = 0; AllocIdx < WeightmapBaseLayerAllocation.Num(); AllocIdx++)
{
const FWeightmapLayerAllocationInfo& Allocation = WeightmapBaseLayerAllocation[AllocIdx];
MaterialInstance->SetVectorParameterValueEditorOnly(FName(*FString::Printf(TEXT("LayerMask_%s"), *Allocation.GetLayerName().ToString())), Masks[Allocation.WeightmapTextureChannel]);
}
// Set the weightmaps
for (int32 i = 0; i < WeightmapBaseTexture.Num(); i++)
{
MaterialInstance->SetTextureParameterValueEditorOnly(FName(*FString::Printf(TEXT("Weightmap%d"), i)), WeightmapBaseTexture[i]);
}
// Set the heightmap, if needed.
if (BaseHeightmap)
{
MaterialInstance->SetTextureParameterValueEditorOnly(FName(TEXT("Heightmap")), BaseHeightmap);
}
MaterialInstance->PostEditChange();
}
++MaterialIndex;
}
MaterialInstances.Remove(nullptr);
MaterialInstances.Shrink();
if (MaterialPerLOD.Num() == 0)
{
MaterialInstances.Empty(1);
MaterialInstances.Add(nullptr);
LODIndexToMaterialIndex.Empty(1);
LODIndexToMaterialIndex.Add(0);
}
// Update mobile combination material
{
GenerateMobileWeightmapLayerAllocations();
MobileCombinationMaterialInstances.SetNumZeroed(MaterialPerLOD.Num());
int8 MobileMaterialIndex = 0;
for (auto It = MaterialPerLOD.CreateConstIterator(); It; ++It)
{
const int8 MaterialLOD = It.Value();
UMaterialInstanceConstant* MobileCombinationMaterialInstance = GetCombinationMaterial(&Context, MobileWeightmapLayerAllocations, MaterialLOD, true);
MobileCombinationMaterialInstances[MobileMaterialIndex] = MobileCombinationMaterialInstance;
if (MobileCombinationMaterialInstance != nullptr)
{
Context.AddMaterialInstance(MobileCombinationMaterialInstance);
}
++MobileMaterialIndex;
}
}
}
void ULandscapeComponent::UpdateMaterialInstances()
{
if (GDisableUpdateLandscapeMaterialInstances)
return;
// we're not having the material update context recreate the render state because we will manually do it for only this component
TOptional<FComponentRecreateRenderStateContext> RecreateRenderStateContext;
RecreateRenderStateContext.Emplace(this);
TOptional<FMaterialUpdateContext> MaterialUpdateContext;
MaterialUpdateContext.Emplace(FMaterialUpdateContext::EOptions::Default & ~FMaterialUpdateContext::EOptions::RecreateRenderStates);
UpdateMaterialInstances_Internal(MaterialUpdateContext.GetValue());
// End material update
MaterialUpdateContext.Reset();
// Recreate the render state for this component, needed to update the static drawlist which has cached the MaterialRenderProxies
// Must be after the FMaterialUpdateContext is destroyed
RecreateRenderStateContext.Reset();
}
void ULandscapeComponent::UpdateMaterialInstances(FMaterialUpdateContext& InOutMaterialContext, TArray<FComponentRecreateRenderStateContext>& InOutRecreateRenderStateContext)
{
InOutRecreateRenderStateContext.Add(this);
UpdateMaterialInstances_Internal(InOutMaterialContext);
}
void ALandscapeProxy::UpdateAllComponentMaterialInstances(FMaterialUpdateContext& InOutMaterialContext, TArray<FComponentRecreateRenderStateContext>& InOutRecreateRenderStateContext, bool bInInvalidateCombinationMaterials)
{
if (bInInvalidateCombinationMaterials)
{
MaterialInstanceConstantMap.Reset();
}
for (ULandscapeComponent* Component : LandscapeComponents)
{
Component->UpdateMaterialInstances(InOutMaterialContext, InOutRecreateRenderStateContext);
}
}
void ALandscapeProxy::UpdateAllComponentMaterialInstances(bool bInInvalidateCombinationMaterials)
{
if (bInInvalidateCombinationMaterials)
{
MaterialInstanceConstantMap.Reset();
}
// we're not having the material update context recreate render states because we will manually do it for only our components
TArray<FComponentRecreateRenderStateContext> RecreateRenderStateContexts;
RecreateRenderStateContexts.Reserve(LandscapeComponents.Num());
for (ULandscapeComponent* Component : LandscapeComponents)
{
RecreateRenderStateContexts.Emplace(Component);
}
TOptional<FMaterialUpdateContext> MaterialUpdateContext;
MaterialUpdateContext.Emplace(FMaterialUpdateContext::EOptions::Default & ~FMaterialUpdateContext::EOptions::RecreateRenderStates);
for (ULandscapeComponent* Component : LandscapeComponents)
{
Component->UpdateMaterialInstances_Internal(MaterialUpdateContext.GetValue());
}
// End material update
MaterialUpdateContext.Reset();
// Recreate the render state for our components, needed to update the static drawlist which has cached the MaterialRenderProxies
// Must be after the FMaterialUpdateContext is destroyed
RecreateRenderStateContexts.Empty();
}
int32 ULandscapeComponent::GetNumMaterials() const
{
return 1;
}
#if WITH_EDITOR
bool ULandscapeComponent::GetMaterialPropertyPath(int32 ElementIndex, UObject*& OutOwner, FString& OutPropertyPath, FProperty*& OutProperty)
{
if (ElementIndex == 0)
{
if (OverrideMaterial)
{
OutOwner = this;
OutPropertyPath = GET_MEMBER_NAME_STRING_CHECKED(ULandscapeComponent, OverrideMaterial);
OutProperty = ULandscapeComponent::StaticClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(ULandscapeComponent, OverrideMaterial));
return true;
}
if (ALandscapeProxy* Proxy = GetLandscapeProxy())
{
OutOwner = Proxy;
OutPropertyPath = GET_MEMBER_NAME_STRING_CHECKED(ALandscapeProxy, LandscapeHoleMaterial);
OutProperty = ALandscapeProxy::StaticClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LandscapeHoleMaterial));
return true;
}
}
return false;
}
#endif // WITH_EDITOR
class UMaterialInterface* ULandscapeComponent::GetMaterial(int32 ElementIndex) const
{
if (ensure(ElementIndex == 0))
{
return GetLandscapeMaterial(ElementIndex);
}
return nullptr;
}
void ULandscapeComponent::SetMaterial(int32 ElementIndex, class UMaterialInterface* Material)
{
if (ensure(ElementIndex == 0))
{
GetLandscapeProxy()->LandscapeMaterial = Material;
}
}
void ULandscapeComponent::PreFeatureLevelChange(ERHIFeatureLevel::Type PendingFeatureLevel)
{
Super::PreFeatureLevelChange(PendingFeatureLevel);
if (UseMobileLandscapeMesh(GShaderPlatformForFeatureLevel[PendingFeatureLevel]))
{
// See if we need to cook platform data for mobile preview in editor
CheckGenerateLandscapePlatformData(false, nullptr);
}
}
void ULandscapeComponent::PostEditUndo()
{
if (IsValid(this))
{
if (!GetLandscapeProxy()->HasLayersContent())
{
UpdateMaterialInstances();
}
}
Super::PostEditUndo();
// On undo, request a recompute weightmap usages which can get desynchronized since there can be duplicated between 2 components and also with the proxy :
GetLandscapeProxy()->RequestProxyLayersWeightmapUsageUpdate();
if (IsValid(this))
{
EditToolRenderData.UpdateSelectionMaterial(EditToolRenderData.SelectedType, this);
if (!GetLandscapeProxy()->HasLayersContent())
{
EditToolRenderData.UpdateDebugColorMaterial(this);
UpdateEditToolRenderData();
}
}
if (GetLandscapeProxy()->HasLayersContent())
{
const bool bUpdateAll = true;
RequestHeightmapUpdate(bUpdateAll);
RequestWeightmapUpdate(bUpdateAll);
// Clear Cached Editing Data
CachedEditingLayer.Invalidate();
CachedEditingLayerData = nullptr;
}
else
{
TSet<ULandscapeComponent*> Components;
Components.Add(this);
GetLandscapeProxy()->FlushGrassComponents(&Components);
}
}
TUniquePtr<FWorldPartitionActorDesc> ALandscapeProxy::CreateClassActorDesc() const
{
return TUniquePtr<FWorldPartitionActorDesc>(new FLandscapeActorDesc());
}
bool ALandscapeProxy::GetReferencedContentObjects(TArray<UObject*>& Objects) const
{
Super::GetReferencedContentObjects(Objects);
if (LandscapeMaterial != nullptr)
{
Objects.AddUnique(LandscapeMaterial);
}
for (const FLandscapePerLODMaterialOverride& LODOverrideMaterial : PerLODOverrideMaterials)
{
if (LODOverrideMaterial.Material != nullptr)
{
Objects.AddUnique(LODOverrideMaterial.Material);
}
}
if (LandscapeHoleMaterial != nullptr)
{
Objects.AddUnique(LandscapeHoleMaterial);
}
return true;
}
void ALandscapeProxy::FixupWeightmaps()
{
WeightmapUsageMap.Empty();
// We've just reinitialized the weightmap usages map and FixupWeightmaps will reconstruct it component by component, but we might in the process delete invalid layers (e.g. those whose landscape info has been deleted),
// in which case ValidateProxyLayersWeightmapUsage() on the entire proxy will be called and, because of WeightmapUsageMap's cleanup above, might report missing weightmap usages.
// To avoid triggering asserts, we simply disable validation until the fixup operation is done :
bTemporarilyDisableWeightmapUsagesValidation = true;
ON_SCOPE_EXIT
{
bTemporarilyDisableWeightmapUsagesValidation = false;
// Now that the job is done, weightmap usages should be valid again
ValidateProxyLayersWeightmapUsage();
};
for (ULandscapeComponent* Component : LandscapeComponents)
{
if (Component != nullptr)
{
Component->FixupWeightmaps();
}
}
}
void ALandscapeProxy::RepairInvalidTextures()
{
TArray<UTexture*> InvalidTextures;
for (ULandscapeComponent* Component : LandscapeComponents)
{
if (Component != nullptr)
{
InvalidTextures.Append(Component->RepairInvalidTextures());
}
}
if (!InvalidTextures.IsEmpty())
{
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("LandscapeName"), FText::FromString(GetPathName()));
Arguments.Add(TEXT("ErrorMessage"), FText::Format(LOCTEXT("InvalidTexturesMessage", "Invalid data detected on {0} {0}|plural(one = texture, other = textures). The data has been cleared to avoid fatal error."), InvalidTextures.Num()));
FMessageLog("MapCheck").Error()
->AddToken(FTextToken::Create(FText::Format(LOCTEXT("MapCheck_Message_ClearedInvalidWeightmap", "{LandscapeName} : {ErrorMessage}"), Arguments)))
->AddToken(FMapErrorToken::Create(FMapErrors::FixedUpDeletedLayerWeightmap));
}
}
bool IsValidLandscapeTextureSourceData(const UTexture& InTexture)
{
FIntPoint SourceDataSize = InTexture.Source.GetLogicalSize();
return ((SourceDataSize.X * SourceDataSize.Y) > 0) == InTexture.Source.HasPayloadData();
}
TArray<UTexture*> ULandscapeComponent::RepairInvalidTextures()
{
TArray<UTexture*> AllTextures = GetGeneratedTextures();
TArray<UTexture*> InvalidTextures;
for (UTexture* Texture : AllTextures)
{
Texture->ConditionalPostLoad();
if (!IsValidLandscapeTextureSourceData(*Texture))
{
UE_LOG(LogLandscape, Error, TEXT("Invalid data found in texture %s from landscape component %s : clearing data."), *Texture->GetName(), *GetPathName());
CreateEmptyTextureMips(CastChecked<UTexture2D>(Texture), true);
Texture->PostEditChange();
InvalidTextures.Add(Texture);
}
}
return InvalidTextures;
}
void ULandscapeComponent::FixupWeightmaps()
{
// Fixup final weightmaps
FixupWeightmaps(FGuid());
// Also fixup all edit layers weightmaps :
ForEachLayer([&](const FGuid& LayerGuid, FLandscapeLayerComponentData& LayerData)
{
FixupWeightmaps(LayerGuid);
});
}
void ULandscapeComponent::FixupWeightmaps(const FGuid& InEditLayerGuid)
{
if (GIsEditor && !HasAnyFlags(RF_ClassDefaultObject))
{
ULandscapeInfo* Info = GetLandscapeInfo();
ALandscapeProxy* Proxy = GetLandscapeProxy();
TArray<UTexture2D*>& LocalWeightmapTextures = GetWeightmapTextures(InEditLayerGuid);
TArray<ULandscapeWeightmapUsage*>& LocalWeightmapTextureUsages = GetWeightmapTexturesUsage(InEditLayerGuid);
TArray<FWeightmapLayerAllocationInfo>& LocalWeightmapLayerAllocations = GetWeightmapLayerAllocations(InEditLayerGuid);
if (Info)
{
LocalWeightmapTextureUsages.Empty();
LocalWeightmapTextureUsages.AddDefaulted(LocalWeightmapTextures.Num());
TArray<ULandscapeLayerInfoObject*> LayersToDelete;
bool bFixedLayerDeletion = false;
// make sure the weightmap textures are fully loaded or deleting layers from them will crash! :)
for (UTexture* WeightmapTexture : LocalWeightmapTextures)
{
WeightmapTexture->ConditionalPostLoad();
}
// LayerInfo Validation check...
for (const auto& Allocation : LocalWeightmapLayerAllocations)
{
if (!Allocation.LayerInfo
|| (Allocation.LayerInfo != ALandscapeProxy::VisibilityLayer && Info->GetLayerInfoIndex(Allocation.LayerInfo) == INDEX_NONE))
{
if (!bFixedLayerDeletion)
{
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("LandscapeName"), FText::FromString(GetPathName()));
FMessageLog("MapCheck").Warning()
->AddToken(FTextToken::Create(FText::Format(LOCTEXT("MapCheck_Message_FixedUpDeletedLayerWeightmap", "{LandscapeName} : Fixed up deleted layer weightmap"), Arguments)))
->AddToken(FMapErrorToken::Create(FMapErrors::FixedUpDeletedLayerWeightmap));
}
bFixedLayerDeletion = true;
LayersToDelete.Add(Allocation.LayerInfo);
}
}
if (bFixedLayerDeletion)
{
{
FLandscapeEditDataInterface LandscapeEdit(Info);
for (int32 Idx = 0; Idx < LayersToDelete.Num(); ++Idx)
{
DeleteLayer(LayersToDelete[Idx], LandscapeEdit);
}
}
ForEachLayer([&](const FGuid& LayerGuid, FLandscapeLayerComponentData& LayerData)
{
SetEditingLayer(LayerGuid);
FLandscapeEditDataInterface LandscapeEdit(Info);
for (int32 Idx = 0; Idx < LayersToDelete.Num(); ++Idx)
{
DeleteLayer(LayersToDelete[Idx], LandscapeEdit);
}
});
// Make sure to clear editing layer and cache
SetEditingLayer(FGuid());
CachedEditingLayer.Invalidate();
CachedEditingLayerData = nullptr;
}
bool bFixedWeightmapTextureIndex = false;
// Store the weightmap allocations in WeightmapUsageMap
for (int32 LayerIdx = 0; LayerIdx < LocalWeightmapLayerAllocations.Num();)
{
FWeightmapLayerAllocationInfo& Allocation = LocalWeightmapLayerAllocations[LayerIdx];
if (!Allocation.IsAllocated())
{
LocalWeightmapLayerAllocations.RemoveAt(LayerIdx);
continue;
}
// Fix up any problems caused by the layer deletion bug.
if (Allocation.WeightmapTextureIndex >= LocalWeightmapTextures.Num())
{
Allocation.WeightmapTextureIndex = LocalWeightmapTextures.Num() - 1;
if (!bFixedWeightmapTextureIndex)
{
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("LandscapeName"), FText::FromString(GetName()));
FMessageLog("MapCheck").Warning()
->AddToken(FTextToken::Create(FText::Format(LOCTEXT("MapCheck_Message_FixedUpIncorrectLayerWeightmap", "{LandscapeName} : Fixed up incorrect layer weightmap texture index"), Arguments)))
->AddToken(FMapErrorToken::Create(FMapErrors::FixedUpIncorrectLayerWeightmap));
}
bFixedWeightmapTextureIndex = true;
}
UTexture2D* WeightmapTexture = LocalWeightmapTextures[Allocation.WeightmapTextureIndex];
TObjectPtr<ULandscapeWeightmapUsage>* TempUsage = Proxy->WeightmapUsageMap.Find(WeightmapTexture);
if (TempUsage == nullptr)
{
TempUsage = &Proxy->WeightmapUsageMap.Add(WeightmapTexture, GetLandscapeProxy()->CreateWeightmapUsage());
(*TempUsage)->LayerGuid.Invalidate();
}
ULandscapeWeightmapUsage* Usage = *TempUsage;
LocalWeightmapTextureUsages[Allocation.WeightmapTextureIndex] = Usage; // Keep a ref to it for faster access
// Detect a shared layer allocation, caused by a previous undo or layer deletion bugs
if (Usage->ChannelUsage[Allocation.WeightmapTextureChannel] != nullptr &&
Usage->ChannelUsage[Allocation.WeightmapTextureChannel] != this)
{
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("LayerName"), FText::FromString(Allocation.GetLayerName().ToString()));
Arguments.Add(TEXT("LandscapeName"), FText::FromString(GetName()));
Arguments.Add(TEXT("ChannelName"), FText::FromString(Usage->ChannelUsage[Allocation.WeightmapTextureChannel]->GetName()));
FMessageLog("MapCheck").Warning()
->AddToken(FTextToken::Create(FText::Format(LOCTEXT("MapCheck_Message_FixedUpSharedLayerWeightmap", "Fixed up shared weightmap texture for layer {LayerName} in component '{LandscapeName}' (shares with '{ChannelName}')"), Arguments)))
->AddToken(FMapErrorToken::Create(FMapErrors::FixedUpSharedLayerWeightmap));
LocalWeightmapLayerAllocations.RemoveAt(LayerIdx);
continue;
}
else
{
Usage->ChannelUsage[Allocation.WeightmapTextureChannel] = this;
}
++LayerIdx;
}
RemoveInvalidWeightmaps(InEditLayerGuid);
}
}
}
void ULandscapeComponent::UpdateLayerAllowListFromPaintedLayers()
{
const TArray<FWeightmapLayerAllocationInfo>& ComponentWeightmapLayerAllocations = GetWeightmapLayerAllocations();
for (const auto& Allocation : ComponentWeightmapLayerAllocations)
{
LayerAllowList.AddUnique(Allocation.LayerInfo);
}
}
//
// LandscapeComponentAlphaInfo
//
struct FLandscapeComponentAlphaInfo
{
int32 LayerIndex;
TArray<uint8> AlphaValues;
// tor
FLandscapeComponentAlphaInfo(ULandscapeComponent* InOwner, int32 InLayerIndex)
: LayerIndex(InLayerIndex)
{
AlphaValues.Empty(FMath::Square(InOwner->ComponentSizeQuads + 1));
AlphaValues.AddZeroed(FMath::Square(InOwner->ComponentSizeQuads + 1));
}
bool IsLayerAllZero() const
{
for (int32 Index = 0; Index < AlphaValues.Num(); Index++)
{
if (AlphaValues[Index] != 0)
{
return false;
}
}
return true;
}
};
struct FCollisionSize
{
public:
static FCollisionSize CreateSimple(bool bUseSimpleCollision, int32 InNumSubSections, int32 InSubsectionSizeQuads, int32 InMipLevel)
{
return bUseSimpleCollision ? Create(InNumSubSections, InSubsectionSizeQuads, InMipLevel) : FCollisionSize();
}
static FCollisionSize Create(int32 InNumSubSections, int32 InSubsectionSizeQuads, int32 InMipLevel)
{
return FCollisionSize(InNumSubSections, InSubsectionSizeQuads, InMipLevel);
}
FCollisionSize(FCollisionSize&& Other) = default;
FCollisionSize& operator=(FCollisionSize&& Other) = default;
private:
FCollisionSize(int32 InNumSubsections, int32 InSubsectionSizeQuads, int32 InMipLevel)
{
SubsectionSizeVerts = ((InSubsectionSizeQuads + 1) >> InMipLevel);
SubsectionSizeQuads = SubsectionSizeVerts - 1;
SizeVerts = InNumSubsections * SubsectionSizeQuads + 1;
SizeVertsSquare = FMath::Square(SizeVerts);
}
FCollisionSize()
{
}
public:
int32 SubsectionSizeVerts = 0;
int32 SubsectionSizeQuads = 0;
int32 SizeVerts = 0;
int32 SizeVertsSquare = 0;
};
void ULandscapeComponent::UpdateDirtyCollisionHeightData(FIntRect Region)
{
// Take first value as is
if (LayerDirtyCollisionHeightData.IsEmpty())
{
LayerDirtyCollisionHeightData = Region;
}
else
{
// Merge min/max region
LayerDirtyCollisionHeightData.Include(Region.Min);
LayerDirtyCollisionHeightData.Include(Region.Max);
}
}
void ULandscapeComponent::ClearDirtyCollisionHeightData()
{
LayerDirtyCollisionHeightData = FIntRect();
}
void ULandscapeComponent::UpdateCollisionHeightData(const FColor* const HeightmapTextureMipData, const FColor* const SimpleCollisionHeightmapTextureData, int32 ComponentX1/*=0*/, int32 ComponentY1/*=0*/, int32 ComponentX2/*=MAX_int32*/, int32 ComponentY2/*=MAX_int32*/, bool bUpdateBounds/*=false*/, const FColor* XYOffsetTextureMipData/*=nullptr*/, bool bInUpdateHeightfieldRegion/*=true*/)
{
ULandscapeInfo* Info = GetLandscapeInfo();
ALandscapeProxy* Proxy = GetLandscapeProxy();
FIntPoint ComponentKey = GetSectionBase() / ComponentSizeQuads;
ULandscapeHeightfieldCollisionComponent* CollisionComp = CollisionComponent.Get();
ULandscapeMeshCollisionComponent* MeshCollisionComponent = Cast<ULandscapeMeshCollisionComponent>(CollisionComp);
ULandscapeHeightfieldCollisionComponent* OldCollisionComponent = CollisionComp;
// Simple collision is not currently supported with mesh collision components
const bool bUsingSimpleCollision = (SimpleCollisionMipLevel > CollisionMipLevel && SimpleCollisionHeightmapTextureData && !XYOffsetmapTexture);
FCollisionSize CollisionSize = FCollisionSize::Create(NumSubsections, SubsectionSizeQuads, CollisionMipLevel);
FCollisionSize SimpleCollisionSize = FCollisionSize::CreateSimple(bUsingSimpleCollision, NumSubsections, SubsectionSizeQuads, SimpleCollisionMipLevel);
const int32 TotalCollisionSize = CollisionSize.SizeVertsSquare + SimpleCollisionSize.SizeVertsSquare;
uint16* CollisionHeightData = nullptr;
uint16* CollisionXYOffsetData = nullptr;
bool CreatedNew = false;
bool ChangeType = false;
// In Landscape Layers the Collision Component is slave and doesn't need to be transacted
if (!Proxy->HasLayersContent())
{
if (CollisionComp)
{
CollisionComp->Modify();
}
}
else
{
// In Landscape Layers, only update dirtied collision height data
if (bInUpdateHeightfieldRegion && ComponentX1 == 0 && ComponentY1 == 0 && ComponentX2 == MAX_int32 && ComponentY2 == MAX_int32 && !LayerDirtyCollisionHeightData.IsEmpty())
{
ComponentX1 = LayerDirtyCollisionHeightData.Min.X;
ComponentY1 = LayerDirtyCollisionHeightData.Min.Y;
ComponentX2 = LayerDirtyCollisionHeightData.Max.X;
ComponentY2 = LayerDirtyCollisionHeightData.Max.Y;
}
ClearDirtyCollisionHeightData();
}
// Existing collision component is same type with collision
if (CollisionComp && ((XYOffsetmapTexture == nullptr) == (MeshCollisionComponent == nullptr)))
{
ComponentX1 = FMath::Clamp(ComponentX1, 0, ComponentSizeQuads);
ComponentY1 = FMath::Clamp(ComponentY1, 0, ComponentSizeQuads);
ComponentX2 = FMath::Clamp(ComponentX2, 0, ComponentSizeQuads);
ComponentY2 = FMath::Clamp(ComponentY2, 0, ComponentSizeQuads);
if (ComponentX2 < ComponentX1 || ComponentY2 < ComponentY1)
{
// nothing to do
return;
}
if (bUpdateBounds)
{
CollisionComp->CachedLocalBox = CachedLocalBox;
CollisionComp->UpdateComponentToWorld();
}
}
else
{
CreatedNew = true;
ChangeType = CollisionComp != nullptr;
ComponentX1 = 0;
ComponentY1 = 0;
ComponentX2 = ComponentSizeQuads;
ComponentY2 = ComponentSizeQuads;
RecreateCollisionComponent(bUsingSimpleCollision);
CollisionComp = CollisionComponent.Get();
MeshCollisionComponent = Cast<ULandscapeMeshCollisionComponent>(CollisionComp);
}
CollisionHeightData = (uint16*)CollisionComp->CollisionHeightData.Lock(LOCK_READ_WRITE);
if (XYOffsetmapTexture && MeshCollisionComponent)
{
CollisionXYOffsetData = (uint16*)MeshCollisionComponent->CollisionXYOffsetData.Lock(LOCK_READ_WRITE);
}
const int32 HeightmapSizeU = GetHeightmap()->Source.GetSizeX();
const int32 HeightmapSizeV = GetHeightmap()->Source.GetSizeY();
// Handle Material WPO baked into heightfield collision
// Material WPO is not currently supported for mesh collision components
const bool bUsingGrassMapHeights = Proxy->bBakeMaterialPositionOffsetIntoCollision && !MeshCollisionComponent && GrassData->HasData() && !IsGrassMapOutdated();
uint16* GrassHeights = nullptr;
if (bUsingGrassMapHeights)
{
if (CollisionMipLevel == 0)
{
GrassHeights = GrassData->GetHeightData().GetData();
}
else
{
if (GrassData->HeightMipData.Contains(CollisionMipLevel))
{
GrassHeights = GrassData->HeightMipData[CollisionMipLevel].GetData();
}
}
}
UpdateCollisionHeightBuffer(ComponentX1, ComponentY1, ComponentX2, ComponentY2, CollisionMipLevel, HeightmapSizeU, HeightmapSizeV, HeightmapTextureMipData, CollisionHeightData, GrassHeights, XYOffsetTextureMipData, CollisionXYOffsetData);
if (bUsingSimpleCollision)
{
uint16* SimpleCollisionGrassHeights = bUsingGrassMapHeights && GrassData->HeightMipData.Contains(SimpleCollisionMipLevel) ? GrassData->HeightMipData[SimpleCollisionMipLevel].GetData() : nullptr;
uint16* const SimpleCollisionHeightData = CollisionHeightData + CollisionSize.SizeVertsSquare;
UpdateCollisionHeightBuffer(ComponentX1, ComponentY1, ComponentX2, ComponentY2, SimpleCollisionMipLevel, HeightmapSizeU, HeightmapSizeV, SimpleCollisionHeightmapTextureData, SimpleCollisionHeightData, SimpleCollisionGrassHeights, nullptr, nullptr);
}
CollisionComp->CollisionHeightData.Unlock();
if (XYOffsetmapTexture && MeshCollisionComponent)
{
MeshCollisionComponent->CollisionXYOffsetData.Unlock();
}
// If we updated an existing component, we need to update the phys x heightfield edit data
if (!CreatedNew && bInUpdateHeightfieldRegion)
{
if (MeshCollisionComponent)
{
// Will be done once for XY Offset data update in FXYOffsetmapAccessor() destructor with UpdateCachedBounds()
//MeshCollisionComponent->RecreateCollision();
}
else if (CollisionMipLevel == 0)
{
CollisionComp->UpdateHeightfieldRegion(ComponentX1, ComponentY1, ComponentX2, ComponentY2);
}
else
{
// Ratio to convert update region coordinate to collision mip coordinates
const float CollisionQuadRatio = (float)CollisionSize.SubsectionSizeQuads / (float)SubsectionSizeQuads;
const int32 CollisionCompX1 = FMath::FloorToInt((float)ComponentX1 * CollisionQuadRatio);
const int32 CollisionCompY1 = FMath::FloorToInt((float)ComponentY1 * CollisionQuadRatio);
const int32 CollisionCompX2 = FMath::CeilToInt( (float)ComponentX2 * CollisionQuadRatio);
const int32 CollisionCompY2 = FMath::CeilToInt( (float)ComponentY2 * CollisionQuadRatio);
CollisionComp->UpdateHeightfieldRegion(CollisionCompX1, CollisionCompY1, CollisionCompX2, CollisionCompY2);
}
}
{
// set relevancy for navigation system
ALandscapeProxy* LandscapeProxy = CollisionComp->GetLandscapeProxy();
CollisionComp->SetCanEverAffectNavigation(LandscapeProxy ? LandscapeProxy->bUsedForNavigation : false);
}
// Move any foliage instances if we created a new collision component.
if (OldCollisionComponent && OldCollisionComponent != CollisionComp)
{
AInstancedFoliageActor::MoveInstancesToNewComponent(Proxy->GetWorld(), OldCollisionComponent, CollisionComp);
}
if (CreatedNew && !ChangeType)
{
UpdateCollisionLayerData();
}
if (CreatedNew && Proxy->GetRootComponent()->IsRegistered())
{
CollisionComp->RegisterComponent();
}
// Invalidate rendered physical materials
// These are updated in UpdatePhysicalMaterialTasks()
PhysicalMaterialHash = 0;
}
void ULandscapeComponent::DestroyCollisionData()
{
ULandscapeHeightfieldCollisionComponent* CollisionComp = CollisionComponent.Get();
if (CollisionComp)
{
CollisionComp->DestroyComponent();
CollisionComponent = CollisionComp = nullptr;
}
}
void ULandscapeComponent::UpdateCollisionData(bool bInUpdateHeightfieldRegion)
{
TArray64<uint8> CollisionMipData;
TArray64<uint8> SimpleCollisionMipData;
TArray64<uint8> XYOffsetMipData;
GetHeightmap()->Source.GetMipData(CollisionMipData, CollisionMipLevel);
if (SimpleCollisionMipLevel > CollisionMipLevel)
{
GetHeightmap()->Source.GetMipData(SimpleCollisionMipData, SimpleCollisionMipLevel);
}
if (XYOffsetmapTexture)
{
XYOffsetmapTexture->Source.GetMipData(XYOffsetMipData, CollisionMipLevel);
}
UpdateCollisionHeightData(
(FColor*)CollisionMipData.GetData(),
SimpleCollisionMipLevel > CollisionMipLevel ? (FColor*)SimpleCollisionMipData.GetData() : nullptr,
0, 0, MAX_int32, MAX_int32, true,
XYOffsetmapTexture ? (FColor*)XYOffsetMipData.GetData() : nullptr, bInUpdateHeightfieldRegion);
}
void ULandscapeComponent::RecreateCollisionComponent(bool bUseSimpleCollision)
{
ULandscapeHeightfieldCollisionComponent* CollisionComp = CollisionComponent.Get();
ULandscapeMeshCollisionComponent* MeshCollisionComponent = nullptr;
TArray<uint8> DominantLayerData;
TArray<ULandscapeLayerInfoObject*> LayerInfos;
ALandscapeProxy* Proxy = GetLandscapeProxy();
ULandscapeInfo* Info = GetLandscapeInfo();
const FCollisionSize CollisionSize = FCollisionSize::Create(NumSubsections, SubsectionSizeQuads, CollisionMipLevel);
const FCollisionSize SimpleCollisionSize = FCollisionSize::CreateSimple(bUseSimpleCollision, NumSubsections, SubsectionSizeQuads, SimpleCollisionMipLevel);
const int32 TotalCollisionSize = CollisionSize.SizeVertsSquare + SimpleCollisionSize.SizeVertsSquare;
if (CollisionComp) // remove old component before changing to other type collision...
{
if (CollisionComp->DominantLayerData.GetElementCount())
{
check(CollisionComp->DominantLayerData.GetElementCount() >= TotalCollisionSize);
DominantLayerData.AddUninitialized(TotalCollisionSize);
const uint8* SrcDominantLayerData = (uint8*)CollisionComp->DominantLayerData.Lock(LOCK_READ_ONLY);
FMemory::Memcpy(DominantLayerData.GetData(), SrcDominantLayerData, TotalCollisionSize * CollisionComp->DominantLayerData.GetElementSize());
CollisionComp->DominantLayerData.Unlock();
}
if (CollisionComp->ComponentLayerInfos.Num())
{
LayerInfos = CollisionComp->ComponentLayerInfos;
}
if (Info)
{
Info->Modify();
}
Proxy->Modify();
CollisionComp->DestroyComponent();
CollisionComp = nullptr;
}
if (XYOffsetmapTexture)
{
MeshCollisionComponent = NewObject<ULandscapeMeshCollisionComponent>(Proxy, NAME_None, RF_Transactional);
CollisionComp = MeshCollisionComponent;
}
else
{
MeshCollisionComponent = nullptr;
CollisionComp = NewObject<ULandscapeHeightfieldCollisionComponent>(Proxy, NAME_None, RF_Transactional);
}
CollisionComp->SetRelativeLocation(GetRelativeLocation());
CollisionComp->SetupAttachment(Proxy->GetRootComponent(), NAME_None);
Proxy->CollisionComponents.Add(CollisionComp);
CollisionComp->RenderComponent = this;
CollisionComp->SetSectionBase(GetSectionBase());
CollisionComp->CollisionSizeQuads = CollisionSize.SubsectionSizeQuads * NumSubsections;
CollisionComp->CollisionScale = (float)(ComponentSizeQuads) / (float)(CollisionComp->CollisionSizeQuads);
CollisionComp->SimpleCollisionSizeQuads = SimpleCollisionSize.SubsectionSizeQuads * NumSubsections;
CollisionComp->CachedLocalBox = CachedLocalBox;
CollisionComp->SetGenerateOverlapEvents(Proxy->bGenerateOverlapEvents);
// Reallocate raw collision data
CollisionComp->CollisionHeightData.Lock(LOCK_READ_WRITE);
uint16* CollisionHeightData = (uint16*)CollisionComp->CollisionHeightData.Realloc(TotalCollisionSize);
FMemory::Memzero(CollisionHeightData, TotalCollisionSize * CollisionComp->CollisionHeightData.GetElementSize());
CollisionComp->CollisionHeightData.Unlock();
if (XYOffsetmapTexture && MeshCollisionComponent)
{
// Need XYOffsetData for Collision Component
MeshCollisionComponent->CollisionXYOffsetData.Lock(LOCK_READ_WRITE);
uint16* CollisionXYOffsetData = (uint16*)MeshCollisionComponent->CollisionXYOffsetData.Realloc(TotalCollisionSize * 2);
FMemory::Memzero(CollisionXYOffsetData, TotalCollisionSize * 2 * MeshCollisionComponent->CollisionXYOffsetData.GetElementSize());
MeshCollisionComponent->CollisionXYOffsetData.Unlock();
}
if (DominantLayerData.Num())
{
CollisionComp->DominantLayerData.Lock(LOCK_READ_WRITE);
uint8* DestDominantLayerData = (uint8*)CollisionComp->DominantLayerData.Realloc(TotalCollisionSize);
FMemory::Memcpy(DestDominantLayerData, DominantLayerData.GetData(), TotalCollisionSize * CollisionComp->DominantLayerData.GetElementSize());
CollisionComp->DominantLayerData.Unlock();
}
if (LayerInfos.Num())
{
CollisionComp->ComponentLayerInfos = MoveTemp(LayerInfos);
}
CollisionComponent = CollisionComp;
}
void ULandscapeComponent::UpdateCollisionHeightBuffer( int32 InComponentX1, int32 InComponentY1, int32 InComponentX2, int32 InComponentY2, int32 InCollisionMipLevel, int32 InHeightmapSizeU, int32 InHeightmapSizeV,
const FColor* const InHeightmapTextureMipData, uint16* OutCollisionHeightData, uint16* InGrassHeightData,
const FColor* const InXYOffsetTextureMipData, uint16* OutCollisionXYOffsetData)
{
FCollisionSize CollisionSize = FCollisionSize::Create(NumSubsections, SubsectionSizeQuads, InCollisionMipLevel);
// Ratio to convert update region coordinate to collision mip coordinates
const float CollisionQuadRatio = (float)CollisionSize.SubsectionSizeQuads / (float)SubsectionSizeQuads;
const int32 SubSectionX1 = FMath::Max(0, FMath::DivideAndRoundDown(InComponentX1 - 1, SubsectionSizeQuads));
const int32 SubSectionY1 = FMath::Max(0, FMath::DivideAndRoundDown(InComponentY1 - 1, SubsectionSizeQuads));
const int32 SubSectionX2 = FMath::Min(FMath::DivideAndRoundUp(InComponentX2 + 1, SubsectionSizeQuads), NumSubsections);
const int32 SubSectionY2 = FMath::Min(FMath::DivideAndRoundUp(InComponentY2 + 1, SubsectionSizeQuads), NumSubsections);
const int32 MipSizeU = InHeightmapSizeU >> InCollisionMipLevel;
const int32 MipSizeV = InHeightmapSizeV >> InCollisionMipLevel;
const int32 HeightmapOffsetX = FMath::RoundToInt(HeightmapScaleBias.Z * (float)InHeightmapSizeU) >> InCollisionMipLevel;
const int32 HeightmapOffsetY = FMath::RoundToInt(HeightmapScaleBias.W * (float)InHeightmapSizeV) >> InCollisionMipLevel;
const int32 XYMipSizeU = XYOffsetmapTexture ? XYOffsetmapTexture->Source.GetSizeX() >> InCollisionMipLevel : 0;
for (int32 SubsectionY = SubSectionY1; SubsectionY < SubSectionY2; ++SubsectionY)
{
for (int32 SubsectionX = SubSectionX1; SubsectionX < SubSectionX2; ++SubsectionX)
{
// Area to update in subsection coordinates
const int32 SubX1 = InComponentX1 - SubsectionSizeQuads * SubsectionX;
const int32 SubY1 = InComponentY1 - SubsectionSizeQuads * SubsectionY;
const int32 SubX2 = InComponentX2 - SubsectionSizeQuads * SubsectionX;
const int32 SubY2 = InComponentY2 - SubsectionSizeQuads * SubsectionY;
// Area to update in collision mip level coords
const int32 CollisionSubX1 = FMath::FloorToInt((float)SubX1 * CollisionQuadRatio);
const int32 CollisionSubY1 = FMath::FloorToInt((float)SubY1 * CollisionQuadRatio);
const int32 CollisionSubX2 = FMath::CeilToInt((float)SubX2 * CollisionQuadRatio);
const int32 CollisionSubY2 = FMath::CeilToInt((float)SubY2 * CollisionQuadRatio);
// Clamp area to update
const int32 VertX1 = FMath::Clamp<int32>(CollisionSubX1, 0, CollisionSize.SubsectionSizeQuads);
const int32 VertY1 = FMath::Clamp<int32>(CollisionSubY1, 0, CollisionSize.SubsectionSizeQuads);
const int32 VertX2 = FMath::Clamp<int32>(CollisionSubX2, 0, CollisionSize.SubsectionSizeQuads);
const int32 VertY2 = FMath::Clamp<int32>(CollisionSubY2, 0, CollisionSize.SubsectionSizeQuads);
for (int32 VertY = VertY1; VertY <= VertY2; VertY++)
{
for (int32 VertX = VertX1; VertX <= VertX2; VertX++)
{
// this uses Quads as we don't want the duplicated vertices
const int32 CompVertX = CollisionSize.SubsectionSizeQuads * SubsectionX + VertX;
const int32 CompVertY = CollisionSize.SubsectionSizeQuads * SubsectionY + VertY;
if (InGrassHeightData)
{
uint16& CollisionHeight = OutCollisionHeightData[CompVertX + CompVertY * CollisionSize.SizeVerts];
const uint16& NewHeight = InGrassHeightData[CompVertX + CompVertY * CollisionSize.SizeVerts];
CollisionHeight = NewHeight;
}
else
{
// X/Y of the vertex we're looking indexed into the texture data
const int32 TexX = HeightmapOffsetX + CollisionSize.SubsectionSizeVerts * SubsectionX + VertX;
const int32 TexY = HeightmapOffsetY + CollisionSize.SubsectionSizeVerts * SubsectionY + VertY;
const FColor& TexData = InHeightmapTextureMipData[TexX + TexY * MipSizeU];
// Copy collision data
uint16& CollisionHeight = OutCollisionHeightData[CompVertX + CompVertY * CollisionSize.SizeVerts];
const uint16 NewHeight = TexData.R << 8 | TexData.G;
CollisionHeight = NewHeight;
}
if (XYOffsetmapTexture && InXYOffsetTextureMipData && OutCollisionXYOffsetData)
{
const int32 TexX = CollisionSize.SubsectionSizeVerts * SubsectionX + VertX;
const int32 TexY = CollisionSize.SubsectionSizeVerts * SubsectionY + VertY;
const FColor& TexData = InXYOffsetTextureMipData[TexX + TexY * XYMipSizeU];
// Copy collision data
const uint16 NewXOffset = TexData.R << 8 | TexData.G;
const uint16 NewYOffset = TexData.B << 8 | TexData.A;
const int32 XYIndex = CompVertX + CompVertY * CollisionSize.SizeVerts;
OutCollisionXYOffsetData[XYIndex * 2] = NewXOffset;
OutCollisionXYOffsetData[XYIndex * 2 + 1] = NewYOffset;
}
}
}
}
}
}
void ULandscapeComponent::UpdateDominantLayerBuffer(int32 InComponentX1, int32 InComponentY1, int32 InComponentX2, int32 InComponentY2, int32 InCollisionMipLevel, int32 InWeightmapSizeU, int32 InDataLayerIdx, const TArray<uint8*>& InCollisionDataPtrs, const TArray<ULandscapeLayerInfoObject*>& InLayerInfos, uint8* OutDominantLayerData)
{
const int32 MipSizeU = InWeightmapSizeU >> InCollisionMipLevel;
FCollisionSize CollisionSize = FCollisionSize::Create(NumSubsections, SubsectionSizeQuads, InCollisionMipLevel);
// Ratio to convert update region coordinate to collision mip coordinates
const float CollisionQuadRatio = (float)CollisionSize.SubsectionSizeQuads / (float)SubsectionSizeQuads;
const int32 SubSectionX1 = FMath::Max(0, FMath::DivideAndRoundDown(InComponentX1 - 1, SubsectionSizeQuads));
const int32 SubSectionY1 = FMath::Max(0, FMath::DivideAndRoundDown(InComponentY1 - 1, SubsectionSizeQuads));
const int32 SubSectionX2 = FMath::Min(FMath::DivideAndRoundUp(InComponentX2 + 1, SubsectionSizeQuads), NumSubsections);
const int32 SubSectionY2 = FMath::Min(FMath::DivideAndRoundUp(InComponentY2 + 1, SubsectionSizeQuads), NumSubsections);
for (int32 SubsectionY = SubSectionY1; SubsectionY < SubSectionY2; ++SubsectionY)
{
for (int32 SubsectionX = SubSectionX1; SubsectionX < SubSectionX2; ++SubsectionX)
{
// Area to update in subsection coordinates
const int32 SubX1 = InComponentX1 - SubsectionSizeQuads * SubsectionX;
const int32 SubY1 = InComponentY1 - SubsectionSizeQuads * SubsectionY;
const int32 SubX2 = InComponentX2 - SubsectionSizeQuads * SubsectionX;
const int32 SubY2 = InComponentY2 - SubsectionSizeQuads * SubsectionY;
// Area to update in collision mip level coords
const int32 CollisionSubX1 = FMath::FloorToInt((float)SubX1 * CollisionQuadRatio);
const int32 CollisionSubY1 = FMath::FloorToInt((float)SubY1 * CollisionQuadRatio);
const int32 CollisionSubX2 = FMath::CeilToInt((float)SubX2 * CollisionQuadRatio);
const int32 CollisionSubY2 = FMath::CeilToInt((float)SubY2 * CollisionQuadRatio);
// Clamp area to update
const int32 VertX1 = FMath::Clamp<int32>(CollisionSubX1, 0, CollisionSize.SubsectionSizeQuads);
const int32 VertY1 = FMath::Clamp<int32>(CollisionSubY1, 0, CollisionSize.SubsectionSizeQuads);
const int32 VertX2 = FMath::Clamp<int32>(CollisionSubX2, 0, CollisionSize.SubsectionSizeQuads);
const int32 VertY2 = FMath::Clamp<int32>(CollisionSubY2, 0, CollisionSize.SubsectionSizeQuads);
for (int32 VertY = VertY1; VertY <= VertY2; VertY++)
{
for (int32 VertX = VertX1; VertX <= VertX2; VertX++)
{
// X/Y of the vertex we're looking indexed into the texture data
const int32 TexX = CollisionSize.SubsectionSizeVerts * SubsectionX + VertX;
const int32 TexY = CollisionSize.SubsectionSizeVerts * SubsectionY + VertY;
const int32 DataOffset = (TexX + TexY * MipSizeU) * sizeof(FColor);
uint8 DominantLayer = 255; // 255 as invalid value
int32 DominantWeight = 0;
for (int32 LayerIdx = 0; LayerIdx < InCollisionDataPtrs.Num(); LayerIdx++)
{
const uint8 LayerWeight = InCollisionDataPtrs[LayerIdx][DataOffset];
const uint8 LayerMinimumWeight = InLayerInfos[LayerIdx] ? (uint8)(InLayerInfos[LayerIdx]->MinimumCollisionRelevanceWeight * 255) : 0;
if (LayerIdx == InDataLayerIdx) // Override value for hole
{
if (LayerWeight > 170) // 255 * 0.66...
{
DominantLayer = LayerIdx;
DominantWeight = INT_MAX;
}
}
else if (LayerWeight > DominantWeight && LayerWeight >= LayerMinimumWeight)
{
DominantLayer = LayerIdx;
DominantWeight = LayerWeight;
}
}
// this uses Quads as we don't want the duplicated vertices
const int32 CompVertX = CollisionSize.SubsectionSizeQuads * SubsectionX + VertX;
const int32 CompVertY = CollisionSize.SubsectionSizeQuads * SubsectionY + VertY;
// Set collision data
OutDominantLayerData[CompVertX + CompVertY * CollisionSize.SizeVerts] = DominantLayer;
}
}
}
}
}
void ULandscapeComponent::UpdateCollisionLayerData(const FColor* const* const WeightmapTextureMipData, const FColor* const* const SimpleCollisionWeightmapTextureMipData, int32 ComponentX1, int32 ComponentY1, int32 ComponentX2, int32 ComponentY2)
{
ULandscapeInfo* Info = GetLandscapeInfo();
ALandscapeProxy* Proxy = GetLandscapeProxy();
FIntPoint ComponentKey = GetSectionBase() / ComponentSizeQuads;
ULandscapeHeightfieldCollisionComponent* CollisionComp = CollisionComponent.Get();
if (CollisionComp)
{
if (!Proxy->HasLayersContent())
{
CollisionComp->Modify();
}
// Simple collision is not currently supported with mesh collision components
const bool bUsingSimpleCollision = (SimpleCollisionMipLevel > CollisionMipLevel && SimpleCollisionWeightmapTextureMipData && !XYOffsetmapTexture);
TArray<ULandscapeLayerInfoObject*> CandidateLayers;
TArray<uint8*> CandidateDataPtrs;
TArray<uint8*> SimpleCollisionDataPtrs;
bool bExistingLayerMismatch = false;
int32 DataLayerIdx = INDEX_NONE;
const TArray<FWeightmapLayerAllocationInfo>& ComponentWeightmapLayerAllocations = GetWeightmapLayerAllocations(false);
const TArray<UTexture2D*>& ComponentWeightmapsTexture = GetWeightmapTextures(false);
// Find the layers we're interested in
for (int32 AllocIdx = 0; AllocIdx < ComponentWeightmapLayerAllocations.Num(); AllocIdx++)
{
const FWeightmapLayerAllocationInfo& AllocInfo = ComponentWeightmapLayerAllocations[AllocIdx];
ULandscapeLayerInfoObject* LayerInfo = AllocInfo.LayerInfo;
if (LayerInfo == ALandscapeProxy::VisibilityLayer || LayerInfo != nullptr)
{
int32 Idx = CandidateLayers.Add(LayerInfo);
CandidateDataPtrs.Add(((uint8*)WeightmapTextureMipData[AllocInfo.WeightmapTextureIndex]) + ChannelOffsets[AllocInfo.WeightmapTextureChannel]);
if (bUsingSimpleCollision)
{
SimpleCollisionDataPtrs.Add(((uint8*)SimpleCollisionWeightmapTextureMipData[AllocInfo.WeightmapTextureIndex]) + ChannelOffsets[AllocInfo.WeightmapTextureChannel]);
}
// Check if we still match the collision component.
if (!CollisionComp->ComponentLayerInfos.IsValidIndex(Idx) || CollisionComp->ComponentLayerInfos[Idx] != LayerInfo)
{
bExistingLayerMismatch = true;
}
if (LayerInfo == ALandscapeProxy::VisibilityLayer)
{
DataLayerIdx = Idx;
bExistingLayerMismatch = true; // always rebuild whole component for hole
}
}
}
if (CandidateLayers.Num() == 0)
{
// No layers, so don't update any weights
CollisionComp->DominantLayerData.RemoveBulkData();
CollisionComp->ComponentLayerInfos.Empty();
}
else
{
uint8* DominantLayerData = (uint8*)CollisionComp->DominantLayerData.Lock(LOCK_READ_WRITE);
FCollisionSize CollisionSize = FCollisionSize::Create(NumSubsections, SubsectionSizeQuads, CollisionMipLevel);
FCollisionSize SimpleCollisionSize = FCollisionSize::CreateSimple(bUsingSimpleCollision, NumSubsections, SubsectionSizeQuads, SimpleCollisionMipLevel);
// If there's no existing data, or the layer allocations have changed, we need to update the data for the whole component.
if (bExistingLayerMismatch || CollisionComp->DominantLayerData.GetElementCount() == 0)
{
ComponentX1 = 0;
ComponentY1 = 0;
ComponentX2 = ComponentSizeQuads;
ComponentY2 = ComponentSizeQuads;
const int32 TotalCollisionSize = CollisionSize.SizeVertsSquare + SimpleCollisionSize.SizeVertsSquare;
DominantLayerData = (uint8*)CollisionComp->DominantLayerData.Realloc(TotalCollisionSize);
FMemory::Memzero(DominantLayerData, TotalCollisionSize);
CollisionComp->ComponentLayerInfos = CandidateLayers;
}
else
{
ComponentX1 = FMath::Clamp(ComponentX1, 0, ComponentSizeQuads);
ComponentY1 = FMath::Clamp(ComponentY1, 0, ComponentSizeQuads);
ComponentX2 = FMath::Clamp(ComponentX2, 0, ComponentSizeQuads);
ComponentY2 = FMath::Clamp(ComponentY2, 0, ComponentSizeQuads);
}
const int32 WeightmapSizeU = ComponentWeightmapsTexture[0]->Source.GetSizeX();
// gmartin: WeightmapScaleBias not handled?
UpdateDominantLayerBuffer(ComponentX1, ComponentY1, ComponentX2, ComponentY2, CollisionMipLevel, WeightmapSizeU, DataLayerIdx, CandidateDataPtrs, CollisionComp->ComponentLayerInfos, DominantLayerData);
if (bUsingSimpleCollision)
{
uint8* const SimpleCollisionHeightData = DominantLayerData + CollisionSize.SizeVertsSquare;
UpdateDominantLayerBuffer(ComponentX1, ComponentY1, ComponentX2, ComponentY2, SimpleCollisionMipLevel, WeightmapSizeU, DataLayerIdx, SimpleCollisionDataPtrs, CollisionComp->ComponentLayerInfos, SimpleCollisionHeightData);
}
CollisionComp->DominantLayerData.Unlock();
}
// Invalidate rendered physical materials
// These are updated in UpdatePhysicalMaterialTasks()
PhysicalMaterialHash = 0;
// We do not force an update of the physics data here. We don't need the layer information in the editor and it
// causes problems if we update it multiple times in a single frame.
}
}
void ULandscapeComponent::UpdateCollisionLayerData()
{
const TArray<UTexture2D*>& ComponentWeightmapsTexture = GetWeightmapTextures();
// Generate the dominant layer data
TArray<TArray64<uint8>> WeightmapTextureMipData;
TArray<FColor*> WeightmapTextureMipDataParam;
WeightmapTextureMipData.Reserve(ComponentWeightmapsTexture.Num());
WeightmapTextureMipDataParam.Reserve(ComponentWeightmapsTexture.Num());
for (int32 WeightmapIdx = 0; WeightmapIdx < ComponentWeightmapsTexture.Num(); ++WeightmapIdx)
{
TArray64<uint8>& MipData = WeightmapTextureMipData.AddDefaulted_GetRef();
ComponentWeightmapsTexture[WeightmapIdx]->Source.GetMipData(MipData, CollisionMipLevel);
WeightmapTextureMipDataParam.Add((FColor*)MipData.GetData());
}
TArray<TArray64<uint8>> SimpleCollisionWeightmapMipData;
TArray<FColor*> SimpleCollisionWeightmapMipDataParam;
if (SimpleCollisionMipLevel > CollisionMipLevel)
{
SimpleCollisionWeightmapMipData.Reserve(ComponentWeightmapsTexture.Num());
SimpleCollisionWeightmapMipDataParam.Reserve(ComponentWeightmapsTexture.Num());
for (int32 WeightmapIdx = 0; WeightmapIdx < ComponentWeightmapsTexture.Num(); ++WeightmapIdx)
{
TArray64<uint8>& MipData = SimpleCollisionWeightmapMipData.AddDefaulted_GetRef();
ComponentWeightmapsTexture[WeightmapIdx]->Source.GetMipData(MipData, SimpleCollisionMipLevel);
SimpleCollisionWeightmapMipDataParam.Add((FColor*)MipData.GetData());
}
}
UpdateCollisionLayerData(WeightmapTextureMipDataParam.GetData(), SimpleCollisionWeightmapMipDataParam.GetData());
}
uint32 ULandscapeComponent::CalculatePhysicalMaterialTaskHash() const
{
uint32 Hash = 0;
// Take into account any material changes.
UMaterialInterface* Material = GetLandscapeMaterial();
for (UMaterialInstanceConstant* MIC = Cast<UMaterialInstanceConstant>(Material); MIC; MIC = Cast<UMaterialInstanceConstant>(Material))
{
Hash = FCrc::TypeCrc32(MIC->ParameterStateId, Hash);
Material = MIC->Parent;
}
UMaterial* MaterialBase = Cast<UMaterial>(Material);
if (MaterialBase != nullptr)
{
Hash = FCrc::TypeCrc32(MaterialBase->StateId, Hash);
}
// We could take into account heightmap and weightmap changes here by adding to the hash.
// Instead we are resetting the stored hash in UpdateCollisionHeightData() and UpdateCollisionLayerData().
return Hash;
}
void ULandscapeComponent::UpdatePhysicalMaterialTasks()
{
uint32 Hash = CalculatePhysicalMaterialTaskHash();
if (PhysicalMaterialHash != Hash)
{
PhysicalMaterialTask.Init(this);
PhysicalMaterialHash = Hash;
}
if (PhysicalMaterialTask.IsValid())
{
if (PhysicalMaterialTask.IsComplete())
{
UpdateCollisionPhysicalMaterialData(PhysicalMaterialTask.GetResultMaterials(), PhysicalMaterialTask.GetResultIds());
PhysicalMaterialTask.Release();
// We do not force an update of the physics data here.
// We don't need the information immediately in the editor and update will happen on cook or PIE.
}
else
{
PhysicalMaterialTask.Tick();
}
}
}
void ULandscapeComponent::UpdateCollisionPhysicalMaterialData(TArray<UPhysicalMaterial*> const& InPhysicalMaterials, TArray<uint8> const& InMaterialIds)
{
// Copy the physical material array
CollisionComponent->PhysicalMaterialRenderObjects = InPhysicalMaterials;
// Copy the physical material IDs for both the full and (optional) simple collision.
const int32 SizeVerts = SubsectionSizeQuads * NumSubsections + 1;
check(InMaterialIds.Num() == SizeVerts * SizeVerts);
const int32 FullCollisionSizeVerts = CollisionComponent->CollisionSizeQuads + 1;
const int32 SimpleCollisionSizeVerts = CollisionComponent->SimpleCollisionSizeQuads > 0 ? CollisionComponent->SimpleCollisionSizeQuads + 1 : 0;
const int32 BulkDataSize = FullCollisionSizeVerts * FullCollisionSizeVerts + SimpleCollisionSizeVerts * SimpleCollisionSizeVerts;
void* Data = CollisionComponent->PhysicalMaterialRenderData.Lock(LOCK_READ_WRITE);
Data = CollisionComponent->PhysicalMaterialRenderData.Realloc(BulkDataSize);
uint8* WritePtr = (uint8*)Data;
const int32 CollisionSizes[2] = { FullCollisionSizeVerts, SimpleCollisionSizeVerts };
for (int32 i = 0; i < 2; ++i)
{
const int32 CollisionSizeVerts = CollisionSizes[i];
if (CollisionSizeVerts == SizeVerts)
{
FMemory::Memcpy(WritePtr, InMaterialIds.GetData(), SizeVerts * SizeVerts);
WritePtr += SizeVerts * SizeVerts;
}
else if (CollisionSizeVerts > 0)
{
const int32 StepSize = SizeVerts / CollisionSizeVerts;
check(CollisionSizeVerts * StepSize == SizeVerts);
for (int32 y = 0; y < SizeVerts; y += StepSize)
{
for (int32 x = 0; x < SizeVerts; x += StepSize)
{
*WritePtr++ = InMaterialIds[y * SizeVerts + x];
}
}
}
}
check(WritePtr - (uint8*)Data == BulkDataSize);
CollisionComponent->PhysicalMaterialRenderData.Unlock();
}
void ULandscapeComponent::GenerateHeightmapMips(TArray<FColor*>& HeightmapTextureMipData, int32 ComponentX1/*=0*/, int32 ComponentY1/*=0*/, int32 ComponentX2/*=MAX_int32*/, int32 ComponentY2/*=MAX_int32*/, FLandscapeTextureDataInfo* TextureDataInfo/*=nullptr*/)
{
bool EndX = false;
bool EndY = false;
if (ComponentX1 == MAX_int32)
{
EndX = true;
ComponentX1 = 0;
}
if (ComponentY1 == MAX_int32)
{
EndY = true;
ComponentY1 = 0;
}
if (ComponentX2 == MAX_int32)
{
ComponentX2 = ComponentSizeQuads;
}
if (ComponentY2 == MAX_int32)
{
ComponentY2 = ComponentSizeQuads;
}
int32 HeightmapSizeU = GetHeightmap()->Source.GetSizeX();
int32 HeightmapSizeV = GetHeightmap()->Source.GetSizeY();
int32 HeightmapOffsetX = FMath::RoundToInt(HeightmapScaleBias.Z * (float)HeightmapSizeU);
int32 HeightmapOffsetY = FMath::RoundToInt(HeightmapScaleBias.W * (float)HeightmapSizeV);
for (int32 SubsectionY = 0; SubsectionY < NumSubsections; SubsectionY++)
{
// Check if subsection is fully above or below the area we are interested in
if ((ComponentY2 < SubsectionSizeQuads*SubsectionY) || // above
(ComponentY1 > SubsectionSizeQuads*(SubsectionY + 1))) // below
{
continue;
}
for (int32 SubsectionX = 0; SubsectionX < NumSubsections; SubsectionX++)
{
// Check if subsection is fully to the left or right of the area we are interested in
if ((ComponentX2 < SubsectionSizeQuads*SubsectionX) || // left
(ComponentX1 > SubsectionSizeQuads*(SubsectionX + 1))) // right
{
continue;
}
// Area to update in previous mip level coords
int32 PrevMipSubX1 = ComponentX1 - SubsectionSizeQuads*SubsectionX;
int32 PrevMipSubY1 = ComponentY1 - SubsectionSizeQuads*SubsectionY;
int32 PrevMipSubX2 = ComponentX2 - SubsectionSizeQuads*SubsectionX;
int32 PrevMipSubY2 = ComponentY2 - SubsectionSizeQuads*SubsectionY;
int32 PrevMipSubsectionSizeQuads = SubsectionSizeQuads;
float InvPrevMipSubsectionSizeQuads = 1.0f / (float)SubsectionSizeQuads;
int32 PrevMipSizeU = HeightmapSizeU;
int32 PrevMipSizeV = HeightmapSizeV;
int32 PrevMipHeightmapOffsetX = HeightmapOffsetX;
int32 PrevMipHeightmapOffsetY = HeightmapOffsetY;
for (int32 Mip = 1; Mip < HeightmapTextureMipData.Num(); Mip++)
{
int32 MipSizeU = HeightmapSizeU >> Mip;
int32 MipSizeV = HeightmapSizeV >> Mip;
int32 MipSubsectionSizeQuads = ((SubsectionSizeQuads + 1) >> Mip) - 1;
float InvMipSubsectionSizeQuads = 1.0f / (float)MipSubsectionSizeQuads;
int32 MipHeightmapOffsetX = HeightmapOffsetX >> Mip;
int32 MipHeightmapOffsetY = HeightmapOffsetY >> Mip;
// Area to update in current mip level coords
int32 MipSubX1 = FMath::FloorToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubX1 * InvPrevMipSubsectionSizeQuads);
int32 MipSubY1 = FMath::FloorToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubY1 * InvPrevMipSubsectionSizeQuads);
int32 MipSubX2 = FMath::CeilToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubX2 * InvPrevMipSubsectionSizeQuads);
int32 MipSubY2 = FMath::CeilToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubY2 * InvPrevMipSubsectionSizeQuads);
// Clamp area to update
int32 VertX1 = FMath::Clamp<int32>(MipSubX1, 0, MipSubsectionSizeQuads);
int32 VertY1 = FMath::Clamp<int32>(MipSubY1, 0, MipSubsectionSizeQuads);
int32 VertX2 = FMath::Clamp<int32>(MipSubX2, 0, MipSubsectionSizeQuads);
int32 VertY2 = FMath::Clamp<int32>(MipSubY2, 0, MipSubsectionSizeQuads);
for (int32 VertY = VertY1; VertY <= VertY2; VertY++)
{
for (int32 VertX = VertX1; VertX <= VertX2; VertX++)
{
// Convert VertX/Y into previous mip's coords
float PrevMipVertX = (float)PrevMipSubsectionSizeQuads * (float)VertX * InvMipSubsectionSizeQuads;
float PrevMipVertY = (float)PrevMipSubsectionSizeQuads * (float)VertY * InvMipSubsectionSizeQuads;
#if 0
// Validate that the vertex we skip wouldn't use the updated data in the parent mip.
// Note this validation is doesn't do anything unless you change the VertY/VertX loops
// above to process all verts from 0 .. MipSubsectionSizeQuads.
if (VertX < VertX1 || VertX > VertX2)
{
check(FMath::CeilToInt(PrevMipVertX) < PrevMipSubX1 || FMath::FloorToInt(PrevMipVertX) > PrevMipSubX2);
continue;
}
if (VertY < VertY1 || VertY > VertY2)
{
check(FMath::CeilToInt(PrevMipVertY) < PrevMipSubY1 || FMath::FloorToInt(PrevMipVertY) > PrevMipSubY2);
continue;
}
#endif
// X/Y of the vertex we're looking indexed into the texture data
int32 TexX = (MipHeightmapOffsetX)+(MipSubsectionSizeQuads + 1) * SubsectionX + VertX;
int32 TexY = (MipHeightmapOffsetY)+(MipSubsectionSizeQuads + 1) * SubsectionY + VertY;
float fPrevMipTexX = (float)(PrevMipHeightmapOffsetX)+(float)((PrevMipSubsectionSizeQuads + 1) * SubsectionX) + PrevMipVertX;
float fPrevMipTexY = (float)(PrevMipHeightmapOffsetY)+(float)((PrevMipSubsectionSizeQuads + 1) * SubsectionY) + PrevMipVertY;
int32 PrevMipTexX = FMath::FloorToInt(fPrevMipTexX);
float fPrevMipTexFracX = FMath::Fractional(fPrevMipTexX);
int32 PrevMipTexY = FMath::FloorToInt(fPrevMipTexY);
float fPrevMipTexFracY = FMath::Fractional(fPrevMipTexY);
checkSlow(TexX >= 0 && TexX < MipSizeU);
checkSlow(TexY >= 0 && TexY < MipSizeV);
checkSlow(PrevMipTexX >= 0 && PrevMipTexX < PrevMipSizeU);
checkSlow(PrevMipTexY >= 0 && PrevMipTexY < PrevMipSizeV);
int32 PrevMipTexX1 = FMath::Min<int32>(PrevMipTexX + 1, PrevMipSizeU - 1);
int32 PrevMipTexY1 = FMath::Min<int32>(PrevMipTexY + 1, PrevMipSizeV - 1);
// Padding for missing data for MIP 0
if (Mip == 1)
{
if (EndX && SubsectionX == NumSubsections - 1 && VertX == VertX2)
{
for (int32 PaddingIdx = PrevMipTexX + PrevMipTexY * PrevMipSizeU; PaddingIdx + 1 < PrevMipTexY1 * PrevMipSizeU; ++PaddingIdx)
{
HeightmapTextureMipData[Mip - 1][PaddingIdx + 1] = HeightmapTextureMipData[Mip - 1][PaddingIdx];
}
}
if (EndY && SubsectionX == NumSubsections - 1 && SubsectionY == NumSubsections - 1 && VertY == VertY2 && VertX == VertX2)
{
for (int32 PaddingYIdx = PrevMipTexY; PaddingYIdx + 1 < PrevMipSizeV; ++PaddingYIdx)
{
for (int32 PaddingXIdx = 0; PaddingXIdx < PrevMipSizeU; ++PaddingXIdx)
{
HeightmapTextureMipData[Mip - 1][PaddingXIdx + (PaddingYIdx + 1) * PrevMipSizeU] = HeightmapTextureMipData[Mip - 1][PaddingXIdx + PaddingYIdx * PrevMipSizeU];
}
}
}
}
FColor* TexData = &(HeightmapTextureMipData[Mip])[TexX + TexY * MipSizeU];
FColor *PreMipTexData00 = &(HeightmapTextureMipData[Mip - 1])[PrevMipTexX + PrevMipTexY * PrevMipSizeU];
FColor *PreMipTexData01 = &(HeightmapTextureMipData[Mip - 1])[PrevMipTexX + PrevMipTexY1 * PrevMipSizeU];
FColor *PreMipTexData10 = &(HeightmapTextureMipData[Mip - 1])[PrevMipTexX1 + PrevMipTexY * PrevMipSizeU];
FColor *PreMipTexData11 = &(HeightmapTextureMipData[Mip - 1])[PrevMipTexX1 + PrevMipTexY1 * PrevMipSizeU];
// Lerp height values
uint16 PrevMipHeightValue00 = PreMipTexData00->R << 8 | PreMipTexData00->G;
uint16 PrevMipHeightValue01 = PreMipTexData01->R << 8 | PreMipTexData01->G;
uint16 PrevMipHeightValue10 = PreMipTexData10->R << 8 | PreMipTexData10->G;
uint16 PrevMipHeightValue11 = PreMipTexData11->R << 8 | PreMipTexData11->G;
uint16 HeightValue = FMath::RoundToInt(
FMath::Lerp(
FMath::Lerp((float)PrevMipHeightValue00, (float)PrevMipHeightValue10, fPrevMipTexFracX),
FMath::Lerp((float)PrevMipHeightValue01, (float)PrevMipHeightValue11, fPrevMipTexFracX),
fPrevMipTexFracY));
TexData->R = HeightValue >> 8;
TexData->G = HeightValue & 255;
// Lerp tangents
TexData->B = FMath::RoundToInt(
FMath::Lerp(
FMath::Lerp((float)PreMipTexData00->B, (float)PreMipTexData10->B, fPrevMipTexFracX),
FMath::Lerp((float)PreMipTexData01->B, (float)PreMipTexData11->B, fPrevMipTexFracX),
fPrevMipTexFracY));
TexData->A = FMath::RoundToInt(
FMath::Lerp(
FMath::Lerp((float)PreMipTexData00->A, (float)PreMipTexData10->A, fPrevMipTexFracX),
FMath::Lerp((float)PreMipTexData01->A, (float)PreMipTexData11->A, fPrevMipTexFracX),
fPrevMipTexFracY));
// Padding for missing data
if (EndX && SubsectionX == NumSubsections - 1 && VertX == VertX2)
{
for (int32 PaddingIdx = TexX + TexY * MipSizeU; PaddingIdx + 1 < (TexY + 1) * MipSizeU; ++PaddingIdx)
{
HeightmapTextureMipData[Mip][PaddingIdx + 1] = HeightmapTextureMipData[Mip][PaddingIdx];
}
}
if (EndY && SubsectionX == NumSubsections - 1 && SubsectionY == NumSubsections - 1 && VertY == VertY2 && VertX == VertX2)
{
for (int32 PaddingYIdx = TexY; PaddingYIdx + 1 < MipSizeV; ++PaddingYIdx)
{
for (int32 PaddingXIdx = 0; PaddingXIdx < MipSizeU; ++PaddingXIdx)
{
HeightmapTextureMipData[Mip][PaddingXIdx + (PaddingYIdx + 1) * MipSizeU] = HeightmapTextureMipData[Mip][PaddingXIdx + PaddingYIdx * MipSizeU];
}
}
}
}
}
// Record the areas we updated
if (TextureDataInfo)
{
int32 TexX1 = (MipHeightmapOffsetX)+(MipSubsectionSizeQuads + 1) * SubsectionX + VertX1;
int32 TexY1 = (MipHeightmapOffsetY)+(MipSubsectionSizeQuads + 1) * SubsectionY + VertY1;
int32 TexX2 = (MipHeightmapOffsetX)+(MipSubsectionSizeQuads + 1) * SubsectionX + VertX2;
int32 TexY2 = (MipHeightmapOffsetY)+(MipSubsectionSizeQuads + 1) * SubsectionY + VertY2;
TextureDataInfo->AddMipUpdateRegion(Mip, TexX1, TexY1, TexX2, TexY2);
}
// Copy current mip values to prev as we move to the next mip.
PrevMipSubsectionSizeQuads = MipSubsectionSizeQuads;
InvPrevMipSubsectionSizeQuads = InvMipSubsectionSizeQuads;
PrevMipSizeU = MipSizeU;
PrevMipSizeV = MipSizeV;
PrevMipHeightmapOffsetX = MipHeightmapOffsetX;
PrevMipHeightmapOffsetY = MipHeightmapOffsetY;
// Use this mip's area as we move to the next mip
PrevMipSubX1 = MipSubX1;
PrevMipSubY1 = MipSubY1;
PrevMipSubX2 = MipSubX2;
PrevMipSubY2 = MipSubY2;
}
}
}
}
void ULandscapeComponent::CreateEmptyTextureMips(UTexture2D* Texture, bool bClear /*= false*/)
{
ETextureSourceFormat Format = Texture->Source.GetFormat();
int32 SizeU = Texture->Source.GetSizeX();
int32 SizeV = Texture->Source.GetSizeY();
if (bClear)
{
Texture->Source.Init2DWithMipChain(SizeU, SizeV, Format);
int32 NumMips = Texture->Source.GetNumMips();
for (int32 MipIndex = 0; MipIndex < NumMips; ++MipIndex)
{
uint8* MipData = Texture->Source.LockMip(MipIndex);
FMemory::Memzero(MipData, Texture->Source.CalcMipSize(MipIndex));
Texture->Source.UnlockMip(MipIndex);
}
}
else
{
TArray64<uint8> TopMipData;
Texture->Source.GetMipData(TopMipData, 0);
Texture->Source.Init2DWithMipChain(SizeU, SizeV, Format);
int32 NumMips = Texture->Source.GetNumMips();
uint8* MipData = Texture->Source.LockMip(0);
FMemory::Memcpy(MipData, TopMipData.GetData(), TopMipData.Num());
Texture->Source.UnlockMip(0);
}
}
template<typename DataType>
void ULandscapeComponent::GenerateMipsTempl(int32 InNumSubsections, int32 InSubsectionSizeQuads, UTexture2D* Texture, DataType* BaseMipData)
{
// Stores pointers to the locked mip data
TArray<DataType*> MipData;
MipData.Add(BaseMipData);
for (int32 MipIndex = 1; MipIndex < Texture->Source.GetNumMips(); ++MipIndex)
{
MipData.Add((DataType*)Texture->Source.LockMip(MipIndex));
}
// Update the newly created mips
UpdateMipsTempl<DataType>(InNumSubsections, InSubsectionSizeQuads, Texture, MipData);
// Unlock all the new mips, but not the base mip's data
for (int32 i = 1; i < MipData.Num(); i++)
{
Texture->Source.UnlockMip(i);
}
}
void ULandscapeComponent::GenerateWeightmapMips(int32 InNumSubsections, int32 InSubsectionSizeQuads, UTexture2D* WeightmapTexture, FColor* BaseMipData)
{
GenerateMipsTempl<FColor>(InNumSubsections, InSubsectionSizeQuads, WeightmapTexture, BaseMipData);
}
namespace
{
template<typename DataType>
void BiLerpTextureData(DataType* Output, const DataType* Data00, const DataType* Data10, const DataType* Data01, const DataType* Data11, float FracX, float FracY)
{
*Output = FMath::RoundToInt(
FMath::Lerp(
FMath::Lerp((float)*Data00, (float)*Data10, FracX),
FMath::Lerp((float)*Data01, (float)*Data11, FracX),
FracY));
}
template<>
void BiLerpTextureData(FColor* Output, const FColor* Data00, const FColor* Data10, const FColor* Data01, const FColor* Data11, float FracX, float FracY)
{
Output->R = FMath::RoundToInt(
FMath::Lerp(
FMath::Lerp((float)Data00->R, (float)Data10->R, FracX),
FMath::Lerp((float)Data01->R, (float)Data11->R, FracX),
FracY));
Output->G = FMath::RoundToInt(
FMath::Lerp(
FMath::Lerp((float)Data00->G, (float)Data10->G, FracX),
FMath::Lerp((float)Data01->G, (float)Data11->G, FracX),
FracY));
Output->B = FMath::RoundToInt(
FMath::Lerp(
FMath::Lerp((float)Data00->B, (float)Data10->B, FracX),
FMath::Lerp((float)Data01->B, (float)Data11->B, FracX),
FracY));
Output->A = FMath::RoundToInt(
FMath::Lerp(
FMath::Lerp((float)Data00->A, (float)Data10->A, FracX),
FMath::Lerp((float)Data01->A, (float)Data11->A, FracX),
FracY));
}
template<typename DataType>
void AverageTexData(DataType* Output, const DataType* Data00, const DataType* Data10, const DataType* Data01, const DataType* Data11)
{
*Output = (((int32)(*Data00) + (int32)(*Data10) + (int32)(*Data01) + (int32)(*Data11)) >> 2);
}
template<>
void AverageTexData(FColor* Output, const FColor* Data00, const FColor* Data10, const FColor* Data01, const FColor* Data11)
{
Output->R = (((int32)Data00->R + (int32)Data10->R + (int32)Data01->R + (int32)Data11->R) >> 2);
Output->G = (((int32)Data00->G + (int32)Data10->G + (int32)Data01->G + (int32)Data11->G) >> 2);
Output->B = (((int32)Data00->B + (int32)Data10->B + (int32)Data01->B + (int32)Data11->B) >> 2);
Output->A = (((int32)Data00->A + (int32)Data10->A + (int32)Data01->A + (int32)Data11->A) >> 2);
}
};
template<typename DataType>
void ULandscapeComponent::UpdateMipsTempl(int32 InNumSubsections, int32 InSubsectionSizeQuads, UTexture2D* Texture, TArray<DataType*>& TextureMipData, int32 ComponentX1/*=0*/, int32 ComponentY1/*=0*/, int32 ComponentX2/*=MAX_int32*/, int32 ComponentY2/*=MAX_int32*/, struct FLandscapeTextureDataInfo* TextureDataInfo/*=nullptr*/)
{
int32 WeightmapSizeU = Texture->Source.GetSizeX();
int32 WeightmapSizeV = Texture->Source.GetSizeY();
// Find the maximum mip where each texel's data comes from just one subsection.
int32 MaxWholeSubsectionMip = FMath::FloorLog2(InSubsectionSizeQuads + 1) - 1;
// Update the mip where each texel's data comes from just one subsection.
for (int32 SubsectionY = 0; SubsectionY < InNumSubsections; SubsectionY++)
{
// Check if subsection is fully above or below the area we are interested in
if ((ComponentY2 < InSubsectionSizeQuads*SubsectionY) || // above
(ComponentY1 > InSubsectionSizeQuads*(SubsectionY + 1))) // below
{
continue;
}
for (int32 SubsectionX = 0; SubsectionX < InNumSubsections; SubsectionX++)
{
// Check if subsection is fully to the left or right of the area we are interested in
if ((ComponentX2 < InSubsectionSizeQuads*SubsectionX) || // left
(ComponentX1 > InSubsectionSizeQuads*(SubsectionX + 1))) // right
{
continue;
}
// Area to update in previous mip level coords
int32 PrevMipSubX1 = ComponentX1 - InSubsectionSizeQuads*SubsectionX;
int32 PrevMipSubY1 = ComponentY1 - InSubsectionSizeQuads*SubsectionY;
int32 PrevMipSubX2 = ComponentX2 - InSubsectionSizeQuads*SubsectionX;
int32 PrevMipSubY2 = ComponentY2 - InSubsectionSizeQuads*SubsectionY;
int32 PrevMipSubsectionSizeQuads = InSubsectionSizeQuads;
float InvPrevMipSubsectionSizeQuads = 1.0f / (float)InSubsectionSizeQuads;
int32 PrevMipSizeU = WeightmapSizeU;
int32 PrevMipSizeV = WeightmapSizeV;
for (int32 Mip = 1; Mip <= MaxWholeSubsectionMip; Mip++)
{
int32 MipSizeU = WeightmapSizeU >> Mip;
int32 MipSizeV = WeightmapSizeV >> Mip;
int32 MipSubsectionSizeQuads = ((InSubsectionSizeQuads + 1) >> Mip) - 1;
float InvMipSubsectionSizeQuads = 1.0f / (float)MipSubsectionSizeQuads;
// Area to update in current mip level coords
int32 MipSubX1 = FMath::FloorToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubX1 * InvPrevMipSubsectionSizeQuads);
int32 MipSubY1 = FMath::FloorToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubY1 * InvPrevMipSubsectionSizeQuads);
int32 MipSubX2 = FMath::CeilToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubX2 * InvPrevMipSubsectionSizeQuads);
int32 MipSubY2 = FMath::CeilToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubY2 * InvPrevMipSubsectionSizeQuads);
// Clamp area to update
int32 VertX1 = FMath::Clamp<int32>(MipSubX1, 0, MipSubsectionSizeQuads);
int32 VertY1 = FMath::Clamp<int32>(MipSubY1, 0, MipSubsectionSizeQuads);
int32 VertX2 = FMath::Clamp<int32>(MipSubX2, 0, MipSubsectionSizeQuads);
int32 VertY2 = FMath::Clamp<int32>(MipSubY2, 0, MipSubsectionSizeQuads);
for (int32 VertY = VertY1; VertY <= VertY2; VertY++)
{
for (int32 VertX = VertX1; VertX <= VertX2; VertX++)
{
// Convert VertX/Y into previous mip's coords
float PrevMipVertX = (float)PrevMipSubsectionSizeQuads * (float)VertX * InvMipSubsectionSizeQuads;
float PrevMipVertY = (float)PrevMipSubsectionSizeQuads * (float)VertY * InvMipSubsectionSizeQuads;
// X/Y of the vertex we're looking indexed into the texture data
int32 TexX = (MipSubsectionSizeQuads + 1) * SubsectionX + VertX;
int32 TexY = (MipSubsectionSizeQuads + 1) * SubsectionY + VertY;
float fPrevMipTexX = (float)((PrevMipSubsectionSizeQuads + 1) * SubsectionX) + PrevMipVertX;
float fPrevMipTexY = (float)((PrevMipSubsectionSizeQuads + 1) * SubsectionY) + PrevMipVertY;
int32 PrevMipTexX = FMath::FloorToInt(fPrevMipTexX);
float fPrevMipTexFracX = FMath::Fractional(fPrevMipTexX);
int32 PrevMipTexY = FMath::FloorToInt(fPrevMipTexY);
float fPrevMipTexFracY = FMath::Fractional(fPrevMipTexY);
check(TexX >= 0 && TexX < MipSizeU);
check(TexY >= 0 && TexY < MipSizeV);
check(PrevMipTexX >= 0 && PrevMipTexX < PrevMipSizeU);
check(PrevMipTexY >= 0 && PrevMipTexY < PrevMipSizeV);
int32 PrevMipTexX1 = FMath::Min<int32>(PrevMipTexX + 1, PrevMipSizeU - 1);
int32 PrevMipTexY1 = FMath::Min<int32>(PrevMipTexY + 1, PrevMipSizeV - 1);
DataType* TexData = &(TextureMipData[Mip])[TexX + TexY * MipSizeU];
DataType *PreMipTexData00 = &(TextureMipData[Mip - 1])[PrevMipTexX + PrevMipTexY * PrevMipSizeU];
DataType *PreMipTexData01 = &(TextureMipData[Mip - 1])[PrevMipTexX + PrevMipTexY1 * PrevMipSizeU];
DataType *PreMipTexData10 = &(TextureMipData[Mip - 1])[PrevMipTexX1 + PrevMipTexY * PrevMipSizeU];
DataType *PreMipTexData11 = &(TextureMipData[Mip - 1])[PrevMipTexX1 + PrevMipTexY1 * PrevMipSizeU];
// Lerp weightmap data
BiLerpTextureData<DataType>(TexData, PreMipTexData00, PreMipTexData10, PreMipTexData01, PreMipTexData11, fPrevMipTexFracX, fPrevMipTexFracY);
}
}
// Record the areas we updated
if (TextureDataInfo)
{
int32 TexX1 = (MipSubsectionSizeQuads + 1) * SubsectionX + VertX1;
int32 TexY1 = (MipSubsectionSizeQuads + 1) * SubsectionY + VertY1;
int32 TexX2 = (MipSubsectionSizeQuads + 1) * SubsectionX + VertX2;
int32 TexY2 = (MipSubsectionSizeQuads + 1) * SubsectionY + VertY2;
TextureDataInfo->AddMipUpdateRegion(Mip, TexX1, TexY1, TexX2, TexY2);
}
// Copy current mip values to prev as we move to the next mip.
PrevMipSubsectionSizeQuads = MipSubsectionSizeQuads;
InvPrevMipSubsectionSizeQuads = InvMipSubsectionSizeQuads;
PrevMipSizeU = MipSizeU;
PrevMipSizeV = MipSizeV;
// Use this mip's area as we move to the next mip
PrevMipSubX1 = MipSubX1;
PrevMipSubY1 = MipSubY1;
PrevMipSubX2 = MipSubX2;
PrevMipSubY2 = MipSubY2;
}
}
}
// Handle mips that have texels from multiple subsections
// not valid weight data, so just average the texels of the previous mip.
for (int32 Mip = MaxWholeSubsectionMip + 1;; ++Mip)
{
int32 MipSubsectionSizeQuads = ((InSubsectionSizeQuads + 1) >> Mip) - 1;
checkSlow(MipSubsectionSizeQuads <= 0);
int32 MipSizeU = FMath::Max<int32>(WeightmapSizeU >> Mip, 1);
int32 MipSizeV = FMath::Max<int32>(WeightmapSizeV >> Mip, 1);
int32 PrevMipSizeU = FMath::Max<int32>(WeightmapSizeU >> (Mip - 1), 1);
int32 PrevMipSizeV = FMath::Max<int32>(WeightmapSizeV >> (Mip - 1), 1);
for (int32 Y = 0; Y < MipSizeV; Y++)
{
for (int32 X = 0; X < MipSizeU; X++)
{
DataType* TexData = &(TextureMipData[Mip])[X + Y * MipSizeU];
DataType *PreMipTexData00 = &(TextureMipData[Mip - 1])[(X * 2 + 0) + (Y * 2 + 0) * PrevMipSizeU];
DataType *PreMipTexData01 = &(TextureMipData[Mip - 1])[(X * 2 + 0) + (Y * 2 + 1) * PrevMipSizeU];
DataType *PreMipTexData10 = &(TextureMipData[Mip - 1])[(X * 2 + 1) + (Y * 2 + 0) * PrevMipSizeU];
DataType *PreMipTexData11 = &(TextureMipData[Mip - 1])[(X * 2 + 1) + (Y * 2 + 1) * PrevMipSizeU];
AverageTexData<DataType>(TexData, PreMipTexData00, PreMipTexData10, PreMipTexData01, PreMipTexData11);
}
}
if (TextureDataInfo)
{
// These mip sizes are small enough that we may as well just update the whole mip.
TextureDataInfo->AddMipUpdateRegion(Mip, 0, 0, MipSizeU - 1, MipSizeV - 1);
}
if (MipSizeU == 1 && MipSizeV == 1)
{
break;
}
}
}
void ULandscapeComponent::UpdateWeightmapMips(int32 InNumSubsections, int32 InSubsectionSizeQuads, UTexture2D* WeightmapTexture, TArray<FColor*>& WeightmapTextureMipData, int32 ComponentX1/*=0*/, int32 ComponentY1/*=0*/, int32 ComponentX2/*=MAX_int32*/, int32 ComponentY2/*=MAX_int32*/, struct FLandscapeTextureDataInfo* TextureDataInfo/*=nullptr*/)
{
UpdateMipsTempl<FColor>(InNumSubsections, InSubsectionSizeQuads, WeightmapTexture, WeightmapTextureMipData, ComponentX1, ComponentY1, ComponentX2, ComponentY2, TextureDataInfo);
}
void ULandscapeComponent::UpdateDataMips(int32 InNumSubsections, int32 InSubsectionSizeQuads, UTexture2D* Texture, TArray<uint8*>& TextureMipData, int32 ComponentX1/*=0*/, int32 ComponentY1/*=0*/, int32 ComponentX2/*=MAX_int32*/, int32 ComponentY2/*=MAX_int32*/, struct FLandscapeTextureDataInfo* TextureDataInfo/*=nullptr*/)
{
UpdateMipsTempl<uint8>(InNumSubsections, InSubsectionSizeQuads, Texture, TextureMipData, ComponentX1, ComponentY1, ComponentX2, ComponentY2, TextureDataInfo);
}
float ULandscapeComponent::GetLayerWeightAtLocation(const FVector& InLocation, ULandscapeLayerInfoObject* LayerInfo, TArray<uint8>* LayerCache, bool bUseEditingWeightmap)
{
// Allocate and discard locally if no external cache is passed in.
TArray<uint8> LocalCache;
if (LayerCache == nullptr)
{
LayerCache = &LocalCache;
}
// Fill the cache if necessary
if (LayerCache->Num() == 0)
{
FLandscapeComponentDataInterface CDI(this);
if (!CDI.GetWeightmapTextureData(LayerInfo, *LayerCache, bUseEditingWeightmap))
{
// no data for this layer for this component.
return 0.0f;
}
}
// Find location
const FVector TestLocation = GetComponentToWorld().InverseTransformPosition(InLocation);
// Abort if the test location is not on this component
if (TestLocation.X < 0 || TestLocation.Y < 0 || TestLocation.X > ComponentSizeQuads || TestLocation.Y > ComponentSizeQuads)
{
return 0.0f;
}
// Find data
int32 X1 = FMath::FloorToInt(TestLocation.X);
int32 Y1 = FMath::FloorToInt(TestLocation.Y);
int32 X2 = FMath::CeilToInt(TestLocation.X);
int32 Y2 = FMath::CeilToInt(TestLocation.Y);
int32 Stride = (SubsectionSizeQuads + 1) * NumSubsections;
// Min is to prevent the sampling of the final column from overflowing
int32 IdxX1 = FMath::Min<int32>(((X1 / SubsectionSizeQuads) * (SubsectionSizeQuads + 1)) + (X1 % SubsectionSizeQuads), Stride - 1);
int32 IdxY1 = FMath::Min<int32>(((Y1 / SubsectionSizeQuads) * (SubsectionSizeQuads + 1)) + (Y1 % SubsectionSizeQuads), Stride - 1);
int32 IdxX2 = FMath::Min<int32>(((X2 / SubsectionSizeQuads) * (SubsectionSizeQuads + 1)) + (X2 % SubsectionSizeQuads), Stride - 1);
int32 IdxY2 = FMath::Min<int32>(((Y2 / SubsectionSizeQuads) * (SubsectionSizeQuads + 1)) + (Y2 % SubsectionSizeQuads), Stride - 1);
// sample
float Sample11 = (float)((*LayerCache)[IdxX1 + Stride * IdxY1]) / 255.0f;
float Sample21 = (float)((*LayerCache)[IdxX2 + Stride * IdxY1]) / 255.0f;
float Sample12 = (float)((*LayerCache)[IdxX1 + Stride * IdxY2]) / 255.0f;
float Sample22 = (float)((*LayerCache)[IdxX2 + Stride * IdxY2]) / 255.0f;
float LerpX = FMath::Fractional(TestLocation.X);
float LerpY = FMath::Fractional(TestLocation.Y);
// Bilinear interpolate
return FMath::Lerp(
FMath::Lerp(Sample11, Sample21, LerpX),
FMath::Lerp(Sample12, Sample22, LerpX),
LerpY);
}
void ULandscapeComponent::GetComponentExtent(int32& MinX, int32& MinY, int32& MaxX, int32& MaxY) const
{
MinX = FMath::Min(SectionBaseX, MinX);
MinY = FMath::Min(SectionBaseY, MinY);
MaxX = FMath::Max(SectionBaseX + ComponentSizeQuads, MaxX);
MaxY = FMath::Max(SectionBaseY + ComponentSizeQuads, MaxY);
}
FIntRect ULandscapeComponent::GetComponentExtent() const
{
int32 MinX = MAX_int32, MinY = MAX_int32, MaxX = MIN_int32, MaxY = MIN_int32;
GetComponentExtent(MinX, MinY, MaxX, MaxY);
return FIntRect(MinX, MinY, MaxX, MaxY);
}
//
// ALandscape
//
bool ULandscapeInfo::SupportsLandscapeEditing() const
{
bool bSupportsEditing = true;
ForAllLandscapeProxies([&bSupportsEditing](ALandscapeProxy* Proxy)
{
if(Proxy->GetOutermost()->bIsCookedForEditor)
{
bSupportsEditing = false;
}
});
return bSupportsEditing;
}
bool ULandscapeInfo::AreAllComponentsRegistered() const
{
const TArray<ALandscapeProxy*>& LandscapeProxies = ALandscapeProxy::GetLandscapeProxies();
for(ALandscapeProxy* LandscapeProxy : LandscapeProxies)
{
if (!IsValid(LandscapeProxy))
{
continue;
}
if (LandscapeProxy->GetLandscapeGuid() == LandscapeGuid)
{
for (ULandscapeComponent* LandscapeComponent : LandscapeProxy->LandscapeComponents)
{
if (LandscapeComponent && !LandscapeComponent->IsRegistered())
{
return false;
}
}
}
}
for (TScriptInterface<ILandscapeSplineInterface> SplineOwner : SplineActors)
{
if (!SplineOwner.GetObject() || !IsValidChecked(SplineOwner.GetObject()))
{
continue;
}
if (SplineOwner->GetLandscapeGuid() == LandscapeGuid)
{
if (SplineOwner->GetSplinesComponent() && !SplineOwner->GetSplinesComponent()->IsRegistered())
{
return false;
}
}
}
return true;
}
#define MAX_LANDSCAPE_SUBSECTIONS 2
bool ULandscapeInfo::HasUnloadedComponentsInRegion(int32 X1, int32 Y1, int32 X2, int32 Y2) const
{
bool bResult = false;
if (LandscapeActor)
{
UWorld* World = LandscapeActor->GetWorld();
int32 ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2;
ALandscape::CalcComponentIndicesOverlap(X1, Y1, X2, Y2, ComponentSizeQuads, ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2);
const UActorPartitionSubsystem::FCellCoord MinCoord = UActorPartitionSubsystem::FCellCoord::GetCellCoord(FIntPoint(ComponentIndexX1 * ComponentSizeQuads, ComponentIndexY1 * ComponentSizeQuads), World->PersistentLevel, LandscapeActor->GridSize);
const UActorPartitionSubsystem::FCellCoord MaxCoord = UActorPartitionSubsystem::FCellCoord::GetCellCoord(FIntPoint(ComponentIndexX2 * ComponentSizeQuads, ComponentIndexY2 * ComponentSizeQuads), World->PersistentLevel, LandscapeActor->GridSize);
if (UWorldPartition* WorldPartition = World->GetWorldPartition())
{
FWorldPartitionHelpers::ForEachActorDesc<ALandscapeProxy>(WorldPartition, [this, World, &MinCoord, &MaxCoord, &bResult](const FWorldPartitionActorDesc* ActorDesc)
{
FLandscapeActorDesc* LandscapeActorDesc = (FLandscapeActorDesc*)ActorDesc;
if (LandscapeActorDesc->GridGuid == LandscapeGuid)
{
const UActorPartitionSubsystem::FCellCoord ActorCoord(LandscapeActorDesc->GridIndexX, LandscapeActorDesc->GridIndexY, LandscapeActorDesc->GridIndexZ, World->PersistentLevel);
if (ActorCoord.X >= MinCoord.X && ActorCoord.Y >= MinCoord.Y && ActorCoord.X <= MaxCoord.X && ActorCoord.Y <= MaxCoord.Y)
{
if (!LandscapeActorDesc->IsLoaded())
{
bResult = true;
return false;
}
}
}
return true;
});
}
}
return bResult;
}
void ULandscapeInfo::GetComponentsInRegion(int32 X1, int32 Y1, int32 X2, int32 Y2, TSet<ULandscapeComponent*>& OutComponents, bool bOverlap) const
{
// Find component range for this block of data
// X2/Y2 Coordinates are "inclusive" max values
int32 ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2;
if (bOverlap)
{
ALandscape::CalcComponentIndicesOverlap(X1, Y1, X2, Y2, ComponentSizeQuads, ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2);
}
else
{
ALandscape::CalcComponentIndicesNoOverlap(X1, Y1, X2, Y2, ComponentSizeQuads, ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2);
}
for (int32 ComponentIndexY = ComponentIndexY1; ComponentIndexY <= ComponentIndexY2; ComponentIndexY++)
{
for (int32 ComponentIndexX = ComponentIndexX1; ComponentIndexX <= ComponentIndexX2; ComponentIndexX++)
{
ULandscapeComponent* Component = XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX, ComponentIndexY));
if (Component && !FLevelUtils::IsLevelLocked(Component->GetLandscapeProxy()->GetLevel()) && FLevelUtils::IsLevelVisible(Component->GetLandscapeProxy()->GetLevel()))
{
OutComponents.Add(Component);
}
}
}
}
// A struct to remember where we have spare texture channels.
struct FWeightmapTextureAllocation
{
int32 X;
int32 Y;
int32 ChannelsInUse;
UTexture2D* Texture;
FColor* TextureData;
FWeightmapTextureAllocation(int32 InX, int32 InY, int32 InChannels, UTexture2D* InTexture, FColor* InTextureData)
: X(InX)
, Y(InY)
, ChannelsInUse(InChannels)
, Texture(InTexture)
, TextureData(InTextureData)
{}
};
// A struct to hold the info about each texture chunk of the total heightmap
struct FHeightmapInfo
{
int32 HeightmapSizeU;
int32 HeightmapSizeV;
UTexture2D* HeightmapTexture;
TArray<FColor*> HeightmapTextureMipData;
};
const TArray<FName>& ALandscapeProxy::GetLayersFromMaterial(UMaterialInterface* MaterialInterface)
{
if (MaterialInterface)
{
const FMaterialCachedExpressionData& CachedExpressionData = MaterialInterface->GetCachedExpressionData();
if (CachedExpressionData.EditorOnlyData)
{
return CachedExpressionData.EditorOnlyData->LandscapeLayerNames;
}
}
return FMaterialCachedExpressionEditorOnlyData::EmptyData.LandscapeLayerNames;
}
const TArray<FName>& ALandscapeProxy::GetLayersFromMaterial() const
{
return GetLayersFromMaterial(LandscapeMaterial);
}
ULandscapeLayerInfoObject* ALandscapeProxy::CreateLayerInfo(const TCHAR* InLayerName, const ULevel* InLevel, const ULandscapeLayerInfoObject* InTemplate)
{
FName LayerObjectName;
FString PackageName = UE::Landscape::GetLayerInfoObjectPackageName(InLevel, InLayerName, LayerObjectName);
UPackage* Package = CreatePackage(*PackageName);
ULandscapeLayerInfoObject* LayerInfo = nullptr;
check(Package != nullptr);
if (InTemplate != nullptr)
{
LayerInfo = DuplicateObject<ULandscapeLayerInfoObject>(InTemplate, Package, LayerObjectName);
}
else
{
LayerInfo = NewObject<ULandscapeLayerInfoObject>(Package, LayerObjectName, RF_Public | RF_Standalone | RF_Transactional);
}
check(LayerInfo != nullptr);
LayerInfo->LayerName = InLayerName;
FAssetRegistryModule::AssetCreated(LayerInfo);
LayerInfo->MarkPackageDirty();
return LayerInfo;
}
ULandscapeLayerInfoObject* ALandscapeProxy::CreateLayerInfo(const TCHAR* InLayerName, const ULandscapeLayerInfoObject* InTemplate)
{
ULandscapeLayerInfoObject* LayerInfo = ALandscapeProxy::CreateLayerInfo(InLayerName, GetLevel(), InTemplate);
check(LayerInfo);
ULandscapeInfo* LandscapeInfo = GetLandscapeInfo();
if (LandscapeInfo)
{
int32 Index = LandscapeInfo->GetLayerInfoIndex(InLayerName, this);
if (Index == INDEX_NONE)
{
LandscapeInfo->Layers.Add(FLandscapeInfoLayerSettings(LayerInfo, this));
}
else
{
LandscapeInfo->Layers[Index].LayerInfoObj = LayerInfo;
}
}
return LayerInfo;
}
#define HEIGHTDATA(X,Y) (HeightData[ FMath::Clamp<int32>(Y,0,VertsY) * VertsX + FMath::Clamp<int32>(X,0,VertsX) ])
ENGINE_API extern bool GDisableAutomaticTextureMaterialUpdateDependencies;
LANDSCAPE_API void ALandscapeProxy::Import(const FGuid& InGuid, int32 InMinX, int32 InMinY, int32 InMaxX, int32 InMaxY, int32 InNumSubsections, int32 InSubsectionSizeQuads, const TMap<FGuid, TArray<uint16>>& InImportHeightData,
const TCHAR* const InHeightmapFileName, const TMap<FGuid, TArray<FLandscapeImportLayerInfo>>& InImportMaterialLayerInfos, ELandscapeImportAlphamapType InImportMaterialLayerType, const TArray<FLandscapeLayer>* InImportLayers)
{
check(InGuid.IsValid());
check(InImportHeightData.Num() == InImportMaterialLayerInfos.Num());
check(CanHaveLayersContent() || InImportLayers == nullptr);
FScopedSlowTask SlowTask(2, LOCTEXT("BeingImportingLandscapeTask", "Importing Landscape"));
SlowTask.MakeDialog();
SlowTask.EnterProgressFrame(1.0f);
const int32 VertsX = InMaxX - InMinX + 1;
const int32 VertsY = InMaxY - InMinY + 1;
ComponentSizeQuads = InNumSubsections * InSubsectionSizeQuads;
NumSubsections = InNumSubsections;
SubsectionSizeQuads = InSubsectionSizeQuads;
LandscapeGuid = InGuid;
Modify();
const int32 NumPatchesX = (VertsX - 1);
const int32 NumPatchesY = (VertsY - 1);
const int32 NumComponentsX = NumPatchesX / ComponentSizeQuads;
const int32 NumComponentsY = NumPatchesY / ComponentSizeQuads;
// currently only support importing into a new/blank landscape actor/proxy
check(LandscapeComponents.Num() == 0);
LandscapeComponents.Empty(NumComponentsX * NumComponentsY);
for (int32 Y = 0; Y < NumComponentsY; Y++)
{
for (int32 X = 0; X < NumComponentsX; X++)
{
const int32 BaseX = InMinX + X * ComponentSizeQuads;
const int32 BaseY = InMinY + Y * ComponentSizeQuads;
ULandscapeComponent* LandscapeComponent = NewObject<ULandscapeComponent>(this, NAME_None, RF_Transactional);
LandscapeComponent->Init(BaseX, BaseY, ComponentSizeQuads, NumSubsections, SubsectionSizeQuads);
}
}
// Ensure that we don't pack so many heightmaps into a texture that their lowest LOD isn't guaranteed to be resident
#define MAX_HEIGHTMAP_TEXTURE_SIZE 512
const int32 ComponentSizeVerts = NumSubsections * (SubsectionSizeQuads + 1);
const int32 ComponentsPerHeightmap = FMath::Min(MAX_HEIGHTMAP_TEXTURE_SIZE / ComponentSizeVerts, 1 << (UTexture2D::GetStaticMinTextureResidentMipCount() - 2));
check(ComponentsPerHeightmap > 0);
// Count how many heightmaps we need and the X dimension of the final heightmap
int32 NumHeightmapsX = 1;
int32 FinalComponentsX = NumComponentsX;
while (FinalComponentsX > ComponentsPerHeightmap)
{
FinalComponentsX -= ComponentsPerHeightmap;
NumHeightmapsX++;
}
// Count how many heightmaps we need and the Y dimension of the final heightmap
int32 NumHeightmapsY = 1;
int32 FinalComponentsY = NumComponentsY;
while (FinalComponentsY > ComponentsPerHeightmap)
{
FinalComponentsY -= ComponentsPerHeightmap;
NumHeightmapsY++;
}
TArray<FHeightmapInfo> HeightmapInfos;
for (int32 HmY = 0; HmY < NumHeightmapsY; HmY++)
{
for (int32 HmX = 0; HmX < NumHeightmapsX; HmX++)
{
FHeightmapInfo& HeightmapInfo = HeightmapInfos[HeightmapInfos.AddZeroed()];
// make sure the heightmap UVs are powers of two.
HeightmapInfo.HeightmapSizeU = (1 << FMath::CeilLogTwo(((HmX == NumHeightmapsX - 1) ? FinalComponentsX : ComponentsPerHeightmap) * ComponentSizeVerts));
HeightmapInfo.HeightmapSizeV = (1 << FMath::CeilLogTwo(((HmY == NumHeightmapsY - 1) ? FinalComponentsY : ComponentsPerHeightmap) * ComponentSizeVerts));
// Construct the heightmap textures
HeightmapInfo.HeightmapTexture = CreateLandscapeTexture(HeightmapInfo.HeightmapSizeU, HeightmapInfo.HeightmapSizeV, TEXTUREGROUP_Terrain_Heightmap, TSF_BGRA8);
int32 MipSubsectionSizeQuads = SubsectionSizeQuads;
int32 MipSizeU = HeightmapInfo.HeightmapSizeU;
int32 MipSizeV = HeightmapInfo.HeightmapSizeV;
while (MipSizeU > 1 && MipSizeV > 1 && MipSubsectionSizeQuads >= 1)
{
int32 MipIndex = HeightmapInfo.HeightmapTextureMipData.Num();
FColor* HeightmapTextureData = (FColor*)HeightmapInfo.HeightmapTexture->Source.LockMip(MipIndex);
FMemory::Memzero(HeightmapTextureData, MipSizeU*MipSizeV*sizeof(FColor));
HeightmapInfo.HeightmapTextureMipData.Add(HeightmapTextureData);
MipSizeU >>= 1;
MipSizeV >>= 1;
MipSubsectionSizeQuads = ((MipSubsectionSizeQuads + 1) >> 1) - 1;
}
}
}
const FVector DrawScale3D = GetRootComponent()->GetRelativeScale3D();
// layer to import data (Final or 1st layer)
const FGuid FinalLayerGuid = FGuid();
const TArray<uint16>& HeightData = InImportHeightData.FindChecked(FinalLayerGuid);
const TArray<FLandscapeImportLayerInfo>& ImportLayerInfos = InImportMaterialLayerInfos.FindChecked(FinalLayerGuid);
// Calculate the normals for each of the two triangles per quad.
TArray<FVector> VertexNormals;
VertexNormals.AddZeroed(VertsX * VertsY);
for (int32 QuadY = 0; QuadY < NumPatchesY; QuadY++)
{
for (int32 QuadX = 0; QuadX < NumPatchesX; QuadX++)
{
const FVector Vert00 = FVector(0.0f, 0.0f, ((float)HEIGHTDATA(QuadX + 0, QuadY + 0) - 32768.0f)*LANDSCAPE_ZSCALE) * DrawScale3D;
const FVector Vert01 = FVector(0.0f, 1.0f, ((float)HEIGHTDATA(QuadX + 0, QuadY + 1) - 32768.0f)*LANDSCAPE_ZSCALE) * DrawScale3D;
const FVector Vert10 = FVector(1.0f, 0.0f, ((float)HEIGHTDATA(QuadX + 1, QuadY + 0) - 32768.0f)*LANDSCAPE_ZSCALE) * DrawScale3D;
const FVector Vert11 = FVector(1.0f, 1.0f, ((float)HEIGHTDATA(QuadX + 1, QuadY + 1) - 32768.0f)*LANDSCAPE_ZSCALE) * DrawScale3D;
const FVector FaceNormal1 = ((Vert00 - Vert10) ^ (Vert10 - Vert11)).GetSafeNormal();
const FVector FaceNormal2 = ((Vert11 - Vert01) ^ (Vert01 - Vert00)).GetSafeNormal();
// contribute to the vertex normals.
VertexNormals[(QuadX + 1 + VertsX * (QuadY + 0))] += FaceNormal1;
VertexNormals[(QuadX + 0 + VertsX * (QuadY + 1))] += FaceNormal2;
VertexNormals[(QuadX + 0 + VertsX * (QuadY + 0))] += FaceNormal1 + FaceNormal2;
VertexNormals[(QuadX + 1 + VertsX * (QuadY + 1))] += FaceNormal1 + FaceNormal2;
}
}
// Weight values for each layer for each component.
TArray<TArray<TArray<uint8>>> ComponentWeightValues;
ComponentWeightValues.AddZeroed(NumComponentsX * NumComponentsY);
for (int32 ComponentY = 0; ComponentY < NumComponentsY; ComponentY++)
{
for (int32 ComponentX = 0; ComponentX < NumComponentsX; ComponentX++)
{
ULandscapeComponent* const LandscapeComponent = LandscapeComponents[ComponentX + ComponentY*NumComponentsX];
TArray<TArray<uint8>>& WeightValues = ComponentWeightValues[ComponentX + ComponentY*NumComponentsX];
// Import alphamap data into local array and check for unused layers for this component.
TArray<FLandscapeComponentAlphaInfo, TInlineAllocator<16>> EditingAlphaLayerData;
for (int32 LayerIndex = 0; LayerIndex < ImportLayerInfos.Num(); LayerIndex++)
{
FLandscapeComponentAlphaInfo* NewAlphaInfo = new(EditingAlphaLayerData) FLandscapeComponentAlphaInfo(LandscapeComponent, LayerIndex);
if (ImportLayerInfos[LayerIndex].LayerData.Num())
{
for (int32 AlphaY = 0; AlphaY <= LandscapeComponent->ComponentSizeQuads; AlphaY++)
{
const uint8* const OldAlphaRowStart = &ImportLayerInfos[LayerIndex].LayerData[(AlphaY + LandscapeComponent->GetSectionBase().Y - InMinY) * VertsX + (LandscapeComponent->GetSectionBase().X - InMinX)];
uint8* const NewAlphaRowStart = &NewAlphaInfo->AlphaValues[AlphaY * (LandscapeComponent->ComponentSizeQuads + 1)];
FMemory::Memcpy(NewAlphaRowStart, OldAlphaRowStart, LandscapeComponent->ComponentSizeQuads + 1);
}
}
}
for (int32 AlphaMapIndex = 0; AlphaMapIndex < EditingAlphaLayerData.Num(); AlphaMapIndex++)
{
if (EditingAlphaLayerData[AlphaMapIndex].IsLayerAllZero())
{
EditingAlphaLayerData.RemoveAt(AlphaMapIndex);
AlphaMapIndex--;
}
}
UE_LOG(LogLandscape, VeryVerbose, TEXT("%s needs %d alphamaps"), *LandscapeComponent->GetName(), EditingAlphaLayerData.Num());
TArray<FWeightmapLayerAllocationInfo>& ComponentWeightmapLayerAllocations = LandscapeComponent->GetWeightmapLayerAllocations();
// Calculate weightmap weights for this component
WeightValues.Empty(EditingAlphaLayerData.Num());
WeightValues.AddZeroed(EditingAlphaLayerData.Num());
ComponentWeightmapLayerAllocations.Empty(EditingAlphaLayerData.Num());
TArray<bool, TInlineAllocator<16>> IsNoBlendArray;
IsNoBlendArray.Empty(EditingAlphaLayerData.Num());
IsNoBlendArray.AddZeroed(EditingAlphaLayerData.Num());
for (int32 WeightLayerIndex = 0; WeightLayerIndex < WeightValues.Num(); WeightLayerIndex++)
{
// Lookup the original layer name
WeightValues[WeightLayerIndex] = EditingAlphaLayerData[WeightLayerIndex].AlphaValues;
new(ComponentWeightmapLayerAllocations) FWeightmapLayerAllocationInfo(ImportLayerInfos[EditingAlphaLayerData[WeightLayerIndex].LayerIndex].LayerInfo);
IsNoBlendArray[WeightLayerIndex] = ImportLayerInfos[EditingAlphaLayerData[WeightLayerIndex].LayerIndex].LayerInfo->bNoWeightBlend;
}
// Discard the temporary alpha data
EditingAlphaLayerData.Empty();
if (InImportMaterialLayerType == ELandscapeImportAlphamapType::Layered)
{
// For each layer...
for (int32 WeightLayerIndex = WeightValues.Num() - 1; WeightLayerIndex >= 0; WeightLayerIndex--)
{
// ... multiply all lower layers'...
for (int32 BelowWeightLayerIndex = WeightLayerIndex - 1; BelowWeightLayerIndex >= 0; BelowWeightLayerIndex--)
{
int32 TotalWeight = 0;
if (IsNoBlendArray[BelowWeightLayerIndex])
{
continue; // skip no blend
}
// ... values by...
for (int32 Idx = 0; Idx < WeightValues[WeightLayerIndex].Num(); Idx++)
{
// ... one-minus the current layer's values
int32 NewValue = (int32)WeightValues[BelowWeightLayerIndex][Idx] * (int32)(255 - WeightValues[WeightLayerIndex][Idx]) / 255;
WeightValues[BelowWeightLayerIndex][Idx] = (uint8)NewValue;
TotalWeight += NewValue;
}
if (TotalWeight == 0)
{
// Remove the layer as it has no contribution
WeightValues.RemoveAt(BelowWeightLayerIndex);
ComponentWeightmapLayerAllocations.RemoveAt(BelowWeightLayerIndex);
IsNoBlendArray.RemoveAt(BelowWeightLayerIndex);
// The current layer has been re-numbered
WeightLayerIndex--;
}
}
}
}
// Weight normalization for total should be 255...
if (WeightValues.Num())
{
for (int32 Idx = 0; Idx < WeightValues[0].Num(); Idx++)
{
int32 TotalWeight = 0;
int32 MaxLayerIdx = -1;
int32 MaxWeight = INT_MIN;
for (int32 WeightLayerIndex = 0; WeightLayerIndex < WeightValues.Num(); WeightLayerIndex++)
{
if (!IsNoBlendArray[WeightLayerIndex])
{
int32 Weight = WeightValues[WeightLayerIndex][Idx];
TotalWeight += Weight;
if (MaxWeight < Weight)
{
MaxWeight = Weight;
MaxLayerIdx = WeightLayerIndex;
}
}
}
if (TotalWeight > 0 && TotalWeight != 255)
{
// normalization...
float Factor = 255.0f / TotalWeight;
TotalWeight = 0;
for (int32 WeightLayerIndex = 0; WeightLayerIndex < WeightValues.Num(); WeightLayerIndex++)
{
if (!IsNoBlendArray[WeightLayerIndex])
{
WeightValues[WeightLayerIndex][Idx] = (uint8)(Factor * WeightValues[WeightLayerIndex][Idx]);
TotalWeight += WeightValues[WeightLayerIndex][Idx];
}
}
if (255 - TotalWeight && MaxLayerIdx >= 0)
{
WeightValues[MaxLayerIdx][Idx] += 255 - TotalWeight;
}
}
}
}
}
}
// Remember where we have spare texture channels.
TArray<FWeightmapTextureAllocation> TextureAllocations;
for (int32 ComponentY = 0; ComponentY < NumComponentsY; ComponentY++)
{
const int32 HmY = ComponentY / ComponentsPerHeightmap;
const int32 HeightmapOffsetY = (ComponentY - ComponentsPerHeightmap*HmY) * NumSubsections * (SubsectionSizeQuads + 1);
for (int32 ComponentX = 0; ComponentX < NumComponentsX; ComponentX++)
{
const int32 HmX = ComponentX / ComponentsPerHeightmap;
const FHeightmapInfo& HeightmapInfo = HeightmapInfos[HmX + HmY * NumHeightmapsX];
ULandscapeComponent* LandscapeComponent = LandscapeComponents[ComponentX + ComponentY*NumComponentsX];
// Lookup array of weight values for this component.
const TArray<TArray<uint8>>& WeightValues = ComponentWeightValues[ComponentX + ComponentY*NumComponentsX];
// Heightmap offsets
const int32 HeightmapOffsetX = (ComponentX - ComponentsPerHeightmap*HmX) * NumSubsections * (SubsectionSizeQuads + 1);
LandscapeComponent->HeightmapScaleBias = FVector4(1.0f / (float)HeightmapInfo.HeightmapSizeU, 1.0f / (float)HeightmapInfo.HeightmapSizeV, (float)((HeightmapOffsetX)) / (float)HeightmapInfo.HeightmapSizeU, ((float)(HeightmapOffsetY)) / (float)HeightmapInfo.HeightmapSizeV);
LandscapeComponent->SetHeightmap(HeightmapInfo.HeightmapTexture);
// Weightmap is sized the same as the component
const int32 WeightmapSize = (SubsectionSizeQuads + 1) * NumSubsections;
// Should be power of two
check(FMath::IsPowerOfTwo(WeightmapSize));
LandscapeComponent->WeightmapScaleBias = FVector4(1.0f / (float)WeightmapSize, 1.0f / (float)WeightmapSize, 0.5f / (float)WeightmapSize, 0.5f / (float)WeightmapSize);
LandscapeComponent->WeightmapSubsectionOffset = (float)(SubsectionSizeQuads + 1) / (float)WeightmapSize;
// Pointers to the texture data where we'll store each layer. Stride is 4 (FColor)
TArray<uint8*> WeightmapTextureDataPointers;
UE_LOG(LogLandscape, VeryVerbose, TEXT("%s needs %d weightmap channels"), *LandscapeComponent->GetName(), WeightValues.Num());
// Find texture channels to store each layer.
int32 LayerIndex = 0;
while (LayerIndex < WeightValues.Num())
{
const int32 RemainingLayers = WeightValues.Num() - LayerIndex;
int32 BestAllocationIndex = -1;
// if we need less than 4 channels, try to find them somewhere to put all of them
if (RemainingLayers < 4)
{
int32 BestDistSquared = MAX_int32;
for (int32 TryAllocIdx = 0; TryAllocIdx < TextureAllocations.Num(); TryAllocIdx++)
{
if (TextureAllocations[TryAllocIdx].ChannelsInUse + RemainingLayers <= 4)
{
FWeightmapTextureAllocation& TryAllocation = TextureAllocations[TryAllocIdx];
const int32 TryDistSquared = FMath::Square(TryAllocation.X - ComponentX) + FMath::Square(TryAllocation.Y - ComponentY);
if (TryDistSquared < BestDistSquared)
{
BestDistSquared = TryDistSquared;
BestAllocationIndex = TryAllocIdx;
}
}
}
}
TArray<FWeightmapLayerAllocationInfo>& ComponentWeightmapLayerAllocations = LandscapeComponent->GetWeightmapLayerAllocations();
TArray<UTexture2D*>& ComponentWeightmapTextures = LandscapeComponent->GetWeightmapTextures();
TArray<ULandscapeWeightmapUsage*>& ComponentWeightmapTexturesUsage = LandscapeComponent->GetWeightmapTexturesUsage();
if (BestAllocationIndex != -1)
{
FWeightmapTextureAllocation& Allocation = TextureAllocations[BestAllocationIndex];
ULandscapeWeightmapUsage* WeightmapUsage = WeightmapUsageMap.FindChecked(Allocation.Texture);
ComponentWeightmapTexturesUsage.Add(WeightmapUsage);
UE_LOG(LogLandscape, VeryVerbose, TEXT(" ==> Storing %d channels starting at %s[%d]"), RemainingLayers, *Allocation.Texture->GetName(), Allocation.ChannelsInUse);
for (int32 i = 0; i < RemainingLayers; i++)
{
ComponentWeightmapLayerAllocations[LayerIndex + i].WeightmapTextureIndex = ComponentWeightmapTextures.Num();
ComponentWeightmapLayerAllocations[LayerIndex + i].WeightmapTextureChannel = Allocation.ChannelsInUse;
WeightmapUsage->ChannelUsage[Allocation.ChannelsInUse] = LandscapeComponent;
switch (Allocation.ChannelsInUse)
{
case 1:
WeightmapTextureDataPointers.Add((uint8*)&Allocation.TextureData->G);
break;
case 2:
WeightmapTextureDataPointers.Add((uint8*)&Allocation.TextureData->B);
break;
case 3:
WeightmapTextureDataPointers.Add((uint8*)&Allocation.TextureData->A);
break;
default:
// this should not occur.
check(0);
}
Allocation.ChannelsInUse++;
}
LayerIndex += RemainingLayers;
ComponentWeightmapTextures.Add(Allocation.Texture);
}
else
{
// We couldn't find a suitable place for these layers, so lets make a new one.
UTexture2D* const WeightmapTexture = CreateLandscapeTexture(WeightmapSize, WeightmapSize, TEXTUREGROUP_Terrain_Weightmap, TSF_BGRA8);
FColor* const MipData = (FColor*)WeightmapTexture->Source.LockMip(0);
const int32 ThisAllocationLayers = FMath::Min<int32>(RemainingLayers, 4);
new(TextureAllocations) FWeightmapTextureAllocation(ComponentX, ComponentY, ThisAllocationLayers, WeightmapTexture, MipData);
ULandscapeWeightmapUsage* WeightmapUsage = WeightmapUsageMap.Add(WeightmapTexture, CreateWeightmapUsage());
ComponentWeightmapTexturesUsage.Add(WeightmapUsage);
UE_LOG(LogLandscape, VeryVerbose, TEXT(" ==> Storing %d channels in new texture %s"), ThisAllocationLayers, *WeightmapTexture->GetName());
WeightmapTextureDataPointers.Add((uint8*)&MipData->R);
ComponentWeightmapLayerAllocations[LayerIndex + 0].WeightmapTextureIndex = ComponentWeightmapTextures.Num();
ComponentWeightmapLayerAllocations[LayerIndex + 0].WeightmapTextureChannel = 0;
WeightmapUsage->ChannelUsage[0] = LandscapeComponent;
if (ThisAllocationLayers > 1)
{
WeightmapTextureDataPointers.Add((uint8*)&MipData->G);
ComponentWeightmapLayerAllocations[LayerIndex + 1].WeightmapTextureIndex = ComponentWeightmapTextures.Num();
ComponentWeightmapLayerAllocations[LayerIndex + 1].WeightmapTextureChannel = 1;
WeightmapUsage->ChannelUsage[1] = LandscapeComponent;
if (ThisAllocationLayers > 2)
{
WeightmapTextureDataPointers.Add((uint8*)&MipData->B);
ComponentWeightmapLayerAllocations[LayerIndex + 2].WeightmapTextureIndex = ComponentWeightmapTextures.Num();
ComponentWeightmapLayerAllocations[LayerIndex + 2].WeightmapTextureChannel = 2;
WeightmapUsage->ChannelUsage[2] = LandscapeComponent;
if (ThisAllocationLayers > 3)
{
WeightmapTextureDataPointers.Add((uint8*)&MipData->A);
ComponentWeightmapLayerAllocations[LayerIndex + 3].WeightmapTextureIndex = ComponentWeightmapTextures.Num();
ComponentWeightmapLayerAllocations[LayerIndex + 3].WeightmapTextureChannel = 3;
WeightmapUsage->ChannelUsage[3] = LandscapeComponent;
}
}
}
ComponentWeightmapTextures.Add(WeightmapTexture);
LayerIndex += ThisAllocationLayers;
}
}
check(WeightmapTextureDataPointers.Num() == WeightValues.Num());
FBox LocalBox(ForceInit);
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 WeightSrcDataIdx = CompY * (ComponentSizeQuads + 1) + CompX;
const int32 HeightTexDataIdx = (HeightmapOffsetX + TexX) + (HeightmapOffsetY + TexY) * (HeightmapInfo.HeightmapSizeU);
const int32 WeightTexDataIdx = (TexX)+(TexY)* (WeightmapSize);
// copy height and normal data
const uint16 HeightValue = HEIGHTDATA(CompX + LandscapeComponent->GetSectionBase().X - InMinX, CompY + LandscapeComponent->GetSectionBase().Y - InMinY);
const FVector Normal = VertexNormals[CompX + LandscapeComponent->GetSectionBase().X - InMinX + VertsX * (CompY + LandscapeComponent->GetSectionBase().Y - InMinY)].GetSafeNormal();
HeightmapInfo.HeightmapTextureMipData[0][HeightTexDataIdx].R = HeightValue >> 8;
HeightmapInfo.HeightmapTextureMipData[0][HeightTexDataIdx].G = HeightValue & 255;
HeightmapInfo.HeightmapTextureMipData[0][HeightTexDataIdx].B = FMath::RoundToInt(127.5f * (Normal.X + 1.0f));
HeightmapInfo.HeightmapTextureMipData[0][HeightTexDataIdx].A = FMath::RoundToInt(127.5f * (Normal.Y + 1.0f));
for (int32 WeightmapIndex = 0; WeightmapIndex < WeightValues.Num(); WeightmapIndex++)
{
WeightmapTextureDataPointers[WeightmapIndex][WeightTexDataIdx * 4] = WeightValues[WeightmapIndex][WeightSrcDataIdx];
}
// Get local space verts
const FVector LocalVertex(CompX, CompY, LandscapeDataAccess::GetLocalHeight(HeightValue));
LocalBox += LocalVertex;
}
}
}
}
LandscapeComponent->CachedLocalBox = LocalBox;
}
}
TArray<UTexture2D*> PendingTexturePlatformDataCreation;
// Unlock the weightmaps' base mips
for (int32 AllocationIndex = 0; AllocationIndex < TextureAllocations.Num(); AllocationIndex++)
{
UTexture2D* const WeightmapTexture = TextureAllocations[AllocationIndex].Texture;
FColor* const BaseMipData = TextureAllocations[AllocationIndex].TextureData;
// Generate mips for weightmaps
ULandscapeComponent::GenerateWeightmapMips(NumSubsections, SubsectionSizeQuads, WeightmapTexture, BaseMipData);
WeightmapTexture->Source.UnlockMip(0);
WeightmapTexture->BeginCachePlatformData();
WeightmapTexture->ClearAllCachedCookedPlatformData();
PendingTexturePlatformDataCreation.Add(WeightmapTexture);
}
// Generate mipmaps for the components, and create the collision components
for (int32 ComponentY = 0; ComponentY < NumComponentsY; ComponentY++)
{
for (int32 ComponentX = 0; ComponentX < NumComponentsX; ComponentX++)
{
const int32 HmX = ComponentX / ComponentsPerHeightmap;
const int32 HmY = ComponentY / ComponentsPerHeightmap;
FHeightmapInfo& HeightmapInfo = HeightmapInfos[HmX + HmY * NumHeightmapsX];
ULandscapeComponent* LandscapeComponent = LandscapeComponents[ComponentX + ComponentY*NumComponentsX];
LandscapeComponent->GenerateHeightmapMips(HeightmapInfo.HeightmapTextureMipData, ComponentX == NumComponentsX - 1 ? MAX_int32 : 0, ComponentY == NumComponentsY - 1 ? MAX_int32 : 0);
LandscapeComponent->UpdateCollisionHeightData(
HeightmapInfo.HeightmapTextureMipData[LandscapeComponent->CollisionMipLevel],
LandscapeComponent->SimpleCollisionMipLevel > LandscapeComponent->CollisionMipLevel ? HeightmapInfo.HeightmapTextureMipData[LandscapeComponent->SimpleCollisionMipLevel] : nullptr);
LandscapeComponent->UpdateCollisionLayerData();
}
}
for (int32 HmIdx = 0; HmIdx < HeightmapInfos.Num(); HmIdx++)
{
FHeightmapInfo& HeightmapInfo = HeightmapInfos[HmIdx];
// Add remaining mips down to 1x1 to heightmap texture. These do not represent quads and are just a simple averages of the previous mipmaps.
// These mips are not used for sampling in the vertex shader but could be sampled in the pixel shader.
int32 Mip = HeightmapInfo.HeightmapTextureMipData.Num();
int32 MipSizeU = (HeightmapInfo.HeightmapTexture->Source.GetSizeX()) >> Mip;
int32 MipSizeV = (HeightmapInfo.HeightmapTexture->Source.GetSizeY()) >> Mip;
while (MipSizeU > 1 && MipSizeV > 1)
{
HeightmapInfo.HeightmapTextureMipData.Add((FColor*)HeightmapInfo.HeightmapTexture->Source.LockMip(Mip));
const int32 PrevMipSizeU = (HeightmapInfo.HeightmapTexture->Source.GetSizeX()) >> (Mip - 1);
const int32 PrevMipSizeV = (HeightmapInfo.HeightmapTexture->Source.GetSizeY()) >> (Mip - 1);
for (int32 Y = 0; Y < MipSizeV; Y++)
{
for (int32 X = 0; X < MipSizeU; X++)
{
FColor* const TexData = &(HeightmapInfo.HeightmapTextureMipData[Mip])[X + Y * MipSizeU];
const FColor* const PreMipTexData00 = &(HeightmapInfo.HeightmapTextureMipData[Mip - 1])[(X * 2 + 0) + (Y * 2 + 0) * PrevMipSizeU];
const FColor* const PreMipTexData01 = &(HeightmapInfo.HeightmapTextureMipData[Mip - 1])[(X * 2 + 0) + (Y * 2 + 1) * PrevMipSizeU];
const FColor* const PreMipTexData10 = &(HeightmapInfo.HeightmapTextureMipData[Mip - 1])[(X * 2 + 1) + (Y * 2 + 0) * PrevMipSizeU];
const FColor* const PreMipTexData11 = &(HeightmapInfo.HeightmapTextureMipData[Mip - 1])[(X * 2 + 1) + (Y * 2 + 1) * PrevMipSizeU];
TexData->R = (((int32)PreMipTexData00->R + (int32)PreMipTexData01->R + (int32)PreMipTexData10->R + (int32)PreMipTexData11->R) >> 2);
TexData->G = (((int32)PreMipTexData00->G + (int32)PreMipTexData01->G + (int32)PreMipTexData10->G + (int32)PreMipTexData11->G) >> 2);
TexData->B = (((int32)PreMipTexData00->B + (int32)PreMipTexData01->B + (int32)PreMipTexData10->B + (int32)PreMipTexData11->B) >> 2);
TexData->A = (((int32)PreMipTexData00->A + (int32)PreMipTexData01->A + (int32)PreMipTexData10->A + (int32)PreMipTexData11->A) >> 2);
}
}
Mip++;
MipSizeU >>= 1;
MipSizeV >>= 1;
}
for (int32 i = 0; i < HeightmapInfo.HeightmapTextureMipData.Num(); i++)
{
HeightmapInfo.HeightmapTexture->Source.UnlockMip(i);
}
HeightmapInfo.HeightmapTexture->BeginCachePlatformData();
HeightmapInfo.HeightmapTexture->ClearAllCachedCookedPlatformData();
PendingTexturePlatformDataCreation.Add(HeightmapInfo.HeightmapTexture);
}
// Build a list of all unique materials the landscape uses
TArray<UMaterialInterface*> LandscapeMaterials;
for (ULandscapeComponent* Component : LandscapeComponents)
{
int8 MaxLOD = FMath::CeilLogTwo(Component->SubsectionSizeQuads + 1) - 1;
for (int8 LODIndex = 0; LODIndex < MaxLOD; ++LODIndex)
{
UMaterialInterface* Material = Component->GetLandscapeMaterial(LODIndex);
LandscapeMaterials.AddUnique(Material);
}
}
// Update all materials and recreate render state of all landscape components
TArray<FComponentRecreateRenderStateContext> RecreateRenderStateContexts;
SlowTask.EnterProgressFrame(1.0f);
{
// We disable automatic material update context, to manage it manually
GDisableAutomaticTextureMaterialUpdateDependencies = true;
FMaterialUpdateContext UpdateContext(FMaterialUpdateContext::EOptions::Default & ~FMaterialUpdateContext::EOptions::RecreateRenderStates);
for (UTexture2D* Texture : PendingTexturePlatformDataCreation)
{
Texture->FinishCachePlatformData();
Texture->PostEditChange();
TSet<UMaterial*> BaseMaterialsThatUseThisTexture;
for (UMaterialInterface* MaterialInterface : LandscapeMaterials)
{
if (DoesMaterialUseTexture(MaterialInterface, Texture))
{
UMaterial* Material = MaterialInterface->GetMaterial();
bool MaterialAlreadyCompute = false;
BaseMaterialsThatUseThisTexture.Add(Material, &MaterialAlreadyCompute);
if (!MaterialAlreadyCompute)
{
if (Material->IsTextureForceRecompileCacheRessource(Texture))
{
UpdateContext.AddMaterial(Material);
Material->UpdateMaterialShaderCacheAndTextureReferences();
}
}
}
}
}
GDisableAutomaticTextureMaterialUpdateDependencies = false;
// Update MaterialInstances (must be done after textures are fully initialized)
UpdateAllComponentMaterialInstances(UpdateContext, RecreateRenderStateContexts);
}
// Recreate the render state for this component, needed to update the static drawlist which has cached the MaterialRenderProxies
// Must be after the FMaterialUpdateContext is destroyed
RecreateRenderStateContexts.Reset();
// Create and initialize landscape info object
ULandscapeInfo* LandscapeInfo = CreateLandscapeInfo();
if (CanHaveLayersContent())
{
// Create the default layer first
ALandscape* LandscapeActor = GetLandscapeActor();
check(LandscapeActor != nullptr);
if (LandscapeActor->GetLayerCount() == 0 && InImportLayers == nullptr)
{
LandscapeActor->CreateDefaultLayer();
}
// Components need to be registered to be able to import the layer content and we will remove them if they should have not been visible
bool ShouldComponentBeRegistered = GetLevel()->bIsVisible;
RegisterAllComponents();
TSet<ULandscapeComponent*> ComponentsToProcess;
struct FLayerImportSettings
{
FGuid SourceLayerGuid;
FGuid DestinationLayerGuid;
};
TArray<FLayerImportSettings> LayerImportSettings;
// Only create Layers on main Landscape
if (LandscapeActor == this && InImportLayers != nullptr)
{
for (const FLandscapeLayer& OldLayer : *InImportLayers)
{
FLandscapeLayer* NewLayer = LandscapeActor->DuplicateLayerAndMoveBrushes(OldLayer);
check(NewLayer != nullptr);
FLayerImportSettings ImportSettings;
ImportSettings.SourceLayerGuid = OldLayer.Guid;
ImportSettings.DestinationLayerGuid = NewLayer->Guid;
LayerImportSettings.Add(ImportSettings);
}
LandscapeInfo->GetComponentsInRegion(InMinX, InMinY, InMaxX, InMaxY, ComponentsToProcess);
}
else
{
// In the case of a streaming proxy, we will generate the layer data for each components that the proxy hold so no need of the grid min/max to calculate the components to update
if (LandscapeActor != this)
{
LandscapeActor->AddLayersToProxy(this);
}
// And we will fill all the landscape components with the provided final layer content put into the default layer (aka layer index 0)
const FLandscapeLayer* DefaultLayer = LandscapeActor->GetLayer(0);
check(DefaultLayer != nullptr);
FLayerImportSettings ImportSettings;
ImportSettings.SourceLayerGuid = FinalLayerGuid;
ImportSettings.DestinationLayerGuid = DefaultLayer->Guid;
LayerImportSettings.Add(ImportSettings);
ComponentsToProcess.Append(ToRawPtrTArrayUnsafe(LandscapeComponents));
}
check(LayerImportSettings.Num() != 0);
// Currently only supports reimporting heightmap data into a single edit layer, which will always be the default layer
ReimportDestinationLayerGuid = LayerImportSettings[0].DestinationLayerGuid;
TSet<UTexture2D*> LayersTextures;
for (const FLayerImportSettings& ImportSettings : LayerImportSettings)
{
FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo, false);
FScopedSetLandscapeEditingLayer Scope(LandscapeActor, ImportSettings.DestinationLayerGuid);
const TArray<uint16>* ImportHeightData = InImportHeightData.Find(ImportSettings.SourceLayerGuid);
if (ImportHeightData != nullptr)
{
LandscapeEdit.SetHeightData(InMinX, InMinY, InMaxX, InMaxY, (uint16*)ImportHeightData->GetData(), 0, false, nullptr);
}
const TArray<FLandscapeImportLayerInfo>* ImportWeightData = InImportMaterialLayerInfos.Find(ImportSettings.SourceLayerGuid);
if (ImportWeightData != nullptr)
{
for (const FLandscapeImportLayerInfo& MaterialLayerInfo : *ImportWeightData)
{
if (MaterialLayerInfo.LayerInfo != nullptr && MaterialLayerInfo.LayerData.Num() > 0)
{
LandscapeEdit.SetAlphaData(MaterialLayerInfo.LayerInfo, InMinX, InMinY, InMaxX, InMaxY, MaterialLayerInfo.LayerData.GetData(), 0, ELandscapeLayerPaintingRestriction::None, true, false);
}
}
}
for (ULandscapeComponent* Component : ComponentsToProcess)
{
FLandscapeLayerComponentData* ComponentLayerData = Component->GetLayerData(ImportSettings.DestinationLayerGuid);
check(ComponentLayerData != nullptr);
LayersTextures.Add(ComponentLayerData->HeightmapData.Texture);
LayersTextures.Append(ToRawPtrTArrayUnsafe(ComponentLayerData->WeightmapData.Textures));
}
}
// Retrigger a caching of the platform data as we wrote again in the textures
for (UTexture2D* Texture : LayersTextures)
{
Texture->UpdateResource();
}
LandscapeActor->RequestLayersContentUpdateForceAll();
if (!ShouldComponentBeRegistered)
{
UnregisterAllComponents();
}
}
else
{
if (GetLevel()->bIsVisible)
{
ReregisterAllComponents();
}
ReimportDestinationLayerGuid = FGuid();
LandscapeInfo->RecreateCollisionComponents();
LandscapeInfo->UpdateAllAddCollisions();
}
ReimportHeightmapFilePath = InHeightmapFileName;
LandscapeInfo->UpdateLayerInfoMap();
}
bool ALandscapeProxy::ExportToRawMesh(int32 InExportLOD, FMeshDescription& OutRawMesh) const
{
FBoxSphereBounds GarbageBounds;
return ExportToRawMesh(InExportLOD, OutRawMesh, GarbageBounds, true);
}
bool ALandscapeProxy::ExportToRawMesh(int32 InExportLOD, FMeshDescription& OutRawMesh, const FBoxSphereBounds& InBounds, bool bIgnoreBounds /*= false*/) const
{
TInlineComponentArray<ULandscapeComponent*> RegisteredLandscapeComponents;
GetComponents<ULandscapeComponent>(RegisteredLandscapeComponents);
const FIntRect LandscapeSectionRect = GetBoundingRect();
const FVector2f LandscapeUVScale = FVector2f(1.0f, 1.0f) / FVector2f(LandscapeSectionRect.Size());
FStaticMeshAttributes Attributes(OutRawMesh);
TVertexAttributesRef<FVector3f> VertexPositions = Attributes.GetVertexPositions();
TEdgeAttributesRef<bool> EdgeHardnesses = Attributes.GetEdgeHardnesses();
TPolygonGroupAttributesRef<FName> PolygonGroupImportedMaterialSlotNames = Attributes.GetPolygonGroupMaterialSlotNames();
TVertexInstanceAttributesRef<FVector3f> VertexInstanceNormals = Attributes.GetVertexInstanceNormals();
TVertexInstanceAttributesRef<FVector3f> VertexInstanceTangents = Attributes.GetVertexInstanceTangents();
TVertexInstanceAttributesRef<float> VertexInstanceBinormalSigns = Attributes.GetVertexInstanceBinormalSigns();
TVertexInstanceAttributesRef<FVector4f> VertexInstanceColors = Attributes.GetVertexInstanceColors();
TVertexInstanceAttributesRef<FVector2f> VertexInstanceUVs = Attributes.GetVertexInstanceUVs();
if (VertexInstanceUVs.GetNumChannels() < 2)
{
VertexInstanceUVs.SetNumChannels(2);
}
// User specified LOD to export
int32 LandscapeLODToExport = ExportLOD;
if (InExportLOD != INDEX_NONE)
{
LandscapeLODToExport = FMath::Clamp<int32>(InExportLOD, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
}
// Export data for each component
for (auto It = RegisteredLandscapeComponents.CreateConstIterator(); It; ++It)
{
ULandscapeComponent* Component = (*It);
// Early out if the Landscape bounds and given bounds do not overlap at all
if (!bIgnoreBounds && !FBoxSphereBounds::SpheresIntersect(Component->Bounds, InBounds))
{
continue;
}
FLandscapeComponentDataInterface CDI(Component, LandscapeLODToExport);
const int32 ComponentSizeQuadsLOD = ((Component->ComponentSizeQuads + 1) >> LandscapeLODToExport) - 1;
const int32 SubsectionSizeQuadsLOD = ((Component->SubsectionSizeQuads + 1) >> LandscapeLODToExport) - 1;
const FIntPoint ComponentOffsetQuads = Component->GetSectionBase() - LandscapeSectionOffset - LandscapeSectionRect.Min;
const FVector2f ComponentUVOffsetLOD = FVector2f(ComponentOffsetQuads)*((float)ComponentSizeQuadsLOD / ComponentSizeQuads);
const FVector2f ComponentUVScaleLOD = LandscapeUVScale*((float)ComponentSizeQuads / ComponentSizeQuadsLOD);
const int32 NumFaces = FMath::Square(ComponentSizeQuadsLOD) * 2;
const int32 NumVertices = NumFaces * 3;
OutRawMesh.ReserveNewVertices(NumVertices);
OutRawMesh.ReserveNewPolygons(NumFaces);
OutRawMesh.ReserveNewVertexInstances(NumVertices);
OutRawMesh.ReserveNewEdges(NumVertices);
FPolygonGroupID PolygonGroupID = INDEX_NONE;
if (OutRawMesh.PolygonGroups().Num() < 1)
{
PolygonGroupID = OutRawMesh.CreatePolygonGroup();
PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(TEXT("LandscapeMat_0"));
}
else
{
PolygonGroupID = OutRawMesh.PolygonGroups().GetFirstValidID();
}
// Check if there are any holes
const int32 VisThreshold = 170;
TArray<uint8> VisDataMap;
const TArray<FWeightmapLayerAllocationInfo>& ComponentWeightmapLayerAllocations = Component->GetWeightmapLayerAllocations();
for (int32 AllocIdx = 0; AllocIdx < ComponentWeightmapLayerAllocations.Num(); AllocIdx++)
{
const FWeightmapLayerAllocationInfo& AllocInfo = ComponentWeightmapLayerAllocations[AllocIdx];
if (AllocInfo.LayerInfo == ALandscapeProxy::VisibilityLayer)
{
CDI.GetWeightmapTextureData(AllocInfo.LayerInfo, VisDataMap);
}
}
const FIntPoint QuadPattern[6] =
{
//face 1
FIntPoint(0, 0),
FIntPoint(0, 1),
FIntPoint(1, 1),
//face 2
FIntPoint(0, 0),
FIntPoint(1, 1),
FIntPoint(1, 0),
};
const int32 WeightMapSize = (SubsectionSizeQuadsLOD + 1) * Component->NumSubsections;
const float SquaredSphereRadius = FMath::Square(InBounds.SphereRadius);
//We need to not duplicate the vertex position, so we use the FIndexAndZ to achieve fast result
TArray<FIndexAndZ> VertIndexAndZ;
VertIndexAndZ.Reserve(ComponentSizeQuadsLOD*ComponentSizeQuadsLOD*UE_ARRAY_COUNT(QuadPattern));
int32 CurrentIndex = 0;
TMap<int32, FVector> IndexToPosition;
IndexToPosition.Reserve(ComponentSizeQuadsLOD*ComponentSizeQuadsLOD*UE_ARRAY_COUNT(QuadPattern));
for (int32 y = 0; y < ComponentSizeQuadsLOD; y++)
{
for (int32 x = 0; x < ComponentSizeQuadsLOD; x++)
{
for (int32 i = 0; i < UE_ARRAY_COUNT(QuadPattern); i++)
{
int32 VertexX = x + QuadPattern[i].X;
int32 VertexY = y + QuadPattern[i].Y;
FVector Position = CDI.GetWorldVertex(VertexX, VertexY);
// If at least one vertex is within the given bounds we should process the quad
new(VertIndexAndZ)FIndexAndZ(CurrentIndex, Position);
IndexToPosition.Add(CurrentIndex, Position);
CurrentIndex++;
}
}
}
// Sort the vertices by z value
VertIndexAndZ.Sort(FCompareIndexAndZ());
auto FindPreviousIndex = [&VertIndexAndZ, &IndexToPosition](int32 Index)->int32
{
const FVector& PositionA = IndexToPosition[Index];
FIndexAndZ CompressPosition(0, PositionA);
// Search for lowest index duplicates
int32 BestIndex = MAX_int32;
for (int32 i = 0; i < IndexToPosition.Num(); i++)
{
if (CompressPosition.Z > (VertIndexAndZ[i].Z + SMALL_NUMBER))
{
//We will not find anything there is no point searching more
break;
}
const FVector& PositionB = IndexToPosition[VertIndexAndZ[i].Index];
if (PointsEqual(PositionA, PositionB, SMALL_NUMBER))
{
if (VertIndexAndZ[i].Index < BestIndex)
{
BestIndex = VertIndexAndZ[i].Index;
}
}
}
return BestIndex < MAX_int32 ? BestIndex : Index;
};
// Export to MeshDescription
TMap<int32, FVertexID> IndexToVertexID;
IndexToVertexID.Reserve(CurrentIndex);
CurrentIndex = 0;
for (int32 y = 0; y < ComponentSizeQuadsLOD; y++)
{
for (int32 x = 0; x < ComponentSizeQuadsLOD; x++)
{
FVector Positions[UE_ARRAY_COUNT(QuadPattern)];
bool bProcess = bIgnoreBounds;
// Fill positions
for (int32 i = 0; i < UE_ARRAY_COUNT(QuadPattern); i++)
{
int32 VertexX = x + QuadPattern[i].X;
int32 VertexY = y + QuadPattern[i].Y;
Positions[i] = CDI.GetWorldVertex(VertexX, VertexY);
// If at least one vertex is within the given bounds we should process the quad
if (!bProcess && InBounds.ComputeSquaredDistanceFromBoxToPoint(Positions[i]) < SquaredSphereRadius)
{
bProcess = true;
}
}
if (bProcess)
{
//Fill the vertexID we need
TArray<FVertexID> VertexIDs;
VertexIDs.Reserve(UE_ARRAY_COUNT(QuadPattern));
TArray<FVertexInstanceID> VertexInstanceIDs;
VertexInstanceIDs.Reserve(UE_ARRAY_COUNT(QuadPattern));
// Fill positions
for (int32 i = 0; i < UE_ARRAY_COUNT(QuadPattern); i++)
{
int32 DuplicateLowestIndex = FindPreviousIndex(CurrentIndex);
FVertexID VertexID;
if (DuplicateLowestIndex < CurrentIndex)
{
VertexID = IndexToVertexID[DuplicateLowestIndex];
}
else
{
VertexID = OutRawMesh.CreateVertex();
VertexPositions[VertexID] = FVector3f(Positions[i]);
}
IndexToVertexID.Add(CurrentIndex, VertexID);
VertexIDs.Add(VertexID);
CurrentIndex++;
}
// Create triangle
{
// Whether this vertex is in hole
bool bInvisible = false;
if (VisDataMap.Num())
{
int32 TexelX, TexelY;
CDI.VertexXYToTexelXY(x, y, TexelX, TexelY);
bInvisible = (VisDataMap[CDI.TexelXYToIndex(TexelX, TexelY)] >= VisThreshold);
}
//Add vertexInstance and polygon only if we are visible
if (!bInvisible)
{
VertexInstanceIDs.Add(OutRawMesh.CreateVertexInstance(VertexIDs[0]));
VertexInstanceIDs.Add(OutRawMesh.CreateVertexInstance(VertexIDs[1]));
VertexInstanceIDs.Add(OutRawMesh.CreateVertexInstance(VertexIDs[2]));
VertexInstanceIDs.Add(OutRawMesh.CreateVertexInstance(VertexIDs[3]));
VertexInstanceIDs.Add(OutRawMesh.CreateVertexInstance(VertexIDs[4]));
VertexInstanceIDs.Add(OutRawMesh.CreateVertexInstance(VertexIDs[5]));
// Fill other vertex data
for (int32 i = 0; i < UE_ARRAY_COUNT(QuadPattern); i++)
{
int32 VertexX = x + QuadPattern[i].X;
int32 VertexY = y + QuadPattern[i].Y;
FVector LocalTangentX, LocalTangentY, LocalTangentZ;
CDI.GetLocalTangentVectors(VertexX, VertexY, LocalTangentX, LocalTangentY, LocalTangentZ);
VertexInstanceTangents[VertexInstanceIDs[i]] = FVector3f(LocalTangentX);
VertexInstanceBinormalSigns[VertexInstanceIDs[i]] = GetBasisDeterminantSign(LocalTangentX, LocalTangentY, LocalTangentZ);
VertexInstanceNormals[VertexInstanceIDs[i]] = FVector3f(LocalTangentZ);
FVector2f UV = (ComponentUVOffsetLOD + FVector2f(VertexX, VertexY))*ComponentUVScaleLOD;
VertexInstanceUVs.Set(VertexInstanceIDs[i], 0, UV);
// Add lightmap UVs
VertexInstanceUVs.Set(VertexInstanceIDs[i], 1, UV);
}
auto AddTriangle = [&OutRawMesh, &EdgeHardnesses, &PolygonGroupID, &VertexIDs, &VertexInstanceIDs](int32 BaseIndex)
{
//Create a polygon from this triangle
TArray<FVertexInstanceID> PerimeterVertexInstances;
PerimeterVertexInstances.SetNum(3);
for (int32 Corner = 0; Corner < 3; ++Corner)
{
PerimeterVertexInstances[Corner] = VertexInstanceIDs[BaseIndex + Corner];
}
// Insert a polygon into the mesh
TArray<FEdgeID> NewEdgeIDs;
const FPolygonID NewPolygonID = OutRawMesh.CreatePolygon(PolygonGroupID, PerimeterVertexInstances, &NewEdgeIDs);
for (const FEdgeID& NewEdgeID : NewEdgeIDs)
{
EdgeHardnesses[NewEdgeID] = false;
}
};
AddTriangle(0);
AddTriangle(3);
}
}
}
else
{
CurrentIndex += UE_ARRAY_COUNT(QuadPattern);
}
}
}
}
//Compact the MeshDescription, if there was visibility mask or some bounding box clip, it need to be compacted so the sparse array are from 0 to n with no invalid data in between.
FElementIDRemappings ElementIDRemappings;
OutRawMesh.Compact(ElementIDRemappings);
return OutRawMesh.Polygons().Num() > 0;
}
FIntRect ALandscapeProxy::GetBoundingRect() const
{
if (LandscapeComponents.Num() > 0)
{
FIntRect Rect(MAX_int32, MAX_int32, MIN_int32, MIN_int32);
for (int32 CompIdx = 0; CompIdx < LandscapeComponents.Num(); CompIdx++)
{
Rect.Include(LandscapeComponents[CompIdx]->GetSectionBase());
}
Rect.Max += FIntPoint(ComponentSizeQuads, ComponentSizeQuads);
Rect -= LandscapeSectionOffset;
return Rect;
}
return FIntRect();
}
bool ALandscape::HasAllComponent()
{
ULandscapeInfo* Info = GetLandscapeInfo();
if (Info && Info->XYtoComponentMap.Num() == LandscapeComponents.Num())
{
// all components are owned by this Landscape actor (no Landscape Proxies)
return true;
}
return false;
}
bool ULandscapeInfo::GetLandscapeExtent(ALandscapeProxy* LandscapeProxy, FIntRect& ProxyExtent) const
{
ProxyExtent.Min.X = INT32_MAX;
ProxyExtent.Min.Y = INT32_MAX;
ProxyExtent.Max.X = INT32_MIN;
ProxyExtent.Max.Y = INT32_MIN;
for (ULandscapeComponent* LandscapeComponent : LandscapeProxy->LandscapeComponents)
{
LandscapeComponent->GetComponentExtent(ProxyExtent.Min.X, ProxyExtent.Min.Y, ProxyExtent.Max.X, ProxyExtent.Max.Y);
}
return ProxyExtent.Min.X != INT32_MAX;
}
bool ULandscapeInfo::GetLandscapeExtent(FIntRect& LandscapeExtent) const
{
return GetLandscapeExtent(LandscapeExtent.Min.X, LandscapeExtent.Min.Y, LandscapeExtent.Max.X, LandscapeExtent.Max.Y);
}
bool ULandscapeInfo::GetLandscapeExtent(int32& MinX, int32& MinY, int32& MaxX, int32& MaxY) const
{
MinX = MAX_int32;
MinY = MAX_int32;
MaxX = MIN_int32;
MaxY = MIN_int32;
// Find range of entire landscape
for (auto& XYComponentPair : XYtoComponentMap)
{
const ULandscapeComponent* Comp = XYComponentPair.Value;
Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY);
}
return (MinX != MAX_int32);
}
LANDSCAPE_API bool ULandscapeInfo::GetLandscapeXYComponentBounds(FIntRect& OutXYComponentBounds) const
{
OutXYComponentBounds = XYComponentBounds;
return (OutXYComponentBounds.Min.X != MIN_int32) && (OutXYComponentBounds.Min.Y != MIN_int32)
&& (OutXYComponentBounds.Max.X != MAX_int32) && (OutXYComponentBounds.Max.Y != MAX_int32);
}
LANDSCAPE_API void ULandscapeInfo::ForAllLandscapeComponents(TFunctionRef<void(ULandscapeComponent*)> Fn) const
{
ForAllLandscapeProxies([&](ALandscapeProxy* Proxy)
{
for (ULandscapeComponent* Component : Proxy->LandscapeComponents)
{
Fn(Component);
}
});
}
bool ULandscapeInfo::GetSelectedExtent(int32& MinX, int32& MinY, int32& MaxX, int32& MaxY) const
{
MinX = MinY = MAX_int32;
MaxX = MaxY = MIN_int32;
for (auto& SelectedPointPair : SelectedRegion)
{
const FIntPoint Key = SelectedPointPair.Key;
if (MinX > Key.X) MinX = Key.X;
if (MaxX < Key.X) MaxX = Key.X;
if (MinY > Key.Y) MinY = Key.Y;
if (MaxY < Key.Y) MaxY = Key.Y;
}
if (MinX != MAX_int32)
{
return true;
}
// if SelectedRegion is empty, try SelectedComponents
for (const ULandscapeComponent* Comp : SelectedComponents)
{
Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY);
}
return MinX != MAX_int32;
}
FVector ULandscapeInfo::GetLandscapeCenterPos(float& LengthZ, int32 MinX /*= MAX_INT*/, int32 MinY /*= MAX_INT*/, int32 MaxX /*= MIN_INT*/, int32 MaxY /*= MIN_INT*/)
{
// MinZ, MaxZ is Local coordinate
float MaxZ = -HALF_WORLD_MAX, MinZ = HALF_WORLD_MAX;
const float ScaleZ = DrawScale.Z;
if (MinX == MAX_int32)
{
// Find range of entire landscape
for (auto It = XYtoComponentMap.CreateIterator(); It; ++It)
{
ULandscapeComponent* Comp = It.Value();
Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY);
}
const int32 Dist = (ComponentSizeQuads + 1) >> 1; // Should be same in ALandscapeGizmoActiveActor::SetTargetLandscape
FVector2f MidPoint(((float)(MinX + MaxX)) / 2.0f, ((float)(MinY + MaxY)) / 2.0f);
MinX = FMath::FloorToInt(MidPoint.X) - Dist;
MaxX = FMath::CeilToInt(MidPoint.X) + Dist;
MinY = FMath::FloorToInt(MidPoint.Y) - Dist;
MaxY = FMath::CeilToInt(MidPoint.Y) + Dist;
check(MidPoint.X == ((float)(MinX + MaxX)) / 2.0f && MidPoint.Y == ((float)(MinY + MaxY)) / 2.0f);
}
check(MinX != MAX_int32);
//if (MinX != MAX_int32)
{
int32 CompX1, CompX2, CompY1, CompY2;
ALandscape::CalcComponentIndicesOverlap(MinX, MinY, MaxX, MaxY, ComponentSizeQuads, CompX1, CompY1, CompX2, CompY2);
for (int32 IndexY = CompY1; IndexY <= CompY2; ++IndexY)
{
for (int32 IndexX = CompX1; IndexX <= CompX2; ++IndexX)
{
ULandscapeComponent* Comp = XYtoComponentMap.FindRef(FIntPoint(IndexX, IndexY));
if (Comp)
{
ULandscapeHeightfieldCollisionComponent* CollisionComp = Comp->CollisionComponent.Get();
if (CollisionComp)
{
uint16* Heights = (uint16*)CollisionComp->CollisionHeightData.Lock(LOCK_READ_ONLY);
int32 CollisionSizeVerts = CollisionComp->CollisionSizeQuads + 1;
int32 StartX = FMath::Max(0, MinX - CollisionComp->GetSectionBase().X);
int32 StartY = FMath::Max(0, MinY - CollisionComp->GetSectionBase().Y);
int32 EndX = FMath::Min(CollisionSizeVerts, MaxX - CollisionComp->GetSectionBase().X + 1);
int32 EndY = FMath::Min(CollisionSizeVerts, MaxY - CollisionComp->GetSectionBase().Y + 1);
for (int32 Y = StartY; Y < EndY; ++Y)
{
for (int32 X = StartX; X < EndX; ++X)
{
float Height = LandscapeDataAccess::GetLocalHeight(Heights[X + Y*CollisionSizeVerts]);
MaxZ = FMath::Max(Height, MaxZ);
MinZ = FMath::Min(Height, MinZ);
}
}
CollisionComp->CollisionHeightData.Unlock();
}
}
}
}
}
const float MarginZ = 3;
if (MaxZ < MinZ)
{
MaxZ = +MarginZ;
MinZ = -MarginZ;
}
LengthZ = (MaxZ - MinZ + 2 * MarginZ) * ScaleZ;
const FVector LocalPosition(((float)(MinX + MaxX)) / 2.0f, ((float)(MinY + MaxY)) / 2.0f, MinZ - MarginZ);
return GetLandscapeProxy()->LandscapeActorToWorld().TransformPosition(LocalPosition);
}
bool ULandscapeInfo::IsValidPosition(int32 X, int32 Y)
{
int32 CompX1, CompX2, CompY1, CompY2;
ALandscape::CalcComponentIndicesOverlap(X, Y, X, Y, ComponentSizeQuads, CompX1, CompY1, CompX2, CompY2);
if (XYtoComponentMap.FindRef(FIntPoint(CompX1, CompY1)))
{
return true;
}
if (XYtoComponentMap.FindRef(FIntPoint(CompX2, CompY2)))
{
return true;
}
return false;
}
void ULandscapeInfo::ExportHeightmap(const FString& Filename)
{
FIntRect ExportRegion;
if (!GetLandscapeExtent(ExportRegion))
{
return;
}
ExportHeightmap(Filename, ExportRegion);
}
void ULandscapeInfo::ExportHeightmap(const FString& Filename, const FIntRect& ExportRegion)
{
FScopedSlowTask Progress(1, LOCTEXT("ExportingLandscapeHeightmapTask", "Exporting Landscape Heightmap..."));
Progress.MakeDialog();
ILandscapeEditorModule& LandscapeEditorModule = FModuleManager::GetModuleChecked<ILandscapeEditorModule>("LandscapeEditor");
FLandscapeEditDataInterface LandscapeEdit(this);
TArray<uint16> HeightData;
int32 ExportWidth = ExportRegion.Width() + 1;
int32 ExportHeight = ExportRegion.Height() + 1;
HeightData.AddZeroed(ExportWidth * ExportHeight);
LandscapeEdit.GetHeightDataFast(ExportRegion.Min.X, ExportRegion.Min.Y, ExportRegion.Max.X, ExportRegion.Max.Y, HeightData.GetData(), 0);
const ILandscapeHeightmapFileFormat* HeightmapFormat = LandscapeEditorModule.GetHeightmapFormatByExtension(*FPaths::GetExtension(Filename, true));
if (HeightmapFormat)
{
HeightmapFormat->Export(*Filename, NAME_None, HeightData, {(uint32)ExportWidth, (uint32)ExportHeight}, DrawScale * FVector(1, 1, LANDSCAPE_ZSCALE));
}
}
void ULandscapeInfo::ExportLayer(ULandscapeLayerInfoObject* LayerInfo, const FString& Filename)
{
FIntRect ExportRegion;
if (!GetLandscapeExtent(ExportRegion))
{
return;
}
ExportLayer(LayerInfo, Filename, ExportRegion);
}
void ULandscapeInfo::ExportLayer(ULandscapeLayerInfoObject* LayerInfo, const FString& Filename, const FIntRect& ExportRegion)
{
FScopedSlowTask Progress(1, LOCTEXT("ExportingLandscapeWeightmapTask", "Exporting Landscape Layer Weightmap..."));
Progress.MakeDialog();
check(LayerInfo);
ILandscapeEditorModule& LandscapeEditorModule = FModuleManager::GetModuleChecked<ILandscapeEditorModule>("LandscapeEditor");
TArray<uint8> WeightData;
int32 ExportWidth = ExportRegion.Width() + 1;
int32 ExportHeight = ExportRegion.Height() + 1;
WeightData.AddZeroed(ExportWidth * ExportHeight);
FLandscapeEditDataInterface LandscapeEdit(this);
LandscapeEdit.GetWeightDataFast(LayerInfo, ExportRegion.Min.X, ExportRegion.Min.Y, ExportRegion.Max.X, ExportRegion.Max.Y, WeightData.GetData(), 0);
const ILandscapeWeightmapFileFormat* WeightmapFormat = LandscapeEditorModule.GetWeightmapFormatByExtension(*FPaths::GetExtension(Filename, true));
if (WeightmapFormat)
{
WeightmapFormat->Export(*Filename, LayerInfo->LayerName, WeightData, { (uint32)ExportWidth, (uint32)ExportHeight }, DrawScale * FVector(1, 1, LANDSCAPE_ZSCALE));
}
}
void ULandscapeInfo::DeleteLayer(ULandscapeLayerInfoObject* LayerInfo, const FName& LayerName)
{
GWarn->BeginSlowTask(LOCTEXT("BeginDeletingLayerTask", "Deleting Layer"), true);
// Remove data from all components
FLandscapeEditDataInterface LandscapeEdit(this);
LandscapeEdit.DeleteLayer(LayerInfo);
// Remove from layer settings array
{
int32 LayerIndex = Layers.IndexOfByPredicate([LayerInfo, LayerName](const FLandscapeInfoLayerSettings& LayerSettings) { return LayerSettings.LayerInfoObj == LayerInfo && LayerSettings.LayerName == LayerName; });
if (LayerIndex != INDEX_NONE)
{
Layers.RemoveAt(LayerIndex);
}
}
ForAllLandscapeProxies([LayerInfo](ALandscapeProxy* Proxy)
{
Proxy->Modify();
int32 Index = Proxy->EditorLayerSettings.IndexOfByKey(LayerInfo);
if (Index != INDEX_NONE)
{
Proxy->EditorLayerSettings.RemoveAt(Index);
}
});
//UpdateLayerInfoMap();
GWarn->EndSlowTask();
}
void ULandscapeInfo::ReplaceLayer(ULandscapeLayerInfoObject* FromLayerInfo, ULandscapeLayerInfoObject* ToLayerInfo)
{
if (ensure(FromLayerInfo != ToLayerInfo))
{
GWarn->BeginSlowTask(LOCTEXT("BeginReplacingLayerTask", "Replacing Layer"), true);
// Remove data from all components
FLandscapeEditDataInterface LandscapeEdit(this);
LandscapeEdit.ReplaceLayer(FromLayerInfo, ToLayerInfo);
// Convert array
for (int32 j = 0; j < Layers.Num(); j++)
{
if (Layers[j].LayerInfoObj == FromLayerInfo)
{
Layers[j].LayerInfoObj = ToLayerInfo;
}
}
ForAllLandscapeProxies([FromLayerInfo, ToLayerInfo](ALandscapeProxy* Proxy)
{
Proxy->Modify();
FLandscapeEditorLayerSettings* ToEditorLayerSettings = Proxy->EditorLayerSettings.FindByKey(ToLayerInfo);
if (ToEditorLayerSettings != nullptr)
{
// If the new layer already exists, simple remove the old layer
int32 Index = Proxy->EditorLayerSettings.IndexOfByKey(FromLayerInfo);
if (Index != INDEX_NONE)
{
Proxy->EditorLayerSettings.RemoveAt(Index);
}
}
else
{
FLandscapeEditorLayerSettings* FromEditorLayerSettings = Proxy->EditorLayerSettings.FindByKey(FromLayerInfo);
if (FromEditorLayerSettings != nullptr)
{
// If only the old layer exists (most common case), change it to point to the new layer info
FromEditorLayerSettings->LayerInfoObj = ToLayerInfo;
}
else
{
// If neither exists in the EditorLayerSettings cache, add it
Proxy->EditorLayerSettings.Add(FLandscapeEditorLayerSettings(ToLayerInfo));
}
}
});
//UpdateLayerInfoMap();
GWarn->EndSlowTask();
}
}
void ULandscapeInfo::GetUsedPaintLayers(const FGuid& InLayerGuid, TArray<ULandscapeLayerInfoObject*>& OutUsedLayerInfos) const
{
OutUsedLayerInfos.Empty();
ForAllLandscapeProxies([&](ALandscapeProxy* Proxy)
{
for (ULandscapeComponent* Component : Proxy->LandscapeComponents)
{
Component->GetUsedPaintLayers(InLayerGuid, OutUsedLayerInfos);
}
});
}
void ALandscapeProxy::EditorApplyScale(const FVector& DeltaScale, const FVector* PivotLocation, bool bAltDown, bool bShiftDown, bool bCtrlDown)
{
FVector ModifiedDeltaScale = DeltaScale;
FVector CurrentScale = GetRootComponent()->GetRelativeScale3D();
// Lock X and Y scaling to the same value :
FVector2f XYDeltaScaleAbs(FMath::Abs(DeltaScale.X), FMath::Abs(DeltaScale.Y));
// Preserve the sign of the chosen delta :
bool bFavorX = (XYDeltaScaleAbs.X > XYDeltaScaleAbs.Y);
if (AActor::bUsePercentageBasedScaling)
{
// Correct for attempts to scale to 0 on any axis
float XYDeltaScale = bFavorX ? DeltaScale.X : DeltaScale.Y;
if (XYDeltaScale == -1.0f)
{
XYDeltaScale = -(CurrentScale.X - 1) / CurrentScale.X;
}
if (ModifiedDeltaScale.Z == -1)
{
ModifiedDeltaScale.Z = -(CurrentScale.Z - 1) / CurrentScale.Z;
}
ModifiedDeltaScale.X = ModifiedDeltaScale.Y = XYDeltaScale;
}
else
{
// The absolute value of X and Y must be preserved so make sure they are preserved in case they flip from positive to negative (e.g.: a (-X, X) scale is accepted) :
float SignMultiplier = FMath::Sign(CurrentScale.X) * FMath::Sign(CurrentScale.Y);
FVector2f NewScale(FVector2f::ZeroVector);
if (bFavorX)
{
NewScale.X = CurrentScale.X + DeltaScale.X;
if (NewScale.X == 0.0f)
{
// Correct for attempts to scale to 0 on this axis : doubly-increment the scale to avoid reaching 0 :
NewScale.X += DeltaScale.X;
}
NewScale.Y = SignMultiplier * NewScale.X;
}
else
{
NewScale.Y = CurrentScale.Y + DeltaScale.Y;
if (NewScale.Y == 0.0f)
{
// Correct for attempts to scale to 0 on this axis : doubly-increment the scale to avoid reaching 0 :
NewScale.Y += DeltaScale.Y;
}
NewScale.X = SignMultiplier * NewScale.Y;
}
ModifiedDeltaScale.X = NewScale.X - CurrentScale.X;
ModifiedDeltaScale.Y = NewScale.Y - CurrentScale.Y;
if (ModifiedDeltaScale.Z == -CurrentScale.Z)
{
ModifiedDeltaScale.Z += 1;
}
}
Super::EditorApplyScale(ModifiedDeltaScale, PivotLocation, bAltDown, bShiftDown, bCtrlDown);
// We need to regenerate collision objects, they depend on scale value
for (ULandscapeHeightfieldCollisionComponent* Comp : CollisionComponents)
{
if (Comp)
{
Comp->RecreateCollision();
}
}
}
void ALandscapeProxy::EditorApplyMirror(const FVector& MirrorScale, const FVector& PivotLocation)
{
Super::EditorApplyMirror(MirrorScale, PivotLocation);
// We need to regenerate collision objects, they depend on scale value
for (ULandscapeHeightfieldCollisionComponent* Comp : CollisionComponents)
{
if (Comp)
{
Comp->RecreateCollision();
}
}
}
void ALandscapeProxy::PostEditMove(bool bFinished)
{
// This point is only reached when Copy and Pasted
Super::PostEditMove(bFinished);
if (bFinished && !GetWorld()->IsGameWorld())
{
ULandscapeInfo::RecreateLandscapeInfo(GetWorld(), true);
RecreateComponentsState();
if (SplineComponent)
{
SplineComponent->CheckSplinesValid();
}
}
}
void ALandscapeProxy::PostEditImport()
{
Super::PostEditImport();
// during import this gets called multiple times, without a valid guid the first time
if (LandscapeGuid.IsValid())
{
CreateLandscapeInfo();
}
UpdateAllComponentMaterialInstances();
}
void ALandscape::PostEditMove(bool bFinished)
{
if (bFinished && !GetWorld()->IsGameWorld())
{
// align all proxies to landscape actor
auto* LandscapeInfo = GetLandscapeInfo();
if (LandscapeInfo)
{
LandscapeInfo->FixupProxiesTransform(true);
}
}
// Some edit layers could be affected by BP brushes, which might need to be updated when the landscape is transformed :
RequestLayersContentUpdate(bFinished ? ELandscapeLayerUpdateMode::Update_All : ELandscapeLayerUpdateMode::Update_All_Editing_NoCollision);
Super::PostEditMove(bFinished);
}
void ALandscape::PostEditUndo()
{
Super::PostEditUndo();
RequestLayersContentUpdate(ELandscapeLayerUpdateMode::Update_All);
}
void ALandscape::PostRegisterAllComponents()
{
Super::PostRegisterAllComponents();
auto HasValidBrush = [this]()
{
for (const FLandscapeLayer& Layer : LandscapeLayers)
{
for (const FLandscapeLayerBrush& Brush : Layer.Brushes)
{
if (IsValid(Brush.GetBrush()))
{
return true;
}
}
}
return false;
};
// Until it is properly supported, ALandscape with layer brushes will force all of its proxies to be loaded in editor
if (GEditor && !GetWorld()->IsGameWorld() && LandscapeGuid.IsValid() && HasValidBrush())
{
if (UWorldPartition* WorldPartition = GetWorld()->GetWorldPartition())
{
FWorldPartitionHelpers::ForEachActorDesc<ALandscapeProxy>(WorldPartition, [this, WorldPartition](const FWorldPartitionActorDesc* ActorDesc)
{
FLandscapeActorDesc* LandscapeActorDesc = (FLandscapeActorDesc*)ActorDesc;
if (LandscapeActorDesc->GridGuid == LandscapeGuid)
{
ActorDescReferences.Add(FWorldPartitionReference(WorldPartition, ActorDesc->GetGuid()));
}
return true;
});
}
}
}
void ALandscape::PostActorCreated()
{
Super::PostActorCreated();
// Newly spawned Landscapes always set this value to true
bIncludeGridSizeInNameForLandscapeActors = true;
}
bool ALandscape::ShouldImport(FString* ActorPropString, bool IsMovingLevel)
{
return GetWorld() != nullptr && !GetWorld()->IsGameWorld();
}
void ALandscape::PostEditImport()
{
check(GetWorld() && !GetWorld()->IsGameWorld());
for (ALandscape* Landscape : TActorRange<ALandscape>(GetWorld()))
{
if (Landscape && Landscape != this && !Landscape->HasAnyFlags(RF_BeginDestroyed) && Landscape->LandscapeGuid == LandscapeGuid)
{
// Copy/Paste case, need to generate new GUID
LandscapeGuid = FGuid::NewGuid();
break;
}
}
// Some edit layers could be affected by BP brushes, which might need to be updated when the landscape is transformed :
RequestLayersContentUpdate(ELandscapeLayerUpdateMode::Update_All);
Super::PostEditImport();
}
void ALandscape::PostDuplicate(bool bDuplicateForPIE)
{
Super::PostDuplicate(bDuplicateForPIE);
if (!bDuplicateForPIE)
{
// Some edit layers could be affected by BP brushes, which might need to be updated when the landscape is transformed :
RequestLayersContentUpdate(ELandscapeLayerUpdateMode::Update_All);
}
}
#endif //WITH_EDITOR
ULandscapeLayerInfoObject::ULandscapeLayerInfoObject(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
#if WITH_EDITORONLY_DATA
, IsReferencedFromLoadedData(false)
#endif // WITH_EDITORONLY_DATA
{
Hardness = 0.5f;
#if WITH_EDITORONLY_DATA
MinimumCollisionRelevanceWeight = 0.0f;
bNoWeightBlend = false;
SplineFalloffModulationTexture = nullptr;
SplineFalloffModulationColorMask = ESplineModulationColorMask::Red;
SplineFalloffModulationTiling = 1.0f;
SplineFalloffModulationBias = 0.5;
SplineFalloffModulationScale = 1.0f;
#endif // WITH_EDITORONLY_DATA
// Assign initial LayerUsageDebugColor
if (!IsTemplate())
{
LayerUsageDebugColor = GenerateLayerUsageDebugColor();
}
}
FLinearColor ULandscapeLayerInfoObject::GenerateLayerUsageDebugColor() const
{
uint8 Hash[20];
FString PathNameString = GetPathName();
FSHA1::HashBuffer(*PathNameString, PathNameString.Len() * sizeof(PathNameString[0]), Hash);
return FLinearColor(float(Hash[0]) / 255.f, float(Hash[1]) / 255.f, float(Hash[2]) / 255.f, 1.f);
}
#if WITH_EDITOR
void ULandscapeLayerInfoObject::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
static const FName NAME_Hardness = GET_MEMBER_NAME_CHECKED(ULandscapeLayerInfoObject, Hardness);
static const FName NAME_PhysMaterial = GET_MEMBER_NAME_CHECKED(ULandscapeLayerInfoObject, PhysMaterial);
static const FName NAME_LayerUsageDebugColor = GET_MEMBER_NAME_CHECKED(ULandscapeLayerInfoObject, LayerUsageDebugColor);
static const FName NAME_MinimumCollisionRelevanceWeight = GET_MEMBER_NAME_CHECKED(ULandscapeLayerInfoObject, MinimumCollisionRelevanceWeight);
static const FName NAME_R = FName(TEXT("R"));
static const FName NAME_G = FName(TEXT("G"));
static const FName NAME_B = FName(TEXT("B"));
static const FName NAME_A = FName(TEXT("A"));
Super::PostEditChangeProperty(PropertyChangedEvent);
const FName PropertyName = PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None;
if (GIsEditor)
{
if (PropertyName == NAME_Hardness)
{
Hardness = FMath::Clamp<float>(Hardness, 0.0f, 1.0f);
}
else if (PropertyName == NAME_PhysMaterial || PropertyName == NAME_MinimumCollisionRelevanceWeight)
{
for (TObjectIterator<ALandscapeProxy> It; It; ++It)
{
ALandscapeProxy* Proxy = *It;
if (Proxy->GetWorld() && !Proxy->GetWorld()->IsPlayInEditor())
{
ULandscapeInfo* Info = Proxy->GetLandscapeInfo();
if (Info)
{
for (int32 i = 0; i < Info->Layers.Num(); ++i)
{
if (Info->Layers[i].LayerInfoObj == this)
{
Proxy->ChangedPhysMaterial();
break;
}
}
}
}
}
}
else if (PropertyName == NAME_LayerUsageDebugColor || PropertyName == NAME_R || PropertyName == NAME_G || PropertyName == NAME_B || PropertyName == NAME_A)
{
LayerUsageDebugColor.A = 1.0f;
for (TObjectIterator<ALandscapeProxy> It; It; ++It)
{
ALandscapeProxy* Proxy = *It;
if (Proxy->GetWorld() && !Proxy->GetWorld()->IsPlayInEditor())
{
Proxy->MarkComponentsRenderStateDirty();
}
}
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ULandscapeLayerInfoObject, SplineFalloffModulationTexture) ||
PropertyName == GET_MEMBER_NAME_CHECKED(ULandscapeLayerInfoObject, SplineFalloffModulationColorMask) ||
PropertyName == GET_MEMBER_NAME_CHECKED(ULandscapeLayerInfoObject, SplineFalloffModulationBias) ||
PropertyName == GET_MEMBER_NAME_CHECKED(ULandscapeLayerInfoObject, SplineFalloffModulationScale) ||
PropertyName == GET_MEMBER_NAME_CHECKED(ULandscapeLayerInfoObject, SplineFalloffModulationTiling))
{
for (TObjectIterator<ULandscapeInfo> It; It; ++It)
{
if(ALandscape* Landscape = It->LandscapeActor.Get())
{
Landscape->OnLayerInfoSplineFalloffModulationChanged(this);
}
}
}
}
}
void ULandscapeLayerInfoObject::PostLoad()
{
Super::PostLoad();
if (GIsEditor)
{
if (!HasAnyFlags(RF_Standalone))
{
SetFlags(RF_Standalone);
}
Hardness = FMath::Clamp<float>(Hardness, 0.0f, 1.0f);
}
}
void ALandscapeProxy::RemoveXYOffsets()
{
bool bFoundXYOffset = false;
for (int32 i = 0; i < LandscapeComponents.Num(); ++i)
{
ULandscapeComponent* Comp = LandscapeComponents[i];
if (Comp && Comp->XYOffsetmapTexture)
{
Comp->XYOffsetmapTexture->SetFlags(RF_Transactional);
Comp->XYOffsetmapTexture->Modify();
Comp->XYOffsetmapTexture->MarkPackageDirty();
Comp->XYOffsetmapTexture->ClearFlags(RF_Standalone);
Comp->Modify();
Comp->MarkPackageDirty();
Comp->XYOffsetmapTexture = nullptr;
Comp->MarkRenderStateDirty();
bFoundXYOffset = true;
}
}
if (bFoundXYOffset)
{
RecreateCollisionComponents();
}
}
void ALandscapeProxy::RecreateCollisionComponents()
{
// We can assume these are all junk; they recreate as needed
FlushGrassComponents();
// Clear old CollisionComponent containers
CollisionComponents.Empty();
// Destroy any owned collision components
TInlineComponentArray<ULandscapeHeightfieldCollisionComponent*> CollisionComps;
GetComponents(CollisionComps);
for (ULandscapeHeightfieldCollisionComponent* Component : CollisionComps)
{
Component->DestroyComponent();
}
TArray<USceneComponent*> AttachedCollisionComponents = RootComponent->GetAttachChildren().FilterByPredicate(
[](USceneComponent* Component)
{
return Cast<ULandscapeHeightfieldCollisionComponent>(Component);
});
// Destroy any attached but un-owned collision components
for (USceneComponent* Component : AttachedCollisionComponents)
{
Component->DestroyComponent();
}
// Recreate collision
CollisionMipLevel = FMath::Clamp<int32>(CollisionMipLevel, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
SimpleCollisionMipLevel = FMath::Clamp<int32>(SimpleCollisionMipLevel, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
for (ULandscapeComponent* Comp : LandscapeComponents)
{
if (Comp)
{
Comp->CollisionMipLevel = CollisionMipLevel;
Comp->SimpleCollisionMipLevel = SimpleCollisionMipLevel;
Comp->DestroyCollisionData();
Comp->UpdateCollisionData();
}
}
}
void ULandscapeInfo::RecreateCollisionComponents()
{
ForAllLandscapeProxies([](ALandscapeProxy* Proxy)
{
Proxy->RecreateCollisionComponents();
});
}
void ULandscapeInfo::RemoveXYOffsets()
{
ForAllLandscapeProxies([](ALandscapeProxy* Proxy)
{
Proxy->RemoveXYOffsets();
});
}
void ULandscapeInfo::PostponeTextureBaking()
{
static const int32 PostponeValue = 60; //frames
ForAllLandscapeProxies([](ALandscapeProxy* Proxy)
{
Proxy->UpdateBakedTexturesCountdown = PostponeValue;
});
}
bool ULandscapeInfo::CanHaveLayersContent() const
{
if (ALandscape* Landscape = LandscapeActor.Get())
{
return Landscape->CanHaveLayersContent();
}
return false;
}
void ULandscapeInfo::ClearDirtyData()
{
if (ALandscape* Landscape = LandscapeActor.Get())
{
ForAllLandscapeComponents([=](ULandscapeComponent* InLandscapeComponent)
{
Landscape->ClearDirtyData(InLandscapeComponent);
});
}
}
void ULandscapeInfo::UpdateAllComponentMaterialInstances(bool bInInvalidateCombinationMaterials)
{
ForAllLandscapeProxies([=](ALandscapeProxy* Proxy)
{
Proxy->UpdateAllComponentMaterialInstances(bInInvalidateCombinationMaterials);
});
}
uint32 ULandscapeInfo::GetGridSize(uint32 InGridSizeInComponents) const
{
return InGridSizeInComponents * ComponentSizeQuads;
}
bool ULandscapeInfo::AreNewLandscapeActorsSpatiallyLoaded() const
{
if (ALandscape* Landscape = LandscapeActor.Get())
{
return Landscape->bAreNewLandscapeActorsSpatiallyLoaded;
}
return false;
}
ALandscapeProxy* ULandscapeInfo::MoveComponentsToLevel(const TArray<ULandscapeComponent*>& InComponents, ULevel* TargetLevel, FName NewProxyName)
{
ALandscape* Landscape = LandscapeActor.Get();
check(Landscape != nullptr);
// Make sure references are in a different package (should be fixed up before calling this method)
// Check the Physical Material is same package with Landscape
if (Landscape->DefaultPhysMaterial && Landscape->DefaultPhysMaterial->GetOutermost() == Landscape->GetOutermost())
{
return nullptr;
}
// Check the LayerInfoObjects are not in same package as Landscape
for (int32 i = 0; i < Layers.Num(); ++i)
{
ULandscapeLayerInfoObject* LayerInfo = Layers[i].LayerInfoObj;
if (LayerInfo && LayerInfo->GetOutermost() == Landscape->GetOutermost())
{
return nullptr;
}
}
// Check the Landscape Materials are not in same package as moved components
for (ULandscapeComponent* Component : InComponents)
{
UMaterialInterface* LandscapeMaterial = Component->GetLandscapeMaterial();
if (LandscapeMaterial && LandscapeMaterial->GetOutermost() == Component->GetOutermost())
{
return nullptr;
}
}
ALandscapeProxy* LandscapeProxy = GetLandscapeProxyForLevel(TargetLevel);
bool bSetPositionAndOffset = false;
if (!LandscapeProxy)
{
FActorSpawnParameters SpawnParams;
SpawnParams.Name = NewProxyName;
SpawnParams.OverrideLevel = TargetLevel;
LandscapeProxy = TargetLevel->GetWorld()->SpawnActor<ALandscapeStreamingProxy>(SpawnParams);
// copy shared properties to this new proxy
LandscapeProxy->GetSharedProperties(Landscape);
LandscapeProxy->CreateLandscapeInfo();
LandscapeProxy->SetActorLabel(LandscapeProxy->GetName());
bSetPositionAndOffset = true;
}
return MoveComponentsToProxy(InComponents, LandscapeProxy, bSetPositionAndOffset, TargetLevel);
}
ALandscapeProxy* ULandscapeInfo::MoveComponentsToProxy(const TArray<ULandscapeComponent*>& InComponents, ALandscapeProxy* LandscapeProxy, bool bSetPositionAndOffset, ULevel* TargetLevel)
{
ALandscape* Landscape = LandscapeActor.Get();
check(Landscape != nullptr);
struct FCompareULandscapeComponentBySectionBase
{
FORCEINLINE bool operator()(const ULandscapeComponent& A, const ULandscapeComponent& B) const
{
return (A.GetSectionBase().X == B.GetSectionBase().X) ? (A.GetSectionBase().Y < B.GetSectionBase().Y) : (A.GetSectionBase().X < B.GetSectionBase().X);
}
};
TArray<ULandscapeComponent*> ComponentsToMove(InComponents);
ComponentsToMove.Sort(FCompareULandscapeComponentBySectionBase());
const int32 ComponentSizeVerts = Landscape->NumSubsections * (Landscape->SubsectionSizeQuads + 1);
const int32 NeedHeightmapSize = 1 << FMath::CeilLogTwo(ComponentSizeVerts);
TSet<ALandscapeProxy*> SelectProxies;
TSet<ULandscapeComponent*> TargetSelectedComponents;
TArray<ULandscapeHeightfieldCollisionComponent*> TargetSelectedCollisionComponents;
for (ULandscapeComponent* Component : ComponentsToMove)
{
SelectProxies.Add(Component->GetLandscapeProxy());
if (Component->GetLandscapeProxy() != LandscapeProxy && (!TargetLevel || Component->GetLandscapeProxy()->GetOuter() != TargetLevel))
{
TargetSelectedComponents.Add(Component);
}
ULandscapeHeightfieldCollisionComponent* CollisionComp = Component->CollisionComponent.Get();
SelectProxies.Add(CollisionComp->GetLandscapeProxy());
if (CollisionComp->GetLandscapeProxy() != LandscapeProxy && (!TargetLevel || CollisionComp->GetLandscapeProxy()->GetOuter() != TargetLevel))
{
TargetSelectedCollisionComponents.Add(CollisionComp);
}
}
// Check which heightmap will need to be renewed :
TSet<UTexture2D*> OldHeightmapTextures;
for (ULandscapeComponent* Component : TargetSelectedComponents)
{
Component->Modify();
OldHeightmapTextures.Add(Component->GetHeightmap());
// Also process all edit layers heightmaps :
Component->ForEachLayer([&](const FGuid& LayerGuid, FLandscapeLayerComponentData& LayerData)
{
OldHeightmapTextures.Add(Component->GetHeightmap(LayerGuid));
});
}
// Need to split all the component which share Heightmap with selected components
TMap<ULandscapeComponent*, bool> HeightmapUpdateComponents;
HeightmapUpdateComponents.Reserve(TargetSelectedComponents.Num() * 4); // worst case
for (ULandscapeComponent* Component : TargetSelectedComponents)
{
// Search neighbor only
const int32 SearchX = Component->GetHeightmap()->Source.GetSizeX() / NeedHeightmapSize - 1;
const int32 SearchY = Component->GetHeightmap()->Source.GetSizeY() / NeedHeightmapSize - 1;
const FIntPoint ComponentBase = Component->GetSectionBase() / Component->ComponentSizeQuads;
for (int32 Y = -SearchY; Y <= SearchY; ++Y)
{
for (int32 X = -SearchX; X <= SearchX; ++X)
{
ULandscapeComponent* const Neighbor = XYtoComponentMap.FindRef(ComponentBase + FIntPoint(X, Y));
if (Neighbor && Neighbor->GetHeightmap() == Component->GetHeightmap() && !HeightmapUpdateComponents.Contains(Neighbor))
{
Neighbor->Modify();
bool bNeedsMoveToCurrentLevel = TargetSelectedComponents.Contains(Neighbor);
HeightmapUpdateComponents.Add(Neighbor, bNeedsMoveToCurrentLevel);
}
}
}
}
// Proxy position/offset needs to be set
if(bSetPositionAndOffset)
{
// set proxy location
// by default first component location
ULandscapeComponent* FirstComponent = *TargetSelectedComponents.CreateConstIterator();
LandscapeProxy->GetRootComponent()->SetWorldLocationAndRotation(FirstComponent->GetComponentLocation(), FirstComponent->GetComponentRotation());
LandscapeProxy->LandscapeSectionOffset = FirstComponent->GetSectionBase();
}
// Hide(unregister) the new landscape if owning level currently in hidden state
if (LandscapeProxy->GetLevel()->bIsVisible == false)
{
LandscapeProxy->UnregisterAllComponents();
}
// Changing Heightmap format for selected components
for (const auto& HeightmapUpdateComponentPair : HeightmapUpdateComponents)
{
ALandscape::SplitHeightmap(HeightmapUpdateComponentPair.Key, HeightmapUpdateComponentPair.Value ? LandscapeProxy : nullptr);
}
// Delete if textures are not referenced anymore...
for (UTexture2D* Texture : OldHeightmapTextures)
{
check(Texture != nullptr);
Texture->SetFlags(RF_Transactional);
Texture->Modify();
Texture->MarkPackageDirty();
Texture->ClearFlags(RF_Standalone);
}
for (ALandscapeProxy* Proxy : SelectProxies)
{
Proxy->Modify();
}
LandscapeProxy->Modify();
LandscapeProxy->MarkPackageDirty();
// Handle XY-offset textures (these don't need splitting, as they aren't currently shared between components like heightmaps/weightmaps can be)
for (ULandscapeComponent* Component : TargetSelectedComponents)
{
if (Component->XYOffsetmapTexture)
{
Component->XYOffsetmapTexture->Modify();
Component->XYOffsetmapTexture->Rename(nullptr, LandscapeProxy);
}
}
// Change Weight maps...
{
FLandscapeEditDataInterface LandscapeEdit(this);
for (ULandscapeComponent* Component : TargetSelectedComponents)
{
Component->ReallocateWeightmaps(&LandscapeEdit, false, true, true, LandscapeProxy);
Component->ForEachLayer([&](const FGuid& LayerGuid, FLandscapeLayerComponentData& LayerData)
{
FScopedSetLandscapeEditingLayer Scope(Landscape, LayerGuid);
Component->ReallocateWeightmaps(&LandscapeEdit, true, true, true, LandscapeProxy);
});
Landscape->RequestLayersContentUpdateForceAll();
}
// Need to re-pack all the Weight map (to have it optimally re-packed...)
for (ALandscapeProxy* Proxy : SelectProxies)
{
Proxy->RemoveInvalidWeightmaps();
}
}
// Move the components to the Proxy actor
// This does not use the MoveSelectedActorsToCurrentLevel path as there is no support to only move certain components.
for (ULandscapeComponent* Component : TargetSelectedComponents)
{
// Need to move or recreate all related data (Height map, Weight map, maybe collision components, allocation info)
Component->GetLandscapeProxy()->LandscapeComponents.Remove(Component);
Component->UnregisterComponent();
Component->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
Component->InvalidateLightingCache();
Component->Rename(nullptr, LandscapeProxy);
LandscapeProxy->LandscapeComponents.Add(Component);
Component->AttachToComponent(LandscapeProxy->GetRootComponent(), FAttachmentTransformRules::KeepWorldTransform);
// clear transient mobile data
Component->MobileDataSourceHash.Invalidate();
Component->MobileMaterialInterfaces.Reset();
Component->MobileWeightmapTextures.Reset();
Component->UpdateMaterialInstances();
}
LandscapeProxy->UpdateCachedHasLayersContent();
for (ULandscapeHeightfieldCollisionComponent* Component : TargetSelectedCollisionComponents)
{
// Need to move or recreate all related data (Height map, Weight map, maybe collision components, allocation info)
Component->GetLandscapeProxy()->CollisionComponents.Remove(Component);
Component->UnregisterComponent();
Component->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
Component->Rename(nullptr, LandscapeProxy);
LandscapeProxy->CollisionComponents.Add(Component);
Component->AttachToComponent(LandscapeProxy->GetRootComponent(), FAttachmentTransformRules::KeepWorldTransform);
// Move any foliage associated
AInstancedFoliageActor::MoveInstancesForComponentToLevel(Component, LandscapeProxy->GetLevel());
}
// Register our new components if destination landscape is registered in scene
if (LandscapeProxy->GetRootComponent()->IsRegistered())
{
LandscapeProxy->RegisterAllComponents();
}
for (ALandscapeProxy* Proxy : SelectProxies)
{
if (Proxy->GetRootComponent()->IsRegistered())
{
Proxy->RegisterAllComponents();
}
}
return LandscapeProxy;
}
void ALandscape::SplitHeightmap(ULandscapeComponent* Comp, ALandscapeProxy* TargetProxy, FMaterialUpdateContext* InOutUpdateContext, TArray<FComponentRecreateRenderStateContext>* InOutRecreateRenderStateContext, bool InReregisterComponent)
{
ULandscapeInfo* Info = Comp->GetLandscapeInfo();
// Make sure the heightmap UVs are powers of two.
int32 ComponentSizeVerts = Comp->NumSubsections * (Comp->SubsectionSizeQuads + 1);
int32 HeightmapSizeU = (1 << FMath::CeilLogTwo(ComponentSizeVerts));
int32 HeightmapSizeV = (1 << FMath::CeilLogTwo(ComponentSizeVerts));
ALandscapeProxy* SrcProxy = Comp->GetLandscapeProxy();
ALandscapeProxy* DstProxy = TargetProxy ? TargetProxy : SrcProxy;
SrcProxy->Modify();
DstProxy->Modify();
UTexture2D* OldHeightmapTexture = Comp->GetHeightmap(false);
UTexture2D* NewHeightmapTexture = NULL;
FVector4 OldHeightmapScaleBias = Comp->HeightmapScaleBias;
FVector4 NewHeightmapScaleBias = FVector4(1.0f / (float)HeightmapSizeU, 1.0f / (float)HeightmapSizeV, 0.0f, 0.0f);
{
// Read old data and split
FLandscapeEditDataInterface LandscapeEdit(Info);
TArray<uint8> HeightData;
HeightData.AddZeroed((1 + Comp->ComponentSizeQuads) * (1 + Comp->ComponentSizeQuads) * sizeof(uint16));
// Because of edge problem, normal would be just copy from old component data
TArray<uint8> NormalData;
NormalData.AddZeroed((1 + Comp->ComponentSizeQuads) * (1 + Comp->ComponentSizeQuads) * sizeof(uint16));
LandscapeEdit.GetHeightDataFast(Comp->GetSectionBase().X, Comp->GetSectionBase().Y, Comp->GetSectionBase().X + Comp->ComponentSizeQuads, Comp->GetSectionBase().Y + Comp->ComponentSizeQuads, (uint16*)HeightData.GetData(), 0, (uint16*)NormalData.GetData());
// Create the new heightmap texture
NewHeightmapTexture = DstProxy->CreateLandscapeTexture(HeightmapSizeU, HeightmapSizeV, TEXTUREGROUP_Terrain_Heightmap, TSF_BGRA8);
ULandscapeComponent::CreateEmptyTextureMips(NewHeightmapTexture, true);
NewHeightmapTexture->PostEditChange();
Comp->HeightmapScaleBias = NewHeightmapScaleBias;
Comp->SetHeightmap(NewHeightmapTexture);
check(Comp->GetHeightmap(false) == Comp->GetHeightmap(true));
LandscapeEdit.SetHeightData(Comp->GetSectionBase().X, Comp->GetSectionBase().Y, Comp->GetSectionBase().X + Comp->ComponentSizeQuads, Comp->GetSectionBase().Y + Comp->ComponentSizeQuads, (uint16*)HeightData.GetData(), 0, false, (uint16*)NormalData.GetData());
}
// End material update
if (InOutUpdateContext != nullptr && InOutRecreateRenderStateContext != nullptr)
{
Comp->UpdateMaterialInstances(*InOutUpdateContext, *InOutRecreateRenderStateContext);
}
else
{
Comp->UpdateMaterialInstances();
}
// We disable automatic material update context, to manage it manually if we have a custom update context specified
GDisableAutomaticTextureMaterialUpdateDependencies = (InOutUpdateContext != nullptr);
if (InOutUpdateContext != nullptr)
{
// Build a list of all unique materials the landscape uses
TArray<UMaterialInterface*> LandscapeMaterials;
int8 MaxLOD = FMath::CeilLogTwo(Comp->SubsectionSizeQuads + 1) - 1;
for (int8 LODIndex = 0; LODIndex < MaxLOD; ++LODIndex)
{
UMaterialInterface* Material = Comp->GetLandscapeMaterial(LODIndex);
LandscapeMaterials.AddUnique(Material);
}
TSet<UMaterial*> BaseMaterialsThatUseThisTexture;
for (UMaterialInterface* MaterialInterface : LandscapeMaterials)
{
if (DoesMaterialUseTexture(MaterialInterface, NewHeightmapTexture))
{
UMaterial* Material = MaterialInterface->GetMaterial();
bool MaterialAlreadyCompute = false;
BaseMaterialsThatUseThisTexture.Add(Material, &MaterialAlreadyCompute);
if (!MaterialAlreadyCompute)
{
if (Material->IsTextureForceRecompileCacheRessource(NewHeightmapTexture))
{
InOutUpdateContext->AddMaterial(Material);
Material->UpdateMaterialShaderCacheAndTextureReferences();
}
}
}
}
}
GDisableAutomaticTextureMaterialUpdateDependencies = false;
#if WITH_EDITORONLY_DATA
check(Comp->GetLandscapeProxy()->HasLayersContent() == DstProxy->CanHaveLayersContent());
if (Comp->GetLandscapeProxy()->HasLayersContent() && DstProxy->CanHaveLayersContent())
{
FLandscapeEditLayerReadback* NewCPUReadback = new FLandscapeEditLayerReadback();
DstProxy->HeightmapsCPUReadback.Add(NewHeightmapTexture, NewCPUReadback);
// Free OldHeightmapTexture's CPUReadBackResource if not used by any component
bool FreeCPUReadBack = true;
for (ULandscapeComponent* Component : SrcProxy->LandscapeComponents)
{
if (Component != Comp && Component->GetHeightmap(false) == OldHeightmapTexture)
{
FreeCPUReadBack = false;
break;
}
}
if (FreeCPUReadBack)
{
FLandscapeEditLayerReadback** OldCPUReadback = SrcProxy->HeightmapsCPUReadback.Find(OldHeightmapTexture);
if (OldCPUReadback)
{
if (FLandscapeEditLayerReadback* ResourceToDelete = *OldCPUReadback)
{
delete ResourceToDelete;
SrcProxy->HeightmapsCPUReadback.Remove(OldHeightmapTexture);
}
}
}
// Move layer content to new layer heightmap
FLandscapeEditDataInterface LandscapeEdit(Info);
ALandscape* Landscape = Info->LandscapeActor.Get();
Comp->ForEachLayer([&](const FGuid& LayerGuid, FLandscapeLayerComponentData& LayerData)
{
UTexture2D* OldLayerHeightmap = LayerData.HeightmapData.Texture;
if (OldLayerHeightmap != nullptr)
{
FScopedSetLandscapeEditingLayer Scope(Landscape, LayerGuid);
// Read old data and split
TArray<uint8> LayerHeightData;
LayerHeightData.AddZeroed((1 + Comp->ComponentSizeQuads) * (1 + Comp->ComponentSizeQuads) * sizeof(uint16));
// Because of edge problem, normal would be just copy from old component data
TArray<uint8> LayerNormalData;
LayerNormalData.AddZeroed((1 + Comp->ComponentSizeQuads) * (1 + Comp->ComponentSizeQuads) * sizeof(uint16));
// Read using old heightmap scale/bias
Comp->HeightmapScaleBias = OldHeightmapScaleBias;
LandscapeEdit.GetHeightDataFast(Comp->GetSectionBase().X, Comp->GetSectionBase().Y, Comp->GetSectionBase().X + Comp->ComponentSizeQuads, Comp->GetSectionBase().Y + Comp->ComponentSizeQuads, (uint16*)LayerHeightData.GetData(), 0, (uint16*)LayerNormalData.GetData());
// Restore new heightmap scale/bias
Comp->HeightmapScaleBias = NewHeightmapScaleBias;
{
UTexture2D* LayerHeightmapTexture = DstProxy->CreateLandscapeTexture(HeightmapSizeU, HeightmapSizeV, TEXTUREGROUP_Terrain_Heightmap, TSF_BGRA8);
ULandscapeComponent::CreateEmptyTextureMips(LayerHeightmapTexture, true);
LayerHeightmapTexture->PostEditChange();
// Set Layer heightmap texture
LayerData.HeightmapData.Texture = LayerHeightmapTexture;
LandscapeEdit.SetHeightData(Comp->GetSectionBase().X, Comp->GetSectionBase().Y, Comp->GetSectionBase().X + Comp->ComponentSizeQuads, Comp->GetSectionBase().Y + Comp->ComponentSizeQuads, (uint16*)LayerHeightData.GetData(), 0, false, (uint16*)LayerNormalData.GetData());
}
}
});
Landscape->RequestLayersContentUpdateForceAll();
}
#endif
// Reregister
if (InReregisterComponent)
{
FComponentReregisterContext ReregisterContext(Comp);
}
}
namespace
{
inline float AdjustStaticLightingResolution(float StaticLightingResolution, int32 NumSubsections, int32 SubsectionSizeQuads, int32 ComponentSizeQuads)
{
// Change Lighting resolution to proper one...
if (StaticLightingResolution > 1.0f)
{
StaticLightingResolution = (int32)StaticLightingResolution;
}
else if (StaticLightingResolution < 1.0f)
{
// Restrict to 1/16
if (StaticLightingResolution < 0.0625)
{
StaticLightingResolution = 0.0625;
}
// Adjust to 1/2^n
int32 i = 2;
int32 LightmapSize = (NumSubsections * (SubsectionSizeQuads + 1)) >> 1;
while (StaticLightingResolution < (1.0f / i) && LightmapSize > 4)
{
i <<= 1;
LightmapSize >>= 1;
}
StaticLightingResolution = 1.0f / i;
int32 PixelPaddingX = GPixelFormats[PF_DXT1].BlockSizeX;
int32 DestSize = (int32)((2 * PixelPaddingX + ComponentSizeQuads + 1) * StaticLightingResolution);
StaticLightingResolution = (float)DestSize / (2 * PixelPaddingX + ComponentSizeQuads + 1);
}
return StaticLightingResolution;
}
};
bool ALandscapeProxy::CanEditChange(const FProperty* InProperty) const
{
if (!Super::CanEditChange(InProperty))
{
return false;
}
if (IsTemplate())
{
return true;
}
// Don't allow edition of properties that are shared with the parent landscape properties
// See ALandscapeProxy::FixupSharedData(ALandscape* Landscape)
if (GetLandscapeActor() != this)
{
FName PropertyName = InProperty ? InProperty->GetFName() : NAME_None;
if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, MaxLODLevel) ||
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, ComponentScreenSizeToUseSubSections) ||
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LODDistributionSetting) ||
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LOD0DistributionSetting) ||
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LOD0ScreenSize) ||
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, TargetDisplayOrder) ||
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, TargetDisplayOrderList))
{
return false;
}
}
return true;
}
void ALandscapeProxy::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
const FName PropertyName = PropertyChangedEvent.MemberProperty ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None;
const FName SubPropertyName = PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None;
bool bChangedPhysMaterial = false;
if (PropertyName == FName(TEXT("RelativeScale3D")))
{
// RelativeScale3D isn't even a property of ALandscapeProxy, it's a property of the root component
if (RootComponent)
{
const FVector OriginalScale = RootComponent->GetRelativeScale3D();
FVector ModifiedScale = OriginalScale;
// Lock X and Y scaling to the same value
if (SubPropertyName == FName("Y"))
{
ModifiedScale.X = FMath::Abs(OriginalScale.Y)*FMath::Sign(ModifiedScale.X);
}
else if (SubPropertyName == FName("X"))
{
ModifiedScale.Y = FMath::Abs(OriginalScale.X)*FMath::Sign(ModifiedScale.Y);
}
ULandscapeInfo* Info = GetLandscapeInfo();
// Correct for attempts to scale to 0 on any axis
if (ModifiedScale.X == 0)
{
if (Info && Info->DrawScale.X < 0)
{
ModifiedScale.Y = ModifiedScale.X = -1;
}
else
{
ModifiedScale.Y = ModifiedScale.X = 1;
}
}
if (ModifiedScale.Z == 0)
{
if (Info && Info->DrawScale.Z < 0)
{
ModifiedScale.Z = -1;
}
else
{
ModifiedScale.Z = 1;
}
}
RootComponent->SetRelativeScale3D(ModifiedScale);
// Update ULandscapeInfo cached DrawScale
if (Info)
{
Info->DrawScale = ModifiedScale;
}
// We need to regenerate collision objects, they depend on scale value
if (PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive)
{
for (int32 ComponentIndex = 0; ComponentIndex < CollisionComponents.Num(); ComponentIndex++)
{
ULandscapeHeightfieldCollisionComponent* Comp = CollisionComponents[ComponentIndex];
if (Comp)
{
Comp->RecreateCollision();
}
}
}
}
}
if (GIsEditor && PropertyName == FName(TEXT("StreamingDistanceMultiplier")))
{
// Recalculate in a few seconds.
GetWorld()->TriggerStreamingDataRebuild();
}
else if (GIsEditor && PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, DefaultPhysMaterial))
{
bChangedPhysMaterial = true;
}
else if (GIsEditor &&
(PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, CollisionMipLevel) ||
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, SimpleCollisionMipLevel) ||
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, CollisionThickness) ||
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bBakeMaterialPositionOffsetIntoCollision) ||
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bGenerateOverlapEvents)))
{
if (bBakeMaterialPositionOffsetIntoCollision)
{
MarkComponentsRenderStateDirty();
}
if (PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive)
{
RecreateCollisionComponents();
}
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, ComponentScreenSizeToUseSubSections))
{
ChangeComponentScreenSizeToUseSubSections(ComponentScreenSizeToUseSubSections);
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LODDistributionSetting)
|| PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LOD0DistributionSetting)
|| PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LOD0ScreenSize))
{
MarkComponentsRenderStateDirty();
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bUseMaterialPositionOffsetInStaticLighting))
{
InvalidateLightingCache();
}
else if ((PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, CastShadow))
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bCastDynamicShadow))
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bCastStaticShadow))
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bCastFarShadow))
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bCastHiddenShadow))
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bCastShadowAsTwoSided))
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bAffectDistanceFieldLighting))
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bRenderCustomDepth))
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, CustomDepthStencilWriteMask))
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, CustomDepthStencilValue))
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LightingChannels))
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LDMaxDrawDistance))
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bUsedForNavigation))
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bFillCollisionUnderLandscapeForNavmesh)))
{
// Replicate shared properties to all components.
for (int32 ComponentIndex = 0; ComponentIndex < LandscapeComponents.Num(); ComponentIndex++)
{
ULandscapeComponent* Comp = LandscapeComponents[ComponentIndex];
if (Comp)
{
Comp->UpdatedSharedPropertiesFromActor();
}
}
}
else if (GIsEditor &&
(PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bMeshHoles) || PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, MeshHolesMaxLod)))
{
CheckGenerateLandscapePlatformData(false, nullptr);
MarkComponentsRenderStateDirty();
}
else if (PropertyName == FName(TEXT("bUseDynamicMaterialInstance")))
{
MarkComponentsRenderStateDirty();
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, RuntimeVirtualTextures)
|| PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, VirtualTextureRenderPassType)
|| PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, VirtualTextureNumLods)
|| PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, VirtualTextureLodBias))
{
MarkComponentsRenderStateDirty();
}
// Remove null layer infos
EditorLayerSettings.RemoveAll([](const FLandscapeEditorLayerSettings& Entry) { return Entry.LayerInfoObj == nullptr; });
// Remove any null landscape components
LandscapeComponents.RemoveAll([](const ULandscapeComponent* Component) { return Component == nullptr; });
// Must do this *after* correcting the scale or reattaching the landscape components will crash!
// Must do this *after* clamping values / propogating values to components
Super::PostEditChangeProperty(PropertyChangedEvent);
// Call that posteditchange when components are registered
if (bChangedPhysMaterial)
{
ChangedPhysMaterial();
}
}
bool ALandscapeStreamingProxy::CanEditChange(const FProperty* InProperty) const
{
if (!Super::CanEditChange(InProperty))
{
return false;
}
if (InProperty && InProperty->GetFName() == GET_MEMBER_NAME_CHECKED(ALandscapeStreamingProxy, LandscapeActor))
{
return !GetWorld()->GetSubsystem<ULandscapeSubsystem>()->IsGridBased();
}
return true;
}
void ALandscapeStreamingProxy::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
const FName PropertyName = PropertyChangedEvent.MemberProperty ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None;
if (PropertyName == FName(TEXT("LandscapeActor")))
{
if (LandscapeActor && IsValidLandscapeActor(LandscapeActor.Get()))
{
LandscapeGuid = LandscapeActor->GetLandscapeGuid();
if (GIsEditor && GetWorld() && !GetWorld()->IsPlayInEditor())
{
// TODO - only need to refresh the old and new landscape info
ULandscapeInfo::RecreateLandscapeInfo(GetWorld(), false);
FixupWeightmaps();
InitializeProxyLayersWeightmapUsage();
}
}
else
{
LandscapeActor = nullptr;
}
}
else if (PropertyName == FName(TEXT("LandscapeMaterial")) || PropertyName == FName(TEXT("LandscapeHoleMaterial")) || PropertyName == FName(TEXT("PerLODOverrideMaterials")))
{
bool RecreateMaterialInstances = true;
if (PropertyName == FName(TEXT("PerLODOverrideMaterials")) && PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayAdd)
{
RecreateMaterialInstances = false;
}
if (RecreateMaterialInstances)
{
{
FMaterialUpdateContext MaterialUpdateContext;
GetLandscapeInfo()->UpdateLayerInfoMap(/*this*/);
// Clear the parents out of combination material instances
for (const auto& MICPair : MaterialInstanceConstantMap)
{
UMaterialInstanceConstant* MaterialInstance = MICPair.Value;
MaterialInstance->BasePropertyOverrides.bOverride_BlendMode = false;
MaterialInstance->SetParentEditorOnly(nullptr);
MaterialUpdateContext.AddMaterialInstance(MaterialInstance);
}
// Remove our references to any material instances
MaterialInstanceConstantMap.Empty();
}
UpdateAllComponentMaterialInstances();
UWorld* World = GetWorld();
if (World != nullptr && UseMobileLandscapeMesh(GShaderPlatformForFeatureLevel[World->FeatureLevel]))
{
for (ULandscapeComponent * Component : LandscapeComponents)
{
if (Component != nullptr)
{
Component->CheckGenerateLandscapePlatformData(false, nullptr);
}
}
}
}
}
// Must do this *after* clamping values
Super::PostEditChangeProperty(PropertyChangedEvent);
}
void ALandscapeStreamingProxy::PostRegisterAllComponents()
{
ALandscapeProxy::PostRegisterAllComponents();
if (LandscapeGuid.IsValid())
{
ULandscapeInfo* LandscapeInfo = GetLandscapeInfo();
check(LandscapeInfo);
if (GEditor && !GetWorld()->IsGameWorld())
{
if (UWorldPartition* WorldPartition = GetWorld()->GetWorldPartition())
{
const FVector ActorLocation = GetActorLocation();
const FBox Bounds(ActorLocation, ActorLocation + (GridSize * LandscapeInfo->DrawScale));
FWorldPartitionHelpers::ForEachActorDesc<ALandscapeSplineActor>(WorldPartition, [this, WorldPartition, &Bounds](const FWorldPartitionActorDesc* ActorDesc) mutable
{
FLandscapeSplineActorDesc* LandscapeSplineActorDesc = (FLandscapeSplineActorDesc*)ActorDesc;
if (LandscapeSplineActorDesc->LandscapeGuid == LandscapeGuid)
{
if (Bounds.IntersectXY(ActorDesc->GetBounds()))
{
ActorDescReferences.Add(FWorldPartitionReference(WorldPartition, ActorDesc->GetGuid()));
}
}
return true;
});
}
}
}
}
AActor* ALandscapeStreamingProxy::GetSceneOutlinerParent() const
{
if (ULandscapeInfo* LandscapeInfo = GetLandscapeInfo())
{
return LandscapeInfo->LandscapeActor.Get();
}
return Super::GetSceneOutlinerParent();
}
bool ALandscapeStreamingProxy::CanDeleteSelectedActor(FText& OutReason) const
{
return true;
}
bool ALandscapeStreamingProxy::GetReferencedContentObjects(TArray<UObject*>& Objects) const
{
Super::GetReferencedContentObjects(Objects);
// Also return the objects referenced by our parent landscape :
if (LandscapeActor != nullptr)
{
LandscapeActor->GetReferencedContentObjects(Objects);
}
return true;
}
bool ALandscapeStreamingProxy::ShouldIncludeGridSizeInName(UWorld* InWorld, const FActorPartitionIdentifier& InIdentifier) const
{
// Always return true if this world setting flag is true
if (Super::ShouldIncludeGridSizeInName(InWorld, InIdentifier))
{
return true;
}
if (ULandscapeInfo* LandscapeInfo = ULandscapeInfo::Find(InWorld, InIdentifier.GetGridGuid()))
{
if (ALandscape* Landscape = LandscapeInfo->LandscapeActor.Get())
{
// This new flag is to support Landscapes that were created with bIncludeGridSizeInNameForPartitionedActors == false or
// that were reconfigured with FLandscapeConfigHelper::ChangeGridSize
return Landscape->bIncludeGridSizeInNameForLandscapeActors;
}
}
return false;
}
bool ALandscape::CanDeleteSelectedActor(FText& OutReason) const
{
if (!IsUserManaged())
{
// Allow Delete of Actor if all other related actors have been deleted
ULandscapeInfo* Info = GetLandscapeInfo();
check(Info);
return Info->CanDeleteLandscape(OutReason);
}
return true;
}
bool ULandscapeInfo::CanDeleteLandscape(FText& OutReason) const
{
check(LandscapeActor != nullptr);
int32 UndeletedProxyCount = 0;
int32 UndeletedSplineCount = 0;
// Check Registered Proxies
for (ALandscapeProxy* RegisteredProxy : Proxies)
{
if (RegisteredProxy == LandscapeActor)
{
continue;
}
check(IsValidChecked(RegisteredProxy));
UndeletedProxyCount++;
}
// Then check for Unloaded Proxies
if (AActor* Actor = LandscapeActor.Get())
{
UWorld* World = Actor->GetWorld();
if (UWorldPartition* WorldPartition = World->GetWorldPartition())
{
FWorldPartitionHelpers::ForEachActorDesc<ALandscapeProxy>(WorldPartition, [this, &UndeletedProxyCount](const FWorldPartitionActorDesc* ActorDesc)
{
FLandscapeActorDesc* LandscapeActorDesc = (FLandscapeActorDesc*)ActorDesc;
if (LandscapeActorDesc->GridGuid == LandscapeGuid)
{
ALandscapeProxy* LandscapeProxy = Cast<ALandscapeProxy>(ActorDesc->GetActor());
if (LandscapeProxy != LandscapeActor)
{
// If LandscapeProxy is null then it is not loaded so not deleted.
if (!LandscapeProxy)
{
++UndeletedProxyCount;
}
else
{
// If Actor is loaded it should be Registered and not pending kill (already accounted for) or pending kill (deleted)
check(Proxies.Contains(LandscapeProxy) == IsValidChecked(LandscapeProxy));
}
}
}
return true;
});
}
}
// Check Registered Splines
for (TScriptInterface<ILandscapeSplineInterface> SplineOwner : SplineActors)
{
// Only check for ALandscapeSplineActor type because ALandscapeProxy also implement the ILandscapeSplineInterface for non WP worlds
if(ALandscapeSplineActor* SplineActor = Cast<ALandscapeSplineActor>(SplineOwner.GetObject()))
{
check(IsValidChecked(SplineActor));
UndeletedSplineCount++;
}
}
// Then check for Unloaded Splines
if (AActor* Actor = LandscapeActor.Get())
{
UWorld* World = Actor->GetWorld();
if (UWorldPartition* WorldPartition = World->GetWorldPartition())
{
FWorldPartitionHelpers::ForEachActorDesc<ALandscapeSplineActor>(WorldPartition, [this, &UndeletedSplineCount](const FWorldPartitionActorDesc* ActorDesc)
{
FLandscapeSplineActorDesc* LandscapeSplineActorDesc = (FLandscapeSplineActorDesc*)ActorDesc;
if (LandscapeSplineActorDesc->LandscapeGuid == LandscapeGuid)
{
ALandscapeSplineActor* SplineActor = Cast<ALandscapeSplineActor>(LandscapeSplineActorDesc->GetActor());
// If SplineActor is null then it is not loaded/deleted. If it's loaded then it needs to be pending kill.
if (!SplineActor)
{
++UndeletedSplineCount;
}
else
{
// If Actor is loaded it should be Registered and not pending kill (already accounted for) or pending kill (deleted)
check(SplineActors.Contains(SplineActor) == IsValidChecked(SplineActor));
}
}
return true;
});
}
}
if (UndeletedProxyCount > 0 || UndeletedSplineCount > 0)
{
OutReason = FText::Format(LOCTEXT("CanDeleteLandscapeReason", "Landscape can't be deleted because it still has {0} LandscapeStreamingProxies and {1} LandscapeSplineActors"), FText::AsNumber(UndeletedProxyCount), FText::AsNumber(UndeletedSplineCount));
return false;
}
return true;
}
void ALandscape::PreEditChange(FProperty* PropertyThatWillChange)
{
PreEditLandscapeMaterial = LandscapeMaterial;
PreEditLandscapeHoleMaterial = LandscapeHoleMaterial;
PreEditPerLODOverrideMaterials = PerLODOverrideMaterials;
Super::PreEditChange(PropertyThatWillChange);
}
void ALandscape::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
const FName PropertyName = PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None;
const FName MemberPropertyName = PropertyChangedEvent.MemberProperty ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None;
bool ChangedMaterial = false;
bool bNeedsRecalcBoundingBox = false;
bool bChangedLighting = false;
bool bPropagateToProxies = false;
ULandscapeInfo* Info = GetLandscapeInfo();
if ((PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LandscapeMaterial) || PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LandscapeHoleMaterial) || MemberPropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, PerLODOverrideMaterials))
&& PropertyChangedEvent.ChangeType != EPropertyChangeType::ArrayAdd)
{
bool HasMaterialChanged = false;
if (PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive)
{
if (PreEditLandscapeMaterial != LandscapeMaterial || PreEditLandscapeHoleMaterial != LandscapeHoleMaterial || PreEditPerLODOverrideMaterials.Num() != PerLODOverrideMaterials.Num() || bIsPerformingInteractiveActionOnLandscapeMaterialOverride)
{
HasMaterialChanged = true;
}
if (!HasMaterialChanged)
{
for (int32 i = 0; i < PerLODOverrideMaterials.Num(); ++i)
{
const FLandscapePerLODMaterialOverride& NewMaterialOverride = PerLODOverrideMaterials[i];
const FLandscapePerLODMaterialOverride& PreEditMaterialOverride = PreEditPerLODOverrideMaterials[i];
if (!(PreEditMaterialOverride == NewMaterialOverride))
{
HasMaterialChanged = true;
break;
}
}
}
bIsPerformingInteractiveActionOnLandscapeMaterialOverride = false;
}
else
{
// We are probably using a slider or something similar in PerLODOverrideMaterials
bIsPerformingInteractiveActionOnLandscapeMaterialOverride = MemberPropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, PerLODOverrideMaterials);
}
if (Info != nullptr && HasMaterialChanged)
{
FMaterialUpdateContext MaterialUpdateContext;
Info->UpdateLayerInfoMap(/*this*/);
ChangedMaterial = true;
// Clear the parents out of combination material instances
for (const auto& MICPair : MaterialInstanceConstantMap)
{
UMaterialInstanceConstant* MaterialInstance = MICPair.Value;
MaterialInstance->BasePropertyOverrides.bOverride_BlendMode = false;
MaterialInstance->SetParentEditorOnly(nullptr);
MaterialUpdateContext.AddMaterialInstance(MaterialInstance);
}
// Remove our references to any material instances
MaterialInstanceConstantMap.Empty();
}
}
else if (MemberPropertyName == FName(TEXT("RelativeScale3D")) ||
MemberPropertyName == FName(TEXT("RelativeLocation")) ||
MemberPropertyName == FName(TEXT("RelativeRotation")))
{
if (Info != nullptr)
{
// update transformations for all linked proxies
Info->FixupProxiesTransform(true);
bNeedsRecalcBoundingBox = true;
}
// Some edit layers could be affected by BP brushes, which might need to be updated when the landscape is transformed :
RequestLayersContentUpdate(ELandscapeLayerUpdateMode::Update_All);
}
else if (GIsEditor && PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, MaxLODLevel))
{
MaxLODLevel = FMath::Clamp<int32>(MaxLODLevel, -1, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
bPropagateToProxies = true;
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, ComponentScreenSizeToUseSubSections))
{
ComponentScreenSizeToUseSubSections = FMath::Clamp<float>(ComponentScreenSizeToUseSubSections, 0.01f, 1.0f);
bPropagateToProxies = true;
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LODDistributionSetting))
{
LODDistributionSetting = FMath::Clamp<float>(LODDistributionSetting, 1.0f, 10.0f);
bPropagateToProxies = true;
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LOD0DistributionSetting))
{
LOD0DistributionSetting = FMath::Clamp<float>(LOD0DistributionSetting, 1.0f, 10.0f);
bPropagateToProxies = true;
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LOD0ScreenSize))
{
LOD0ScreenSize = FMath::Clamp<float>(LOD0ScreenSize, 0.1f, 10.0f);
bPropagateToProxies = true;
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, CollisionMipLevel))
{
CollisionMipLevel = FMath::Clamp<int32>(CollisionMipLevel, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
bPropagateToProxies = true;
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, DefaultPhysMaterial))
{
bPropagateToProxies = true;
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, SimpleCollisionMipLevel))
{
SimpleCollisionMipLevel = FMath::Clamp<int32>(SimpleCollisionMipLevel, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
bPropagateToProxies = true;
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bBakeMaterialPositionOffsetIntoCollision))
{
bPropagateToProxies = true;
}
else if (
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, RuntimeVirtualTextures) ||
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, VirtualTextureRenderPassType) ||
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, VirtualTextureNumLods) ||
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, VirtualTextureLodBias))
{
bPropagateToProxies = true;
}
else if (GIsEditor && PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, StaticLightingResolution))
{
StaticLightingResolution = ::AdjustStaticLightingResolution(StaticLightingResolution, NumSubsections, SubsectionSizeQuads, ComponentSizeQuads);
bChangedLighting = true;
}
else if (GIsEditor && PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, StaticLightingLOD))
{
StaticLightingLOD = FMath::Clamp<int32>(StaticLightingLOD, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
bChangedLighting = true;
}
else if (GIsEditor && PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, ExportLOD))
{
ExportLOD = FMath::Clamp<int32>(ExportLOD, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
}
// Must do this *after* clamping values
Super::PostEditChangeProperty(PropertyChangedEvent);
bPropagateToProxies = bPropagateToProxies || bNeedsRecalcBoundingBox || bChangedLighting;
if (Info != nullptr)
{
if (bPropagateToProxies)
{
// Propagate Event to Proxies...
for (ALandscapeProxy* Proxy : Info->Proxies)
{
Proxy->GetSharedProperties(this);
Proxy->PostEditChangeProperty(PropertyChangedEvent);
}
}
// Update normals if DrawScale3D is changed
if (MemberPropertyName == FName(TEXT("RelativeScale3D")))
{
FLandscapeEditDataInterface LandscapeEdit(Info);
LandscapeEdit.RecalculateNormals();
}
if (bNeedsRecalcBoundingBox || ChangedMaterial || bChangedLighting)
{
// We cannot iterate the XYtoComponentMap directly because reregistering components modifies the array.
TArray<ULandscapeComponent*> AllComponents;
Info->XYtoComponentMap.GenerateValueArray(AllComponents);
for (ULandscapeComponent* Comp : AllComponents)
{
if (ensure(Comp))
{
Comp->Modify();
if (bNeedsRecalcBoundingBox)
{
Comp->UpdateCachedBounds();
Comp->UpdateBounds();
}
if (bChangedLighting)
{
Comp->InvalidateLightingCache();
}
}
}
if (ChangedMaterial)
{
UpdateAllComponentMaterialInstances();
UWorld* World = GetWorld();
if (World != nullptr && UseMobileLandscapeMesh(GShaderPlatformForFeatureLevel[World->FeatureLevel]))
{
for (ULandscapeComponent * Component : LandscapeComponents)
{
if (Component != nullptr)
{
Component->CheckGenerateLandscapePlatformData(false, nullptr);
}
}
}
}
}
// Need to update Gizmo scene proxy
if (bNeedsRecalcBoundingBox && GetWorld())
{
for (ALandscapeGizmoActiveActor* Gizmo : TActorRange<ALandscapeGizmoActiveActor>(GetWorld()))
{
Gizmo->MarkComponentsRenderStateDirty();
}
}
// Must be done after the AActor::PostEditChange as we depend on the relinking of the landscapeInfo->LandscapeActor
if (ChangedMaterial)
{
LandscapeMaterialChangedDelegate.Broadcast();
}
}
PreEditLandscapeMaterial = nullptr;
PreEditLandscapeHoleMaterial = nullptr;
PreEditPerLODOverrideMaterials.Empty();
}
void ALandscapeProxy::ChangedPhysMaterial()
{
for (ULandscapeComponent* LandscapeComponent : LandscapeComponents)
{
if (LandscapeComponent && LandscapeComponent->IsRegistered())
{
ULandscapeHeightfieldCollisionComponent* CollisionComponent = LandscapeComponent->CollisionComponent.Get();
if (CollisionComponent)
{
LandscapeComponent->UpdateCollisionLayerData();
// Physical materials cooked into collision object, so we need to recreate it
CollisionComponent->RecreateCollision();
}
}
}
}
void ULandscapeComponent::PreEditChange(FProperty* PropertyThatWillChange)
{
Super::PreEditChange(PropertyThatWillChange);
if (GIsEditor && PropertyThatWillChange && (PropertyThatWillChange->GetFName() == GET_MEMBER_NAME_CHECKED(ULandscapeComponent, ForcedLOD) || PropertyThatWillChange->GetFName() == GET_MEMBER_NAME_CHECKED(ULandscapeComponent, LODBias)))
{
// PreEdit unregister component and re-register after PostEdit so we will lose XYtoComponentMap for this component
ULandscapeInfo* Info = GetLandscapeInfo();
if (Info)
{
FIntPoint ComponentKey = GetSectionBase() / ComponentSizeQuads;
auto RegisteredComponent = Info->XYtoComponentMap.FindRef(ComponentKey);
if (RegisteredComponent == nullptr)
{
Info->XYtoComponentMap.Add(ComponentKey, this);
}
}
}
}
void ULandscapeComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
const FName PropertyName = PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None;
const FName MemberPropertyName = PropertyChangedEvent.MemberProperty ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None;
if (PropertyName == FName(TEXT("OverrideMaterial")) || MemberPropertyName == FName(TEXT("PerLODOverrideMaterials")) || MemberPropertyName == FName(TEXT("MaterialPerLOD_Key")))
{
bool RecreateMaterialInstances = true;
if (PropertyName == FName(TEXT("PerLODOverrideMaterials")) && PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayAdd)
{
RecreateMaterialInstances = false;
}
if (RecreateMaterialInstances)
{
UpdateMaterialInstances();
UWorld* World = GetWorld();
if (World != nullptr && UseMobileLandscapeMesh(GShaderPlatformForFeatureLevel[World->FeatureLevel]))
{
CheckGenerateLandscapePlatformData(false, nullptr);
}
}
}
else if (GIsEditor && (PropertyName == FName(TEXT("ForcedLOD")) || PropertyName == FName(TEXT("LODBias"))))
{
bool bForcedLODChanged = PropertyName == FName(TEXT("ForcedLOD"));
SetLOD(bForcedLODChanged, bForcedLODChanged ? ForcedLOD : LODBias);
}
else if (GIsEditor && PropertyName == FName(TEXT("StaticLightingResolution")))
{
if (StaticLightingResolution > 0.0f)
{
StaticLightingResolution = ::AdjustStaticLightingResolution(StaticLightingResolution, NumSubsections, SubsectionSizeQuads, ComponentSizeQuads);
}
else
{
StaticLightingResolution = 0;
}
InvalidateLightingCache();
}
else if (GIsEditor && PropertyName == FName(TEXT("LightingLODBias")))
{
int32 MaxLOD = FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1;
LightingLODBias = FMath::Clamp<int32>(LightingLODBias, -1, MaxLOD);
InvalidateLightingCache();
}
else if (GIsEditor &&
(PropertyName == GET_MEMBER_NAME_CHECKED(ULandscapeComponent, CollisionMipLevel) ||
PropertyName == GET_MEMBER_NAME_CHECKED(ULandscapeComponent, SimpleCollisionMipLevel)))
{
CollisionMipLevel = FMath::Clamp<int32>(CollisionMipLevel, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
SimpleCollisionMipLevel = FMath::Clamp<int32>(SimpleCollisionMipLevel, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
if (PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive)
{
DestroyCollisionData();
UpdateCollisionData(); // Rebuild for new CollisionMipLevel
}
}
// Must do this *after* clamping values
Super::PostEditChangeProperty(PropertyChangedEvent);
}
TSet<class ULandscapeComponent*> ULandscapeInfo::GetSelectedComponents() const
{
return SelectedComponents;
}
TSet<class ULandscapeComponent*> ULandscapeInfo::GetSelectedRegionComponents() const
{
return SelectedRegionComponents;
}
void ULandscapeInfo::UpdateSelectedComponents(TSet<ULandscapeComponent*>& NewComponents, bool bIsComponentwise /*=true*/)
{
int32 InSelectType = bIsComponentwise ? FLandscapeEditToolRenderData::ST_COMPONENT : FLandscapeEditToolRenderData::ST_REGION;
if (bIsComponentwise)
{
for (TSet<ULandscapeComponent*>::TIterator It(NewComponents); It; ++It)
{
ULandscapeComponent* Comp = *It;
if ((Comp->EditToolRenderData.SelectedType & InSelectType) == 0)
{
Comp->Modify(false);
int32 SelectedType = Comp->EditToolRenderData.SelectedType;
SelectedType |= InSelectType;
Comp->EditToolRenderData.UpdateSelectionMaterial(SelectedType, Comp);
Comp->UpdateEditToolRenderData();
}
}
// Remove the material from any old components that are no longer in the region
TSet<ULandscapeComponent*> RemovedComponents = SelectedComponents.Difference(NewComponents);
for (TSet<ULandscapeComponent*>::TIterator It(RemovedComponents); It; ++It)
{
ULandscapeComponent* Comp = *It;
Comp->Modify(false);
int32 SelectedType = Comp->EditToolRenderData.SelectedType;
SelectedType &= ~InSelectType;
Comp->EditToolRenderData.UpdateSelectionMaterial(SelectedType, Comp);
Comp->UpdateEditToolRenderData();
}
SelectedComponents = NewComponents;
}
else
{
// Only add components...
if (NewComponents.Num())
{
for (TSet<ULandscapeComponent*>::TIterator It(NewComponents); It; ++It)
{
ULandscapeComponent* Comp = *It;
if ((Comp->EditToolRenderData.SelectedType & InSelectType) == 0)
{
Comp->Modify(false);
int32 SelectedType = Comp->EditToolRenderData.SelectedType;
SelectedType |= InSelectType;
Comp->EditToolRenderData.UpdateSelectionMaterial(SelectedType, Comp);
Comp->UpdateEditToolRenderData();
}
SelectedRegionComponents.Add(*It);
}
}
else
{
// Remove the material from any old components that are no longer in the region
for (TSet<ULandscapeComponent*>::TIterator It(SelectedRegionComponents); It; ++It)
{
ULandscapeComponent* Comp = *It;
Comp->Modify(false);
int32 SelectedType = Comp->EditToolRenderData.SelectedType;
SelectedType &= ~InSelectType;
Comp->EditToolRenderData.UpdateSelectionMaterial(SelectedType, Comp);
Comp->UpdateEditToolRenderData();
}
SelectedRegionComponents = NewComponents;
}
}
}
void ULandscapeInfo::ClearSelectedRegion(bool bIsComponentwise /*= true*/)
{
TSet<ULandscapeComponent*> NewComponents;
UpdateSelectedComponents(NewComponents, bIsComponentwise);
if (!bIsComponentwise)
{
SelectedRegion.Empty();
}
}
void ULandscapeComponent::ReallocateWeightmaps(FLandscapeEditDataInterface* DataInterface, bool InCanUseEditingWeightmap, bool InSaveToTransactionBuffer, bool InForceReallocate, ALandscapeProxy* InTargetProxy, TArray<UTexture*>* OutNewCreatedTextures)
{
int32 NeededNewChannels = 0;
ALandscapeProxy* TargetProxy = InTargetProxy ? InTargetProxy : GetLandscapeProxy();
FGuid EditingLayerGUID = GetEditingLayerGUID();
check(!TargetProxy->HasLayersContent() || !InCanUseEditingWeightmap || EditingLayerGUID.IsValid());
FGuid TargetLayerGuid = InCanUseEditingWeightmap ? EditingLayerGUID : FGuid();
TArray<FWeightmapLayerAllocationInfo>& ComponentWeightmapLayerAllocations = GetWeightmapLayerAllocations(InCanUseEditingWeightmap);
TArray<UTexture2D*>& ComponentWeightmapTextures = GetWeightmapTextures(InCanUseEditingWeightmap);
TArray<ULandscapeWeightmapUsage*>& ComponentWeightmapTexturesUsage = GetWeightmapTexturesUsage(InCanUseEditingWeightmap);
// When force reallocating, skip tests to see if allocations are necessary based on Component's WeightmapLayeAllocInfo
if (!InForceReallocate)
{
for (int32 LayerIdx = 0; LayerIdx < ComponentWeightmapLayerAllocations.Num(); LayerIdx++)
{
if (!ComponentWeightmapLayerAllocations[LayerIdx].IsAllocated())
{
NeededNewChannels++;
}
}
// All channels allocated!
if (NeededNewChannels == 0)
{
return;
}
}
bool bMarkPackageDirty = DataInterface == nullptr ? true : DataInterface->GetShouldDirtyPackage();
if (InSaveToTransactionBuffer)
{
Modify(bMarkPackageDirty);
TargetProxy->Modify(bMarkPackageDirty);
}
if (!InForceReallocate)
{
// UE_LOG(LogLandscape, Log, TEXT("----------------------"));
// UE_LOG(LogLandscape, Log, TEXT("Component %s needs %d layers (%d new)"), *GetName(), WeightmapLayerAllocations.Num(), NeededNewChannels);
// See if our existing textures have sufficient space
int32 ExistingTexAvailableChannels = 0;
for (int32 TexIdx = 0; TexIdx < ComponentWeightmapTextures.Num(); TexIdx++)
{
ULandscapeWeightmapUsage* Usage = ComponentWeightmapTexturesUsage[TexIdx];
check(Usage);
check(Usage->LayerGuid == TargetLayerGuid);
ExistingTexAvailableChannels += Usage->FreeChannelCount();
if (ExistingTexAvailableChannels >= NeededNewChannels)
{
break;
}
}
if (ExistingTexAvailableChannels >= NeededNewChannels)
{
// UE_LOG(LogLandscape, Log, TEXT("Existing texture has available channels"));
// Allocate using our existing textures' spare channels.
int32 CurrentAlloc = 0;
for (int32 TexIdx = 0; TexIdx < ComponentWeightmapTextures.Num(); TexIdx++)
{
ULandscapeWeightmapUsage* Usage = ComponentWeightmapTexturesUsage[TexIdx];
for (int32 ChanIdx = 0; ChanIdx < 4; ChanIdx++)
{
if (Usage->ChannelUsage[ChanIdx] == nullptr)
{
// Find next allocation to treat
for (; CurrentAlloc < ComponentWeightmapLayerAllocations.Num(); ++CurrentAlloc)
{
FWeightmapLayerAllocationInfo& AllocInfo = ComponentWeightmapLayerAllocations[CurrentAlloc];
if (!AllocInfo.IsAllocated())
{
break;
}
}
FWeightmapLayerAllocationInfo& AllocInfo = ComponentWeightmapLayerAllocations[CurrentAlloc];
check(!AllocInfo.IsAllocated());
// Zero out the data for this texture channel
if (DataInterface)
{
DataInterface->ZeroTextureChannel(ComponentWeightmapTextures[TexIdx], ChanIdx);
}
AllocInfo.WeightmapTextureIndex = TexIdx;
AllocInfo.WeightmapTextureChannel = ChanIdx;
Usage->ChannelUsage[ChanIdx] = this;
NeededNewChannels--;
if (NeededNewChannels == 0)
{
return;
}
}
}
}
// we should never get here.
check(false);
}
}
// UE_LOG(LogLandscape, Log, TEXT("Reallocating."));
// We are totally reallocating the weightmap
int32 TotalNeededChannels = ComponentWeightmapLayerAllocations.Num();
int32 CurrentLayer = 0;
TArray<UTexture2D*> NewWeightmapTextures;
TArray<ULandscapeWeightmapUsage*> NewComponentWeightmapTexturesUsage;
while (TotalNeededChannels > 0)
{
// UE_LOG(LogLandscape, Log, TEXT("Still need %d channels"), TotalNeededChannels);
UTexture2D* CurrentWeightmapTexture = nullptr;
ULandscapeWeightmapUsage* CurrentWeightmapUsage = nullptr;
if (TotalNeededChannels < 4)
{
// UE_LOG(LogLandscape, Log, TEXT("Looking for nearest"));
// see if we can find a suitable existing weightmap texture with sufficient channels
int32 BestDistanceSquared = MAX_int32;
for (auto& ItPair : TargetProxy->WeightmapUsageMap)
{
ULandscapeWeightmapUsage* TryWeightmapUsage = ItPair.Value;
//
if (TryWeightmapUsage->FreeChannelCount() >= TotalNeededChannels && TryWeightmapUsage->LayerGuid == TargetLayerGuid)
{
if (TryWeightmapUsage->IsEmpty())
{
CurrentWeightmapTexture = ItPair.Key;
CurrentWeightmapUsage = TryWeightmapUsage;
break;
}
else
{
// See if this candidate is closer than any others we've found
for (int32 ChanIdx = 0; ChanIdx < ULandscapeWeightmapUsage::NumChannels; ChanIdx++)
{
if (TryWeightmapUsage->ChannelUsage[ChanIdx] != nullptr)
{
int32 TryDistanceSquared = (TryWeightmapUsage->ChannelUsage[ChanIdx]->GetSectionBase() - GetSectionBase()).SizeSquared();
if (TryDistanceSquared < BestDistanceSquared)
{
CurrentWeightmapTexture = ItPair.Key;
CurrentWeightmapUsage = TryWeightmapUsage;
BestDistanceSquared = TryDistanceSquared;
}
}
}
}
}
}
}
bool NeedsUpdateResource = false;
// No suitable weightmap texture
if (CurrentWeightmapTexture == nullptr)
{
MarkPackageDirty();
// Weightmap is sized the same as the component
int32 WeightmapSize = (SubsectionSizeQuads + 1) * NumSubsections;
// We need a new weightmap texture
CurrentWeightmapTexture = TargetProxy->CreateLandscapeTexture(WeightmapSize, WeightmapSize, TEXTUREGROUP_Terrain_Weightmap, TSF_BGRA8);
// Alloc dummy mips
CreateEmptyTextureMips(CurrentWeightmapTexture, true);
CurrentWeightmapTexture->PostEditChange();
if (OutNewCreatedTextures != nullptr)
{
OutNewCreatedTextures->Add(CurrentWeightmapTexture);
}
// Store it in the usage map
CurrentWeightmapUsage = TargetProxy->WeightmapUsageMap.Add(CurrentWeightmapTexture, TargetProxy->CreateWeightmapUsage());
CurrentWeightmapUsage->LayerGuid = TargetLayerGuid;
// UE_LOG(LogLandscape, Log, TEXT("Making a new texture %s"), *CurrentWeightmapTexture->GetName());
}
NewComponentWeightmapTexturesUsage.Add(CurrentWeightmapUsage);
NewWeightmapTextures.Add(CurrentWeightmapTexture);
for (int32 ChanIdx = 0; ChanIdx < 4 && TotalNeededChannels > 0; ChanIdx++)
{
// UE_LOG(LogLandscape, Log, TEXT("Finding allocation for layer %d"), CurrentLayer);
if (CurrentWeightmapUsage->ChannelUsage[ChanIdx] == nullptr)
{
// Use this allocation
FWeightmapLayerAllocationInfo& AllocInfo = ComponentWeightmapLayerAllocations[CurrentLayer];
if (!AllocInfo.IsAllocated())
{
// New layer - zero out the data for this texture channel
if (DataInterface)
{
DataInterface->ZeroTextureChannel(CurrentWeightmapTexture, ChanIdx);
// UE_LOG(LogLandscape, Log, TEXT("Zeroing out channel %s.%d"), *CurrentWeightmapTexture->GetName(), ChanIdx);
}
}
else
{
UTexture2D* OldWeightmapTexture = ComponentWeightmapTextures[AllocInfo.WeightmapTextureIndex];
// Copy the data
if (ensure(DataInterface != nullptr)) // it's not safe to skip the copy
{
DataInterface->CopyTextureChannel(CurrentWeightmapTexture, ChanIdx, OldWeightmapTexture, AllocInfo.WeightmapTextureChannel);
DataInterface->ZeroTextureChannel(OldWeightmapTexture, AllocInfo.WeightmapTextureChannel);
// UE_LOG(LogLandscape, Log, TEXT("Copying old channel (%s).%d to new channel (%s).%d"), *OldWeightmapTexture->GetName(), AllocInfo.WeightmapTextureChannel, *CurrentWeightmapTexture->GetName(), ChanIdx);
}
// Remove the old allocation
ULandscapeWeightmapUsage* OldWeightmapUsage = ComponentWeightmapTexturesUsage[AllocInfo.WeightmapTextureIndex];
OldWeightmapUsage->ChannelUsage[AllocInfo.WeightmapTextureChannel] = nullptr;
}
// Assign the new allocation
CurrentWeightmapUsage->ChannelUsage[ChanIdx] = this;
AllocInfo.WeightmapTextureIndex = NewWeightmapTextures.Num() - 1;
AllocInfo.WeightmapTextureChannel = ChanIdx;
CurrentLayer++;
TotalNeededChannels--;
}
}
}
if (DataInterface)
{
// Update the mipmaps for the textures we edited
for (int32 Idx = 0; Idx < NewWeightmapTextures.Num(); Idx++)
{
UTexture2D* WeightmapTexture = NewWeightmapTextures[Idx];
FLandscapeTextureDataInfo* WeightmapDataInfo = DataInterface->GetTextureDataInfo(WeightmapTexture);
int32 NumMips = WeightmapTexture->Source.GetNumMips();
TArray<FColor*> WeightmapTextureMipData;
WeightmapTextureMipData.AddUninitialized(NumMips);
for (int32 MipIdx = 0; MipIdx < NumMips; MipIdx++)
{
WeightmapTextureMipData[MipIdx] = (FColor*)WeightmapDataInfo->GetMipData(MipIdx);
}
ULandscapeComponent::UpdateWeightmapMips(NumSubsections, SubsectionSizeQuads, WeightmapTexture, WeightmapTextureMipData, 0, 0, MAX_int32, MAX_int32, WeightmapDataInfo);
}
}
// Replace the weightmap textures
SetWeightmapTextures(MoveTemp(NewWeightmapTextures), InCanUseEditingWeightmap);
SetWeightmapTexturesUsage(MoveTemp(NewComponentWeightmapTexturesUsage), InCanUseEditingWeightmap);
TargetProxy->ValidateProxyLayersWeightmapUsage();
}
void ALandscapeProxy::RemoveInvalidWeightmaps()
{
if (GIsEditor)
{
for (decltype(WeightmapUsageMap)::TIterator It(WeightmapUsageMap); It; ++It)
{
UTexture2D* Tex = It.Key();
ULandscapeWeightmapUsage* Usage = It.Value();
if (Usage->IsEmpty()) // Invalid Weight-map
{
if (Tex)
{
Tex->SetFlags(RF_Transactional);
Tex->Modify();
Tex->MarkPackageDirty();
Tex->ClearFlags(RF_Standalone);
}
It.RemoveCurrent();
}
}
// Remove Unused Weightmaps...
for (int32 Idx = 0; Idx < LandscapeComponents.Num(); ++Idx)
{
ULandscapeComponent* Component = LandscapeComponents[Idx];
Component->RemoveInvalidWeightmaps();
}
}
}
void ULandscapeComponent::RemoveInvalidWeightmaps()
{
// Process the final weightmaps
RemoveInvalidWeightmaps(FGuid());
// Also process all edit layers weightmaps :
ForEachLayer([&](const FGuid& LayerGuid, FLandscapeLayerComponentData& LayerData)
{
RemoveInvalidWeightmaps(LayerGuid);
});
}
void ULandscapeComponent::RemoveInvalidWeightmaps(const FGuid& InEditLayerGuid)
{
TArray<FWeightmapLayerAllocationInfo>& ComponentWeightmapLayerAllocations = GetWeightmapLayerAllocations(InEditLayerGuid);
TArray<UTexture2D*>& ComponentWeightmapTextures = GetWeightmapTextures(InEditLayerGuid);
TArray<ULandscapeWeightmapUsage*>& ComponentWeightmapTexturesUsage = GetWeightmapTexturesUsage(InEditLayerGuid);
// Adjust WeightmapTextureIndex index for other layers
TSet<int32> UnUsedTextureIndices;
{
TSet<int32> UsedTextureIndices;
for (int32 LayerIdx = 0; LayerIdx < ComponentWeightmapLayerAllocations.Num(); LayerIdx++)
{
UsedTextureIndices.Add(ComponentWeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex);
}
for (int32 WeightIdx = 0; WeightIdx < ComponentWeightmapTextures.Num(); ++WeightIdx)
{
if (!UsedTextureIndices.Contains(WeightIdx))
{
UnUsedTextureIndices.Add(WeightIdx);
}
}
}
int32 RemovedTextures = 0;
for (int32 UnusedIndex : UnUsedTextureIndices)
{
int32 WeightmapTextureIndexToRemove = UnusedIndex - RemovedTextures;
ComponentWeightmapTextures[WeightmapTextureIndexToRemove]->SetFlags(RF_Transactional);
ComponentWeightmapTextures[WeightmapTextureIndexToRemove]->Modify();
ComponentWeightmapTextures[WeightmapTextureIndexToRemove]->MarkPackageDirty();
ComponentWeightmapTextures[WeightmapTextureIndexToRemove]->ClearFlags(RF_Standalone);
ComponentWeightmapTextures.RemoveAt(WeightmapTextureIndexToRemove);
ComponentWeightmapTexturesUsage.RemoveAt(WeightmapTextureIndexToRemove);
// Adjust WeightmapTextureIndex index for other layers
for (int32 LayerIdx = 0; LayerIdx < ComponentWeightmapLayerAllocations.Num(); LayerIdx++)
{
FWeightmapLayerAllocationInfo& Allocation = ComponentWeightmapLayerAllocations[LayerIdx];
if (Allocation.WeightmapTextureIndex > WeightmapTextureIndexToRemove)
{
Allocation.WeightmapTextureIndex--;
}
checkSlow(Allocation.WeightmapTextureIndex < WeightmapTextures.Num());
}
RemovedTextures++;
}
}
void ULandscapeComponent::InitHeightmapData(TArray<FColor>& Heights, bool bUpdateCollision)
{
int32 ComponentSizeVerts = NumSubsections * (SubsectionSizeQuads + 1);
if (Heights.Num() != FMath::Square(ComponentSizeVerts))
{
return;
}
// Handling old Height map....
if (HeightmapTexture && HeightmapTexture->GetOutermost() != GetTransientPackage()
&& HeightmapTexture->GetOutermost() == GetOutermost()
&& HeightmapTexture->Source.GetSizeX() >= ComponentSizeVerts) // if Height map is not valid...
{
HeightmapTexture->SetFlags(RF_Transactional);
HeightmapTexture->Modify();
HeightmapTexture->MarkPackageDirty();
HeightmapTexture->ClearFlags(RF_Standalone); // Delete if no reference...
}
// New Height map
TArray<FColor*> HeightmapTextureMipData;
// make sure the heightmap UVs are powers of two.
int32 HeightmapSizeU = (1 << FMath::CeilLogTwo(ComponentSizeVerts));
int32 HeightmapSizeV = (1 << FMath::CeilLogTwo(ComponentSizeVerts));
// Height map construction
SetHeightmap(GetLandscapeProxy()->CreateLandscapeTexture(HeightmapSizeU, HeightmapSizeV, TEXTUREGROUP_Terrain_Heightmap, TSF_BGRA8));
int32 MipSubsectionSizeQuads = SubsectionSizeQuads;
int32 MipSizeU = HeightmapSizeU;
int32 MipSizeV = HeightmapSizeV;
HeightmapScaleBias = FVector4(1.0f / (float)HeightmapSizeU, 1.0f / (float)HeightmapSizeV, 0.0f, 0.0f);
int32 Mip = 0;
while (MipSizeU > 1 && MipSizeV > 1 && MipSubsectionSizeQuads >= 1)
{
FColor* HeightmapTextureData = (FColor*)GetHeightmap()->Source.LockMip(Mip);
if (Mip == 0)
{
FMemory::Memcpy(HeightmapTextureData, Heights.GetData(), MipSizeU*MipSizeV*sizeof(FColor));
}
else
{
FMemory::Memzero(HeightmapTextureData, MipSizeU*MipSizeV*sizeof(FColor));
}
HeightmapTextureMipData.Add(HeightmapTextureData);
MipSizeU >>= 1;
MipSizeV >>= 1;
Mip++;
MipSubsectionSizeQuads = ((MipSubsectionSizeQuads + 1) >> 1) - 1;
}
ULandscapeComponent::GenerateHeightmapMips(HeightmapTextureMipData);
if (bUpdateCollision)
{
UpdateCollisionHeightData(
HeightmapTextureMipData[CollisionMipLevel],
SimpleCollisionMipLevel > CollisionMipLevel ? HeightmapTextureMipData[SimpleCollisionMipLevel] : nullptr);
}
for (int32 i = 0; i < HeightmapTextureMipData.Num(); i++)
{
GetHeightmap()->Source.UnlockMip(i);
}
GetHeightmap()->PostEditChange();
}
void ULandscapeComponent::InitWeightmapData(TArray<ULandscapeLayerInfoObject*>& LayerInfos, TArray<TArray<uint8> >& WeightmapData)
{
if (LayerInfos.Num() != WeightmapData.Num() || LayerInfos.Num() <= 0)
{
return;
}
int32 ComponentSizeVerts = NumSubsections * (SubsectionSizeQuads + 1);
// Validation..
for (int32 Idx = 0; Idx < WeightmapData.Num(); ++Idx)
{
if (WeightmapData[Idx].Num() != FMath::Square(ComponentSizeVerts))
{
return;
}
}
for (int32 Idx = 0; Idx < WeightmapTextures.Num(); ++Idx)
{
if (WeightmapTextures[Idx] && WeightmapTextures[Idx]->GetOutermost() != GetTransientPackage()
&& WeightmapTextures[Idx]->GetOutermost() == GetOutermost()
&& WeightmapTextures[Idx]->Source.GetSizeX() == ComponentSizeVerts)
{
WeightmapTextures[Idx]->SetFlags(RF_Transactional);
WeightmapTextures[Idx]->Modify();
WeightmapTextures[Idx]->MarkPackageDirty();
WeightmapTextures[Idx]->ClearFlags(RF_Standalone); // Delete if no reference...
}
}
WeightmapTextures.Empty();
WeightmapLayerAllocations.Empty(LayerInfos.Num());
for (int32 Idx = 0; Idx < LayerInfos.Num(); ++Idx)
{
new (WeightmapLayerAllocations)FWeightmapLayerAllocationInfo(LayerInfos[Idx]);
}
ReallocateWeightmaps();
check(WeightmapLayerAllocations.Num() > 0 && WeightmapTextures.Num() > 0);
int32 WeightmapSize = ComponentSizeVerts;
WeightmapScaleBias = FVector4(1.0f / (float)WeightmapSize, 1.0f / (float)WeightmapSize, 0.5f / (float)WeightmapSize, 0.5f / (float)WeightmapSize);
WeightmapSubsectionOffset = (float)(SubsectionSizeQuads + 1) / (float)WeightmapSize;
TArray<void*> WeightmapDataPtrs;
WeightmapDataPtrs.AddUninitialized(WeightmapTextures.Num());
for (int32 WeightmapIdx = 0; WeightmapIdx < WeightmapTextures.Num(); ++WeightmapIdx)
{
// Calling modify here makes sure that async texture compilation finishes (triggered by ReallocateWeightmaps) so we can Lock the mip
WeightmapTextures[WeightmapIdx]->Modify();
WeightmapDataPtrs[WeightmapIdx] = WeightmapTextures[WeightmapIdx]->Source.LockMip(0);
}
for (int32 LayerIdx = 0; LayerIdx < WeightmapLayerAllocations.Num(); ++LayerIdx)
{
void* DestDataPtr = WeightmapDataPtrs[WeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex];
uint8* DestTextureData = (uint8*)DestDataPtr + ChannelOffsets[WeightmapLayerAllocations[LayerIdx].WeightmapTextureChannel];
uint8* SrcTextureData = (uint8*)&WeightmapData[LayerIdx][0];
for (int32 i = 0; i < WeightmapData[LayerIdx].Num(); i++)
{
DestTextureData[i * 4] = SrcTextureData[i];
}
}
for (int32 Idx = 0; Idx < WeightmapTextures.Num(); Idx++)
{
UTexture2D* WeightmapTexture = WeightmapTextures[Idx];
WeightmapTexture->Source.UnlockMip(0);
}
for (int32 Idx = 0; Idx < WeightmapTextures.Num(); Idx++)
{
UTexture2D* WeightmapTexture = WeightmapTextures[Idx];
{
const bool bShouldDirtyPackage = true;
FLandscapeTextureDataInfo WeightmapDataInfo(WeightmapTexture, bShouldDirtyPackage);
int32 NumMips = WeightmapTexture->Source.GetNumMips();
TArray<FColor*> WeightmapTextureMipData;
WeightmapTextureMipData.AddUninitialized(NumMips);
for (int32 MipIdx = 0; MipIdx < NumMips; MipIdx++)
{
WeightmapTextureMipData[MipIdx] = (FColor*)WeightmapDataInfo.GetMipData(MipIdx);
}
ULandscapeComponent::UpdateWeightmapMips(NumSubsections, SubsectionSizeQuads, WeightmapTexture, WeightmapTextureMipData, 0, 0, MAX_int32, MAX_int32, &WeightmapDataInfo);
}
WeightmapTexture->PostEditChange();
}
FlushRenderingCommands();
MaterialInstances.Empty(1);
MaterialInstances.Add(nullptr);
LODIndexToMaterialIndex.Empty(1);
LODIndexToMaterialIndex.Add(0);
// TODO: need to update layer system?
}
#define MAX_LANDSCAPE_EXPORT_COMPONENTS_NUM 16
#define MAX_LANDSCAPE_PROP_TEXT_LENGTH 1024*1024*16
bool ALandscapeProxy::ShouldExport()
{
if (!bIsMovingToLevel && LandscapeComponents.Num() > MAX_LANDSCAPE_EXPORT_COMPONENTS_NUM)
{
// Prompt to save startup packages
if (EAppReturnType::Yes == FMessageDialog::Open(EAppMsgType::YesNo, FText::Format(
NSLOCTEXT("UnrealEd", "LandscapeExport_Warning", "Landscape has large number({0}) of components, so it will use large amount memory to copy it to the clipboard. Do you want to proceed?"), FText::AsNumber(LandscapeComponents.Num()))))
{
return true;
}
else
{
return false;
}
}
return true;
}
bool ALandscapeProxy::ShouldImport(FString* ActorPropString, bool IsMovingToLevel)
{
bIsMovingToLevel = IsMovingToLevel;
if (!bIsMovingToLevel && ActorPropString && ActorPropString->Len() > MAX_LANDSCAPE_PROP_TEXT_LENGTH)
{
// Prompt to save startup packages
if (EAppReturnType::Yes == FMessageDialog::Open(EAppMsgType::YesNo, FText::Format(
NSLOCTEXT("UnrealEd", "LandscapeImport_Warning", "Landscape is about to import large amount memory ({0}MB) from the clipboard, which will take some time. Do you want to proceed?"), FText::AsNumber(ActorPropString->Len() >> 20))))
{
return true;
}
else
{
return false;
}
}
return true;
}
bool ALandscapeStreamingProxy::IsValidLandscapeActor(ALandscape* Landscape)
{
if (Landscape)
{
if (!Landscape->HasAnyFlags(RF_BeginDestroyed))
{
if (LandscapeActor.IsNull() && !LandscapeGuid.IsValid())
{
return true; // always valid for newly created Proxy
}
if (((LandscapeActor && LandscapeActor == Landscape)
|| (LandscapeActor.IsNull() && LandscapeGuid.IsValid() && LandscapeGuid == Landscape->GetLandscapeGuid()))
&& ComponentSizeQuads == Landscape->ComponentSizeQuads
&& NumSubsections == Landscape->NumSubsections
&& SubsectionSizeQuads == Landscape->SubsectionSizeQuads)
{
return true;
}
}
}
return false;
}
/* Returns the list of layer names relevant to mobile platforms. Walks the material tree following feature level switch nodes. */
static void GetAllMobileRelevantLayerNames(TSet<FName>& OutLayerNames, UMaterial* InMaterial)
{
TArray<FName> LayerNames;
TArray<UMaterialExpression*> ES31Expressions;
InMaterial->GetAllReferencedExpressions(ES31Expressions, nullptr, ERHIFeatureLevel::ES3_1);
TArray<UMaterialExpression*> MobileExpressions = MoveTemp(ES31Expressions);
for (UMaterialExpression* Expression : MobileExpressions)
{
if (Expression)
{
Expression->GetLandscapeLayerNames(LayerNames);
}
}
for (const FName& Name : LayerNames)
{
OutLayerNames.Add(Name);
}
}
void ULandscapeComponent::GenerateMobileWeightmapLayerAllocations()
{
TSet<FName> LayerNames;
GetAllMobileRelevantLayerNames(LayerNames, GetLandscapeMaterial()->GetMaterial());
MobileWeightmapLayerAllocations = WeightmapLayerAllocations.FilterByPredicate([&](const FWeightmapLayerAllocationInfo& Allocation) -> bool
{
return Allocation.LayerInfo && LayerNames.Contains(Allocation.GetLayerName());
}
);
MobileWeightmapLayerAllocations.StableSort(([&](const FWeightmapLayerAllocationInfo& A, const FWeightmapLayerAllocationInfo& B) -> bool
{
ULandscapeLayerInfoObject* LhsLayerInfo = A.LayerInfo;
ULandscapeLayerInfoObject* RhsLayerInfo = B.LayerInfo;
if (!LhsLayerInfo && !RhsLayerInfo) return false; // equally broken :P
if (!LhsLayerInfo && RhsLayerInfo) return false; // broken layers sort to the end
if (!RhsLayerInfo && LhsLayerInfo) return true;
// Sort visibility layer to the front
if (LhsLayerInfo == ALandscapeProxy::VisibilityLayer && RhsLayerInfo != ALandscapeProxy::VisibilityLayer) return true;
if (RhsLayerInfo == ALandscapeProxy::VisibilityLayer && LhsLayerInfo != ALandscapeProxy::VisibilityLayer) return false;
// Sort non-weight blended layers to the front so if we have exactly 3 layers, the 3rd is definitely weight-based.
if (LhsLayerInfo->bNoWeightBlend && !RhsLayerInfo->bNoWeightBlend) return true;
if (RhsLayerInfo->bNoWeightBlend && !LhsLayerInfo->bNoWeightBlend) return false;
return false; // equal, preserve order
}));
}
void ULandscapeComponent::GeneratePlatformPixelData(bool bIsCooking, const ITargetPlatform* TargetPlatform)
{
check(!IsTemplate());
GenerateMobileWeightmapLayerAllocations();
int32 WeightmapSize = (SubsectionSizeQuads + 1) * NumSubsections;
MobileWeightmapTextures.Empty();
UTexture2D* MobileWeightNormalmapTexture = GetLandscapeProxy()->CreateLandscapeTexture(WeightmapSize, WeightmapSize, TEXTUREGROUP_Terrain_Weightmap, TSF_BGRA8, nullptr, GMobileCompressLandscapeWeightMaps ? true : false );
CreateEmptyTextureMips(MobileWeightNormalmapTexture, true);
{
FLandscapeTextureDataInterface LandscapeData;
// copy normals into B/A channels
LandscapeData.CopyTextureFromHeightmap(MobileWeightNormalmapTexture, 2, this, 2);
LandscapeData.CopyTextureFromHeightmap(MobileWeightNormalmapTexture, 3, this, 3);
UTexture2D* CurrentWeightmapTexture = MobileWeightNormalmapTexture;
MobileWeightmapTextures.Add(CurrentWeightmapTexture);
int32 CurrentChannel = 0;
// We can potentially save a channel allocation if we have weight based blends.
const bool bAtLeastOneWeightBasedBlend = MobileWeightmapLayerAllocations.FindByPredicate([&](const FWeightmapLayerAllocationInfo& Allocation) -> bool { return !Allocation.LayerInfo->bNoWeightBlend; }) != nullptr;
const bool bUseWeightBasedChannelOptim = bAtLeastOneWeightBasedBlend && MobileWeightmapLayerAllocations.Num() <= 3;
MobileBlendableLayerMask = 0;
// Give normal map a full texture if this doesn't increase the overall allocation count.
// This then saves a texture slot because we don't need to sample a combined normalmap/weightmap texture with two different sampler settings.
// This optimization won't be useful or valid if we are already applying the weight based blending channel optimization.
const int32 NumTexturesCombinedNormal = FMath::DivideAndRoundUp(MobileWeightmapLayerAllocations.Num() + 2, 4);
const int32 NumTexturesIsolatedNormal = 1 + FMath::DivideAndRoundUp(MobileWeightmapLayerAllocations.Num(), 4);
const bool bIsolateNormalMap = !bUseWeightBasedChannelOptim && NumTexturesCombinedNormal == NumTexturesIsolatedNormal;
int32 RemainingChannels = bIsolateNormalMap ? 0 : 2;
for (auto& Allocation : MobileWeightmapLayerAllocations)
{
if (Allocation.LayerInfo)
{
// If we can pack into 2 channels with the 3rd implied, track the mask for the weight blendable layers
if (bUseWeightBasedChannelOptim)
{
MobileBlendableLayerMask |= (!Allocation.LayerInfo->bNoWeightBlend ? (1 << CurrentChannel) : 0);
// we don't need to create a new texture for the 3rd layer
if (RemainingChannels == 0)
{
Allocation.WeightmapTextureIndex = 0;
Allocation.WeightmapTextureChannel = 2; // not a valid texture channel, but used for the mask.
break;
}
}
if (RemainingChannels == 0)
{
// create a new weightmap texture if we've run out of channels
CurrentChannel = 0;
RemainingChannels = 4;
CurrentWeightmapTexture = GetLandscapeProxy()->CreateLandscapeTexture(WeightmapSize, WeightmapSize, TEXTUREGROUP_Terrain_Weightmap, TSF_BGRA8, nullptr, GMobileCompressLandscapeWeightMaps ? true : false);
CreateEmptyTextureMips(CurrentWeightmapTexture, true);
MobileWeightmapTextures.Add(CurrentWeightmapTexture);
}
LandscapeData.CopyTextureFromWeightmap(CurrentWeightmapTexture, CurrentChannel, this, Allocation.LayerInfo);
// update Allocation
Allocation.WeightmapTextureIndex = MobileWeightmapTextures.Num() - 1;
Allocation.WeightmapTextureChannel = CurrentChannel;
CurrentChannel++;
RemainingChannels--;
}
}
}
GDisableAutomaticTextureMaterialUpdateDependencies = true;
for (int TextureIdx = 0; TextureIdx < MobileWeightmapTextures.Num(); TextureIdx++)
{
UTexture* Texture = MobileWeightmapTextures[TextureIdx];
Texture->PostEditChange();
// PostEditChange() will assign a random GUID to the texture, which leads to non-deterministic builds.
Texture->SetDeterministicLightingGuid();
}
GDisableAutomaticTextureMaterialUpdateDependencies = false;
FLinearColor Masks[4];
Masks[0] = FLinearColor(1, 0, 0, 0);
Masks[1] = FLinearColor(0, 1, 0, 0);
Masks[2] = FLinearColor(0, 0, 1, 0);
Masks[3] = FLinearColor(0, 0, 0, 1);
if (!GIsEditor)
{
// This path is used by game mode running with uncooked data, eg standalone executable Mobile Preview.
// Game mode cannot create MICs, so we use a MaterialInstanceDynamic here.
// Fallback to use non mobile materials if there is no mobile one
if (MobileCombinationMaterialInstances.Num() == 0)
{
MobileCombinationMaterialInstances.Append(MaterialInstances);
}
MobileMaterialInterfaces.Reset();
MobileMaterialInterfaces.Reserve(MobileCombinationMaterialInstances.Num());
for (int32 MaterialIndex = 0; MaterialIndex < MobileCombinationMaterialInstances.Num(); ++MaterialIndex)
{
UMaterialInstanceDynamic* NewMobileMaterialInstance = UMaterialInstanceDynamic::Create(MobileCombinationMaterialInstances[MaterialIndex], this);
// Set the layer mask
for (const auto& Allocation : MobileWeightmapLayerAllocations)
{
if (Allocation.LayerInfo)
{
NewMobileMaterialInstance->SetVectorParameterValue(FName(*FString::Printf(TEXT("LayerMask_%s"), *Allocation.GetLayerName().ToString())), Masks[Allocation.WeightmapTextureChannel]);
}
}
for (int TextureIdx = 0; TextureIdx < MobileWeightmapTextures.Num(); TextureIdx++)
{
NewMobileMaterialInstance->SetTextureParameterValue(FName(*FString::Printf(TEXT("Weightmap%d"), TextureIdx)), MobileWeightmapTextures[TextureIdx]);
}
MobileMaterialInterfaces.Add(NewMobileMaterialInstance);
}
}
else
{
// When cooking, we need to make a persistent MIC. In the editor we also do so in
// case we start a Cook in Editor operation, which will reuse the MIC we create now.
check(LODIndexToMaterialIndex.Num() > 0);
if (MaterialPerLOD.Num() == 0)
{
int32 MaxLOD = FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1;
for (int32 LODIndex = 0; LODIndex <= MaxLOD; ++LODIndex)
{
UMaterialInterface* CurrentMaterial = GetLandscapeMaterial(LODIndex);
if (MaterialPerLOD.Find(CurrentMaterial) == nullptr)
{
MaterialPerLOD.Add(CurrentMaterial, LODIndex);
}
}
}
MobileCombinationMaterialInstances.SetNumZeroed(MaterialPerLOD.Num());
MobileMaterialInterfaces.Reset();
MobileMaterialInterfaces.Reserve(MaterialPerLOD.Num());
int8 MaterialIndex = 0;
for (auto It = MaterialPerLOD.CreateConstIterator(); It; ++It)
{
const int8 MaterialLOD = It.Value();
// Find or set a matching MIC in the Landscape's map.
MobileCombinationMaterialInstances[MaterialIndex] = GetCombinationMaterial(nullptr, MobileWeightmapLayerAllocations, MaterialLOD, true);
check(MobileCombinationMaterialInstances[MaterialIndex] != nullptr);
if (bIsCooking)
{
// If we are cooking ensure we are caching shader maps.
MobileCombinationMaterialInstances[MaterialIndex]->BeginCacheForCookedPlatformData(TargetPlatform);
}
UMaterialInstanceConstant* NewMobileMaterialInstance = NewObject<ULandscapeMaterialInstanceConstant>(this);
NewMobileMaterialInstance->SetParentEditorOnly(MobileCombinationMaterialInstances[MaterialIndex]);
// Set the layer mask
for (const auto& Allocation : MobileWeightmapLayerAllocations)
{
if (Allocation.LayerInfo)
{
NewMobileMaterialInstance->SetVectorParameterValueEditorOnly(FName(*FString::Printf(TEXT("LayerMask_%s"), *Allocation.GetLayerName().ToString())), Masks[Allocation.WeightmapTextureChannel]);
}
}
for (int TextureIdx = 0; TextureIdx < MobileWeightmapTextures.Num(); TextureIdx++)
{
NewMobileMaterialInstance->SetTextureParameterValueEditorOnly(FName(*FString::Printf(TEXT("Weightmap%d"), TextureIdx)), MobileWeightmapTextures[TextureIdx]);
}
NewMobileMaterialInstance->PostEditChange();
MobileMaterialInterfaces.Add(NewMobileMaterialInstance);
++MaterialIndex;
}
}
}
// FBox2D version that uses integers
struct FIntBox2D
{
FIntBox2D() : Min(INT32_MAX, INT32_MAX), Max(-INT32_MAX, -INT32_MAX) {}
void Add(FIntPoint const& Pos)
{
Min = FIntPoint(FMath::Min(Min.X, Pos.X), FMath::Min(Min.Y, Pos.Y));
Max = FIntPoint(FMath::Max(Max.X, Pos.X), FMath::Max(Max.Y, Pos.Y));
}
void Add(FIntBox2D const& Rhs)
{
Min = FIntPoint(FMath::Min(Min.X, Rhs.Min.X), FMath::Min(Min.Y, Rhs.Min.Y));
Max = FIntPoint(FMath::Max(Max.X, Rhs.Max.X), FMath::Max(Max.Y, Rhs.Max.Y));
}
bool Intersects(FIntBox2D const& Rhs)
{
return !((Rhs.Max.X < Min.X) || (Rhs.Min.X > Max.X) || (Rhs.Max.Y < Min.Y) || (Rhs.Min.Y > Max.Y));
}
FIntPoint Min;
FIntPoint Max;
};
// Segment the hole map and return an array of hole bounding rectangles
void GetHoleBounds(int32 InSize, TArray<uint8> const& InVisibilityData, TArray<FIntBox2D>& OutHoleBounds)
{
check(InVisibilityData.Num() == InSize * InSize);
TArray<uint32> HoleSegmentLabels;
HoleSegmentLabels.AddZeroed(InSize*InSize);
TArray<uint32, TInlineAllocator<32>> LabelEquivalenceMap;
LabelEquivalenceMap.Add(0);
uint32 NextLabel = 1;
// First pass fills HoleSegmentLabels with labels.
for (int32 y = 0; y < InSize; ++y)
{
for (int32 x = 0; x < InSize; ++x)
{
const uint8 VisThreshold = 170;
const bool bIsHole = InVisibilityData[y * InSize + x] >= VisThreshold;
if (bIsHole)
{
uint8 WestLabel = (x > 0) ? HoleSegmentLabels[y * InSize + x - 1] : 0;
uint8 NorthLabel = (y > 0) ? HoleSegmentLabels[(y - 1) * InSize + x] : 0;
if (WestLabel != 0 && NorthLabel != 0 && WestLabel != NorthLabel)
{
uint32 MinLabel = FMath::Min(WestLabel, NorthLabel);
uint32 MaxLabel = FMath::Max(WestLabel, NorthLabel);
LabelEquivalenceMap[MaxLabel] = MinLabel;
HoleSegmentLabels[y * InSize + x] = MinLabel;
}
else if (WestLabel != 0)
{
HoleSegmentLabels[y * InSize + x] = WestLabel;
}
else if (NorthLabel != 0)
{
HoleSegmentLabels[y * InSize + x] = NorthLabel;
}
else
{
LabelEquivalenceMap.Add(NextLabel);
HoleSegmentLabels[y * InSize + x] = NextLabel++;
}
}
}
}
// Resolve label equivalences.
for (int32 Index = 0; Index < LabelEquivalenceMap.Num(); ++ Index)
{
int32 CommonIndex = Index;
while (LabelEquivalenceMap[CommonIndex] != CommonIndex)
{
CommonIndex = LabelEquivalenceMap[CommonIndex];
}
LabelEquivalenceMap[Index] = CommonIndex;
}
// Flatten labels to be contiguous.
int32 NumLabels = 0;
for (int32 Index = 0; Index < LabelEquivalenceMap.Num(); ++Index)
{
if (LabelEquivalenceMap[Index] == Index)
{
LabelEquivalenceMap[Index] = NumLabels++;
}
else
{
LabelEquivalenceMap[Index] = LabelEquivalenceMap[LabelEquivalenceMap[Index]];
}
}
// Second pass finds bounds for each label.
// Could also write contiguous labels to HoleSegmentLabels here if we want to keep that info.
OutHoleBounds.AddDefaulted(NumLabels);
for (int32 y = 0; y < InSize - 1; ++y)
{
for (int32 x = 0; x < InSize - 1; ++x)
{
const int32 Index = InSize * y + x;
const int32 Label = LabelEquivalenceMap[HoleSegmentLabels[Index]];
OutHoleBounds[Label].Add(FIntPoint(x, y));
}
}
}
// Move vertex index up to the next location which obeys the condition:
// PosAt(VertexIndex, LodIndex) > PosAt(VertexIndex - 1, LodIndex + 1)
// Maths derived from pattern when analyzing a spreadsheet containing a dump of lod vertex positions.
inline void AlignVertexDown(int32 InLodIndex, int32& InOutVertexIndex)
{
const int32 Offset = InOutVertexIndex & ((2 << InLodIndex) - 1);
if (Offset < (1 << InLodIndex))
{
InOutVertexIndex -= Offset;
}
}
// Move vertex index up to the next location which obeys the condition:
// PosAt(VertexIndex, LodIndex) < PosAt(VertexIndex + 1, LodIndex + 1)
// Maths derived from pattern when analyzing a spreadsheet containing a dump of lod vertex positions.
inline void AlignVertexUp(int32 InLodIndex, int32& InOutVertexIndex)
{
const int32 Offset = (InOutVertexIndex + 1) & ((2 << InLodIndex) - 1);
if (Offset > (1 << InLodIndex))
{
InOutVertexIndex += (1 << InLodIndex) - Offset;
}
}
// Expand bounding rectangles from LodIndex-1 to LodIndex
void ExpandBoundsForLod(int32 InSize, int32 InLodIndex, TArray<FIntBox2D> const& InHoleBounds, TArray<FIntBox2D>& OutHoleBounds)
{
OutHoleBounds.AddZeroed(InHoleBounds.Num());
for (int32 i = 0; i < InHoleBounds.Num(); ++i)
{
// Expand
const int32 ExpandDistance = (2 << InLodIndex) - 1;
OutHoleBounds[i].Min.X = InHoleBounds[i].Min.X - ExpandDistance;
OutHoleBounds[i].Min.Y = InHoleBounds[i].Min.Y - ExpandDistance;
OutHoleBounds[i].Max.X = InHoleBounds[i].Max.X + ExpandDistance;
OutHoleBounds[i].Max.Y = InHoleBounds[i].Max.Y + ExpandDistance;
// Snap to continuous LOD borders so that consecutive vertices with different LODs don't overlap
if (InLodIndex > 0)
{
AlignVertexDown(InLodIndex, OutHoleBounds[i].Min.X);
AlignVertexDown(InLodIndex, OutHoleBounds[i].Min.Y);
AlignVertexUp(InLodIndex, OutHoleBounds[i].Max.X);
AlignVertexUp(InLodIndex, OutHoleBounds[i].Max.Y);
}
// Clamp to edges
OutHoleBounds[i].Min.X = FMath::Max(OutHoleBounds[i].Min.X, 0);
OutHoleBounds[i].Max.X = FMath::Min(OutHoleBounds[i].Max.X, InSize - 1);
OutHoleBounds[i].Min.Y = FMath::Max(OutHoleBounds[i].Min.Y, 0);
OutHoleBounds[i].Max.Y = FMath::Min(OutHoleBounds[i].Max.Y, InSize - 1);
}
}
// Combine intersecting bounding rectangles into to form their bounding rectangles.
void CombineIntersectingBounds(TArray<FIntBox2D>& InOutHoleBounds)
{
int i = 1;
while (i < InOutHoleBounds.Num())
{
int j = i + 1;
for (; j < InOutHoleBounds.Num(); ++j)
{
if (InOutHoleBounds[i].Intersects(InOutHoleBounds[j]))
{
InOutHoleBounds[i].Add(InOutHoleBounds[j]);
InOutHoleBounds.RemoveAtSwap(j);
break;
}
}
if (j == InOutHoleBounds.Num())
{
++i;
}
}
}
// Build an array with an entry per vertex which contains the Lod at which that vertex falls inside a hole bounding rectangle.
// This is the Lod at which we should clamp the vertex in the vertex shader.
void BuildHoleVertexLods(int32 InSize, int32 InNumLods, TArray<FIntBox2D> const& InHoleBounds, TArray<uint8>& OutHoleVertexLods)
{
// Generate hole bounds for each Lod level from Lod0 InHoleBounds
TArray< TArray<FIntBox2D> > HoleBoundsPerLevel;
HoleBoundsPerLevel.AddDefaulted(InNumLods);
HoleBoundsPerLevel[0] = InHoleBounds;
for (int32 LodIndex = 1; LodIndex < InNumLods; ++LodIndex)
{
ExpandBoundsForLod(InSize, LodIndex, HoleBoundsPerLevel[LodIndex - 1], HoleBoundsPerLevel[LodIndex]);
}
for (int32 LodIndex = 0; LodIndex < InNumLods; ++LodIndex)
{
CombineIntersectingBounds(HoleBoundsPerLevel[LodIndex]);
}
// Initialize output to the max Lod
OutHoleVertexLods.Init(InNumLods, InSize * InSize);
// Fill by writing each Lod level in turn
for (int32 LodIndex = InNumLods - 1; LodIndex >= 0; --LodIndex)
{
TArray<FIntBox2D> const& HoleBoundsAtLevel = HoleBoundsPerLevel[LodIndex];
for (int32 BoxIndex = 1; BoxIndex < HoleBoundsAtLevel.Num(); ++BoxIndex)
{
const FIntPoint Min = HoleBoundsAtLevel[BoxIndex].Min;
const FIntPoint Max = HoleBoundsAtLevel[BoxIndex].Max;
for (int32 y = Min.Y; y <= Max.Y; ++y)
{
for (int32 x = Min.X; x <= Max.X; ++x)
{
OutHoleVertexLods[y * InSize + x] = LodIndex;
}
}
}
}
}
// Structure containing the hole render data required by the runtime rendering.
template <typename INDEX_TYPE>
struct FLandscapeHoleRenderData
{
TArray<INDEX_TYPE> HoleIndices;
int32 MinIndex;
int32 MaxIndex;
};
// Serialize the hole render data.
template <typename INDEX_TYPE>
void SerializeHoleRenderData(FMemoryArchive& Ar, FLandscapeHoleRenderData<INDEX_TYPE>& InHoleRenderData)
{
bool b16BitIndices = sizeof(INDEX_TYPE) == 2;
Ar << b16BitIndices;
Ar << InHoleRenderData.MinIndex;
Ar << InHoleRenderData.MaxIndex;
int32 HoleIndexCount = InHoleRenderData.HoleIndices.Num();
Ar << HoleIndexCount;
Ar.Serialize(InHoleRenderData.HoleIndices.GetData(), HoleIndexCount * sizeof(INDEX_TYPE));
}
// Take the processed hole map and generate the hole render data.
template <typename INDEX_TYPE>
void BuildHoleRenderData(int32 InNumSubsections, int32 InSubsectionSizeVerts, TArray<uint8> const& InVisibilityData, TArray<uint32>& InVertexToIndexMap, FLandscapeHoleRenderData<INDEX_TYPE>& OutHoleRenderData)
{
const int32 SizeVerts = InNumSubsections * InSubsectionSizeVerts;
const int32 SubsectionSizeQuads = InSubsectionSizeVerts - 1;
const uint8 VisThreshold = 170;
INDEX_TYPE MaxIndex = 0;
INDEX_TYPE MinIndex = TNumericLimits<INDEX_TYPE>::Max();
for (int32 SubY = 0; SubY < InNumSubsections; SubY++)
{
for (int32 SubX = 0; SubX < InNumSubsections; SubX++)
{
for (int32 y = 0; y < SubsectionSizeQuads; y++)
{
for (int32 x = 0; x < SubsectionSizeQuads; x++)
{
const int32 x0 = x;
const int32 y0 = y;
const int32 x1 = x + 1;
const int32 y1 = y + 1;
const int32 VertexIndex = (SubY * InSubsectionSizeVerts + y0) * SizeVerts + SubX * InSubsectionSizeVerts + x0;
const bool bIsHole = InVisibilityData[VertexIndex] < VisThreshold;
if (bIsHole)
{
INDEX_TYPE i00 = InVertexToIndexMap[FLandscapeVertexRef::GetVertexIndex(FLandscapeVertexRef(x0, y0, SubX, SubY), InNumSubsections, InSubsectionSizeVerts)];
INDEX_TYPE i10 = InVertexToIndexMap[FLandscapeVertexRef::GetVertexIndex(FLandscapeVertexRef(x1, y0, SubX, SubY), InNumSubsections, InSubsectionSizeVerts)];
INDEX_TYPE i11 = InVertexToIndexMap[FLandscapeVertexRef::GetVertexIndex(FLandscapeVertexRef(x1, y1, SubX, SubY), InNumSubsections, InSubsectionSizeVerts)];
INDEX_TYPE i01 = InVertexToIndexMap[FLandscapeVertexRef::GetVertexIndex(FLandscapeVertexRef(x0, y1, SubX, SubY), InNumSubsections, InSubsectionSizeVerts)];
OutHoleRenderData.HoleIndices.Add(i00);
OutHoleRenderData.HoleIndices.Add(i11);
OutHoleRenderData.HoleIndices.Add(i10);
OutHoleRenderData.HoleIndices.Add(i00);
OutHoleRenderData.HoleIndices.Add(i01);
OutHoleRenderData.HoleIndices.Add(i11);
// Update the min/max index ranges
MaxIndex = FMath::Max<INDEX_TYPE>(MaxIndex, i00);
MinIndex = FMath::Min<INDEX_TYPE>(MinIndex, i00);
MaxIndex = FMath::Max<INDEX_TYPE>(MaxIndex, i10);
MinIndex = FMath::Min<INDEX_TYPE>(MinIndex, i10);
MaxIndex = FMath::Max<INDEX_TYPE>(MaxIndex, i11);
MinIndex = FMath::Min<INDEX_TYPE>(MinIndex, i11);
MaxIndex = FMath::Max<INDEX_TYPE>(MaxIndex, i01);
MinIndex = FMath::Min<INDEX_TYPE>(MinIndex, i01);
}
}
}
}
}
OutHoleRenderData.MinIndex = MinIndex;
OutHoleRenderData.MaxIndex = MaxIndex;
}
// Generates vertex and index buffer data from the component's height map and visibility textures.
// For use on mobile platforms that don't use vertex texture fetch for height or alpha testing for visibility.
void ULandscapeComponent::GeneratePlatformVertexData(const ITargetPlatform* TargetPlatform)
{
if (IsTemplate())
{
return;
}
check(GetHeightmap());
check(GetHeightmap()->Source.GetFormat() == TSF_BGRA8);
TArray<uint8> NewPlatformData;
FMemoryWriter PlatformAr(NewPlatformData);
const int32 SubsectionSizeVerts = SubsectionSizeQuads + 1;
const int32 MaxLOD = FMath::CeilLogTwo(SubsectionSizeVerts) - 1;
const int32 NumMips = FMath::Min(LANDSCAPE_MAX_ES_LOD, GetHeightmap()->Source.GetNumMips());
const float HeightmapSubsectionOffsetU = (float)(SubsectionSizeVerts) / (float)GetHeightmap()->Source.GetSizeX();
const float HeightmapSubsectionOffsetV = (float)(SubsectionSizeVerts) / (float)GetHeightmap()->Source.GetSizeY();
// Get the required height mip data
TArray<TArray64<uint8>> HeightmapMipRawData;
TArray64<FColor*> HeightmapMipData;
for (int32 MipIdx = 0; MipIdx < NumMips; MipIdx++)
{
int32 MipSubsectionSizeVerts = (SubsectionSizeVerts) >> MipIdx;
if (MipSubsectionSizeVerts > 1)
{
new(HeightmapMipRawData) TArray64<uint8>();
GetHeightmap()->Source.GetMipData(HeightmapMipRawData.Last(), MipIdx);
HeightmapMipData.Add((FColor*)HeightmapMipRawData.Last().GetData());
}
}
// Get any hole data
int32 NumHoleLods = 0;
TArray< uint8 > VisibilityData;
if (ComponentHasVisibilityPainted() && GetLandscapeProxy()->bMeshHoles)
{
const TArray<FWeightmapLayerAllocationInfo>& ComponentWeightmapLayerAllocations = GetWeightmapLayerAllocations();
for (int32 AllocIdx = 0; AllocIdx < ComponentWeightmapLayerAllocations.Num(); AllocIdx++)
{
const FWeightmapLayerAllocationInfo& AllocInfo = ComponentWeightmapLayerAllocations[AllocIdx];
if (AllocInfo.LayerInfo == ALandscapeProxy::VisibilityLayer)
{
NumHoleLods = FMath::Clamp<int32>(GetLandscapeProxy()->MeshHolesMaxLod, 1, NumMips);
FLandscapeComponentDataInterface CDI(this, 0);
CDI.GetWeightmapTextureData(AllocInfo.LayerInfo, VisibilityData);
break;
}
}
}
// Layout index buffer to determine best vertex order.
// This vertex layout code is duplicated in FLandscapeSharedBuffers::CreateIndexBuffers() to create matching index buffers at runtime.
const int32 NumVertices = FMath::Square(SubsectionSizeVerts * NumSubsections);
TArray<uint32> VertexToIndexMap;
VertexToIndexMap.AddUninitialized(NumVertices);
FMemory::Memset(VertexToIndexMap.GetData(), 0xFF, NumVertices * sizeof(uint32));
TArray<FLandscapeVertexRef> VertexOrder;
VertexOrder.Empty(NumVertices);
// Can't stream if the number of hole LODs is greater than the number of streaming LODs
const int32 MaxLODClamp = FMath::Min((uint32)GetLandscapeProxy()->MaxLODLevel, (uint32)MAX_MESH_LOD_COUNT - 1u);
const bool bStreamLandscapeMeshLODs = TargetPlatform
&& TargetPlatform->SupportsFeature(ETargetPlatformFeatures::LandscapeMeshLODStreaming)
&& NumHoleLods <= FMath::Min(MaxLOD, MaxLODClamp);
int32 NumStreamingLODs = bStreamLandscapeMeshLODs ? FMath::Min(MaxLOD, MaxLODClamp) : 0;
NumStreamingLODs = FMath::Max(0, NumStreamingLODs - 5); // Always inline bottom 5 LODs
TArray<int32> StreamingLODVertStartOffsets;
StreamingLODVertStartOffsets.AddUninitialized(NumStreamingLODs);
for (int32 Mip = MaxLOD; Mip >= 0; Mip--)
{
int32 LodSubsectionSizeQuads = (SubsectionSizeVerts >> Mip) - 1;
float MipRatio = (float)SubsectionSizeQuads / (float)LodSubsectionSizeQuads; // Morph current MIP to base MIP
if (Mip < NumStreamingLODs)
{
StreamingLODVertStartOffsets[Mip] = VertexOrder.Num();
}
for (int32 SubY = 0; SubY < NumSubsections; SubY++)
{
for (int32 SubX = 0; SubX < NumSubsections; SubX++)
{
for (int32 Y = 0; Y < LodSubsectionSizeQuads; Y++)
{
for (int32 X = 0; X < LodSubsectionSizeQuads; X++)
{
for (int32 CornerId = 0; CornerId < 4; CornerId++)
{
const int32 CornerX = FMath::RoundToInt((float)(X + (CornerId & 1)) * MipRatio);
const int32 CornerY = FMath::RoundToInt((float)(Y + (CornerId >> 1)) * MipRatio);
const FLandscapeVertexRef VertexRef(CornerX, CornerY, SubX, SubY);
const int32 VertexIndex = FLandscapeVertexRef::GetVertexIndex(VertexRef, NumSubsections, SubsectionSizeVerts);
if (VertexToIndexMap[VertexIndex] == 0xFFFFFFFF)
{
VertexToIndexMap[VertexIndex] = VertexOrder.Num();
VertexOrder.Add(VertexRef);
}
}
}
}
}
}
}
if (VertexOrder.Num() != NumVertices)
{
UE_LOG(LogLandscape, Warning, TEXT("VertexOrder count of %d did not match expected size of %d"), VertexOrder.Num(), NumVertices);
}
// Build and serialize hole render data which includes a unique index buffer with the holes missing.
// This fills HoleVertexLods which is required for filling the vertex data.
TArray<uint8> HoleVertexLods;
PlatformAr << NumHoleLods;
if (NumHoleLods > 0)
{
TArray<FIntBox2D> HoleBounds;
GetHoleBounds(SubsectionSizeVerts * NumSubsections, VisibilityData, HoleBounds);
BuildHoleVertexLods(SubsectionSizeVerts * NumSubsections, NumHoleLods, HoleBounds, HoleVertexLods);
if (NumVertices <= UINT16_MAX)
{
FLandscapeHoleRenderData<uint16> HoleRenderData;
BuildHoleRenderData(NumSubsections, SubsectionSizeVerts, VisibilityData, VertexToIndexMap, HoleRenderData);
SerializeHoleRenderData(PlatformAr, HoleRenderData);
}
else
{
FLandscapeHoleRenderData<uint32> HoleRenderData;
BuildHoleRenderData(NumSubsections, SubsectionSizeVerts, VisibilityData, VertexToIndexMap, HoleRenderData);
SerializeHoleRenderData(PlatformAr, HoleRenderData);
}
}
// Fill in the vertices in the specified order.
const int32 SizeVerts = SubsectionSizeVerts * NumSubsections;
int32 NumInlineMobileVertices = NumStreamingLODs > 0 ? StreamingLODVertStartOffsets.Last() : FMath::Square(SizeVerts);
TArray<FLandscapeMobileVertex> InlineMobileVertices;
InlineMobileVertices.AddZeroed(NumInlineMobileVertices);
FLandscapeMobileVertex* DstVert = InlineMobileVertices.GetData();
int32 StreamingLODIdx = NumStreamingLODs - 1;
TArray<TArray<uint8>> StreamingLODData;
StreamingLODData.Empty(NumStreamingLODs);
StreamingLODData.AddDefaulted(NumStreamingLODs);
for (int32 Idx = 0; Idx < NumVertices; Idx++)
{
if (StreamingLODIdx >= 0
&& StreamingLODIdx >= NumHoleLods - 1
&& Idx >= StreamingLODVertStartOffsets[StreamingLODIdx])
{
const int32 EndIdx = StreamingLODIdx - 1 < 0 || StreamingLODIdx == NumHoleLods - 1 ?
FMath::Square(SizeVerts) :
StreamingLODVertStartOffsets[StreamingLODIdx - 1];
const int32 NumVerts = EndIdx - StreamingLODVertStartOffsets[StreamingLODIdx];
TArray<uint8>& StreamingLOD = StreamingLODData[StreamingLODIdx];
StreamingLOD.Empty(NumVerts * sizeof(FLandscapeMobileVertex));
StreamingLOD.AddZeroed(NumVerts * sizeof(FLandscapeMobileVertex));
DstVert = (FLandscapeMobileVertex*)StreamingLOD.GetData();
--StreamingLODIdx;
}
// Store XY position info
const int32 X = VertexOrder[Idx].X;
const int32 Y = VertexOrder[Idx].Y;
check(X < 256 && Y < 256);
DstVert->Position[0] = X;
DstVert->Position[1] = Y;
const int32 SubX = VertexOrder[Idx].SubX;
const int32 SubY = VertexOrder[Idx].SubY;
check(SubX < 2 && SubY < 2);
DstVert->Position[2] = (SubX << 1) | SubY;
// Store hole info
const int32 VertexIndex = (SubY * SubsectionSizeVerts + Y) * SizeVerts + SubX * SubsectionSizeVerts + X;
const int32 HoleVertexLod = (NumHoleLods > 0) ? HoleVertexLods[VertexIndex] : 0;
const int32 HoleMaxLod = (NumHoleLods > 0) ? NumHoleLods : 0;
check(HoleMaxLod < 8 && HoleVertexLod < 8);
DstVert->Position[2] |= (HoleMaxLod << 5) | (HoleVertexLod << 2);
// Calculate min/max height for packing
TArray<int32> MipHeights;
MipHeights.AddZeroed(HeightmapMipData.Num());
uint16 MaxHeight = 0, MinHeight = 65535;
float HeightmapScaleBiasZ = HeightmapScaleBias.Z + HeightmapSubsectionOffsetU * (float)SubX;
float HeightmapScaleBiasW = HeightmapScaleBias.W + HeightmapSubsectionOffsetV * (float)SubY;
int32 BaseMipOfsX = FMath::RoundToInt(HeightmapScaleBiasZ * (float)GetHeightmap()->Source.GetSizeX());
int32 BaseMipOfsY = FMath::RoundToInt(HeightmapScaleBiasW * (float)GetHeightmap()->Source.GetSizeY());
for (int32 Mip = 0; Mip < HeightmapMipData.Num(); ++Mip)
{
int32 MipSizeX = GetHeightmap()->Source.GetSizeX() >> Mip;
int32 CurrentMipOfsX = BaseMipOfsX >> Mip;
int32 CurrentMipOfsY = BaseMipOfsY >> Mip;
int32 MipX = X >> Mip;
int32 MipY = Y >> Mip;
FColor* CurrentMipSrcRow = HeightmapMipData[Mip] + (CurrentMipOfsY + MipY) * MipSizeX + CurrentMipOfsX;
uint16 Height = CurrentMipSrcRow[MipX].R << 8 | CurrentMipSrcRow[MipX].G;
MipHeights[Mip] = Height;
MaxHeight = FMath::Max(MaxHeight, Height);
MinHeight = FMath::Min(MinHeight, Height);
}
DstVert->LODHeights[0] = MinHeight >> 8;
DstVert->LODHeights[1] = MinHeight & 0xff;
// Quantize height delta so we can store in 8 bits in the spare Position channel
uint16 HeightDelta = FMath::Max(MaxHeight - MinHeight, 1);
HeightDelta = (HeightDelta + 255) & (~255);
DstVert->Position[3] = HeightDelta >> 8;
// Now quantize the mip heights to 255 steps between MinHeight and MinHeight+HeightDelta
for (int32 Mip = 0; Mip < HeightmapMipData.Num(); ++Mip)
{
check(Mip < 6);
DstVert->LODHeights[2 + Mip] = FMath::RoundToInt(((float)(MipHeights[Mip] - MinHeight) / (float)HeightDelta) * 255.f);
}
DstVert++;
}
// Serialize vertex buffer
PlatformAr << NumInlineMobileVertices;
PlatformAr.Serialize(InlineMobileVertices.GetData(), NumInlineMobileVertices*sizeof(FLandscapeMobileVertex));
// Copy to PlatformData as Compressed
PlatformData.InitializeFromUncompressedData(NewPlatformData, StreamingLODData);
}
FName ALandscapeProxy::GenerateUniqueLandscapeTextureName(UObject* InOuter, TextureGroup InLODGroup) const
{
FName BaseName;
if (InLODGroup == TEXTUREGROUP_Terrain_Heightmap)
{
BaseName = "Heightmap";
}
else if (InLODGroup == TEXTUREGROUP_Terrain_Weightmap)
{
BaseName = "Weightmap";
}
return MakeUniqueObjectName(InOuter, UTexture2D::StaticClass(), BaseName);
}
UTexture2D* ALandscapeProxy::CreateLandscapeTexture(int32 InSizeX, int32 InSizeY, TextureGroup InLODGroup, ETextureSourceFormat InFormat, UObject* OptionalOverrideOuter, bool bCompress) const
{
UObject* TexOuter = OptionalOverrideOuter ? OptionalOverrideOuter : const_cast<ALandscapeProxy*>(this);
UTexture2D* NewTexture = NewObject<UTexture2D>(TexOuter, GenerateUniqueLandscapeTextureName(TexOuter, InLODGroup));
NewTexture->Source.Init2DWithMipChain(InSizeX, InSizeY, InFormat);
NewTexture->SRGB = false;
NewTexture->CompressionNone = !bCompress;
NewTexture->MipGenSettings = TMGS_LeaveExistingMips;
NewTexture->AddressX = TA_Clamp;
NewTexture->AddressY = TA_Clamp;
NewTexture->LODGroup = InLODGroup;
return NewTexture;
}
UTexture2D* ALandscapeProxy::CreateLandscapeToolTexture(int32 InSizeX, int32 InSizeY, TextureGroup InLODGroup, ETextureSourceFormat InFormat) const
{
UObject* TexOuter = const_cast<ALandscapeProxy*>(this);
UTexture2D* NewTexture = NewObject<UTexture2D>(TexOuter, GenerateUniqueLandscapeTextureName(TexOuter, InLODGroup));
NewTexture->Source.Init(InSizeX, InSizeY, 1, 1, InFormat);
NewTexture->SRGB = false;
NewTexture->CompressionNone = true;
NewTexture->MipGenSettings = TMGS_NoMipmaps;
NewTexture->AddressX = TA_Clamp;
NewTexture->AddressY = TA_Clamp;
NewTexture->LODGroup = InLODGroup;
return NewTexture;
}
ULandscapeWeightmapUsage* ALandscapeProxy::CreateWeightmapUsage()
{
// NonTransactional on purpose : it's too much trouble to have usages transactional since they're present in the proxies and duplicated in possibly multiple components,
// plus some edit layers (the splines layer, namely, which is procedural) are non-transactional, which complicates things further. Instead, we just regenerate the usages
// on undo
return NewObject<ULandscapeWeightmapUsage>(this, ULandscapeWeightmapUsage::StaticClass(), NAME_None, RF_NoFlags);
}
void ALandscapeProxy::RemoveOverlappingComponent(ULandscapeComponent* Component)
{
Modify();
Component->Modify();
if (Component->CollisionComponent.IsValid() && (Component->CollisionComponent->RenderComponent.Get() == Component || Component->CollisionComponent->RenderComponent.IsNull()))
{
Component->CollisionComponent->Modify();
CollisionComponents.Remove(Component->CollisionComponent.Get());
Component->CollisionComponent.Get()->DestroyComponent();
}
LandscapeComponents.Remove(Component);
Component->DestroyComponent();
}
TArray<FLinearColor> ALandscapeProxy::SampleRTData(UTextureRenderTarget2D* InRenderTarget, FLinearColor InRect)
{
if (!InRenderTarget)
{
FMessageLog("Blueprint").Warning(LOCTEXT("SampleRTData_InvalidRenderTarget", "SampleRTData: Render Target must be non-null."));
return { FLinearColor(0,0,0,0) };
}
else if (!InRenderTarget->GetResource())
{
FMessageLog("Blueprint").Warning(LOCTEXT("SampleRTData_ReleasedRenderTarget", "SampleRTData: Render Target has been released."));
return { FLinearColor(0,0,0,0) };
}
else
{
ETextureRenderTargetFormat format = (InRenderTarget->RenderTargetFormat);
if ((format == (RTF_RGBA16f)) || (format == (RTF_RGBA32f)) || (format == (RTF_RGBA8)))
{
FTextureRenderTargetResource* RTResource = InRenderTarget->GameThread_GetRenderTargetResource();
InRect.R = FMath::Clamp(int(InRect.R), 0, InRenderTarget->SizeX - 1);
InRect.G = FMath::Clamp(int(InRect.G), 0, InRenderTarget->SizeY - 1);
InRect.B = FMath::Clamp(int(InRect.B), int(InRect.R + 1), InRenderTarget->SizeX);
InRect.A = FMath::Clamp(int(InRect.A), int(InRect.G + 1), InRenderTarget->SizeY);
FIntRect Rect = FIntRect(InRect.R, InRect.G, InRect.B, InRect.A);
FReadSurfaceDataFlags ReadPixelFlags(RCM_MinMax);
TArray<FColor> OutLDR;
TArray<FLinearColor> OutHDR;
TArray<FLinearColor> OutVals;
bool ishdr = ((format == (RTF_R16f)) || (format == (RTF_RG16f)) || (format == (RTF_RGBA16f)) || (format == (RTF_R32f)) || (format == (RTF_RG32f)) || (format == (RTF_RGBA32f)));
if (!ishdr)
{
RTResource->ReadPixels(OutLDR, ReadPixelFlags, Rect);
for (auto i : OutLDR)
{
OutVals.Add(FLinearColor(float(i.R), float(i.G), float(i.B), float(i.A)) / 255.0f);
}
}
else
{
RTResource->ReadLinearColorPixels(OutHDR, ReadPixelFlags, Rect);
return OutHDR;
}
return OutVals;
}
}
FMessageLog("Blueprint").Warning(LOCTEXT("SampleRTData_InvalidTexture", "SampleRTData: Currently only 4 channel formats are supported: RTF_RGBA8, RTF_RGBA16f, and RTF_RGBA32f."));
return { FLinearColor(0,0,0,0) };
}
bool ALandscapeProxy::LandscapeImportHeightmapFromRenderTarget(UTextureRenderTarget2D* InRenderTarget, bool InImportHeightFromRGChannel)
{
uint64 StartCycle = FPlatformTime::Cycles64();
ALandscape* Landscape = GetLandscapeActor();
if (Landscape == nullptr)
{
FMessageLog("Blueprint").Error(LOCTEXT("LandscapeImportHeightmapFromRenderTarget_NullLandscape", "LandscapeImportHeightmapFromRenderTarget: Landscape must be non-null."));
return false;
}
if (Landscape->HasLayersContent())
{
//todo: Support an edit layer name input parameter to support import to edit layers.
FMessageLog("Blueprint").Error(LOCTEXT("LandscapeImportHeightmapFromRenderTarget_LandscapeLayersNotSupported", "LandscapeImportHeightmapFromRenderTarget: Cannot import to landscape with Edit Layers enabled."));
return false;
}
int32 MinX, MinY, MaxX, MaxY;
ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo();
if (!LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY))
{
FMessageLog("Blueprint").Error(LOCTEXT("LandscapeImportHeightmapFromRenderTarget_InvalidLandscapeExtends", "LandscapeImportHeightmapFromRenderTarget: The landscape min extends are invalid."));
return false;
}
if (InRenderTarget == nullptr || InRenderTarget->GetResource() == nullptr)
{
FMessageLog("Blueprint").Error(LOCTEXT("LandscapeImportHeightmapFromRenderTarget_InvalidRT", "LandscapeImportHeightmapFromRenderTarget: Render Target must be non null and not released."));
return false;
}
FTextureRenderTargetResource* RenderTargetResource = InRenderTarget->GameThread_GetRenderTargetResource();
FIntRect SampleRect = FIntRect(0, 0, FMath::Min(1 + MaxX - MinX, InRenderTarget->SizeX), FMath::Min(1 + MaxY - MinY, InRenderTarget->SizeY));
TArray<uint16> HeightData;
switch (InRenderTarget->RenderTargetFormat)
{
case RTF_RGBA16f:
case RTF_RGBA32f:
{
TArray<FLinearColor> OutputRTHeightmap;
OutputRTHeightmap.Reserve(SampleRect.Width() * SampleRect.Height());
RenderTargetResource->ReadLinearColorPixels(OutputRTHeightmap, FReadSurfaceDataFlags(RCM_MinMax, CubeFace_MAX), SampleRect);
HeightData.Reserve(OutputRTHeightmap.Num());
for (auto LinearColor : OutputRTHeightmap)
{
if (InImportHeightFromRGChannel)
{
FColor Color = LinearColor.ToFColor(false);
uint16 Height = ((Color.R << 8) | Color.G);
HeightData.Add(Height);
}
else
{
HeightData.Add((uint16)LinearColor.R);
}
}
}
break;
case RTF_RGBA8:
{
TArray<FColor> OutputRTHeightmap;
OutputRTHeightmap.Reserve(SampleRect.Width() * SampleRect.Height());
RenderTargetResource->ReadPixels(OutputRTHeightmap, FReadSurfaceDataFlags(RCM_MinMax, CubeFace_MAX), SampleRect);
HeightData.Reserve(OutputRTHeightmap.Num());
for (FColor Color : OutputRTHeightmap)
{
uint16 Height = ((Color.R << 8) | Color.G);
HeightData.Add(Height);
}
}
break;
default:
{
FMessageLog("Blueprint").Error(LOCTEXT("LandscapeImportHeightmapFromRenderTarget_InvalidRTFormat", "LandscapeImportHeightmapFromRenderTarget: The Render Target format is invalid. We only support RTF_RGBA16f, RTF_RGBA32f, RTF_RGBA8"));
return false;
}
}
FScopedTransaction Transaction(LOCTEXT("Undo_ImportHeightmap", "Importing Landscape Heightmap"));
FHeightmapAccessor<false> HeightmapAccessor(LandscapeInfo);
HeightmapAccessor.SetData(MinX, MinY, SampleRect.Width() - 1, SampleRect.Height() - 1, HeightData.GetData());
double SecondsTaken = FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - StartCycle);
UE_LOG(LogLandscapeBP, Display, TEXT("Took %f seconds to import heightmap from render target."), SecondsTaken);
return true;
}
#endif
bool ALandscapeProxy::LandscapeExportHeightmapToRenderTarget(UTextureRenderTarget2D* InRenderTarget, bool bInExportHeightIntoRGChannel, bool InExportLandscapeProxies)
{
#if WITH_EDITOR
uint64 StartCycle = FPlatformTime::Cycles64();
UMaterial* HeightmapRenderMaterial = LoadObject<UMaterial>(nullptr, TEXT("/Engine/EditorLandscapeResources/Landscape_Heightmap_To_RenderTarget2D.Landscape_Heightmap_To_RenderTarget2D"));
if (HeightmapRenderMaterial == nullptr)
{
FMessageLog("Blueprint").Error(LOCTEXT("LandscapeExportHeightmapToRenderTarget_Landscape_Heightmap_To_RenderTarget2D.", "LandscapeExportHeightmapToRenderTarget: Material Landscape_Heightmap_To_RenderTarget2D not found in engine content."));
return false;
}
TArray<ULandscapeComponent*> LandscapeComponentsToExport;
// Export the component of the specified proxy
LandscapeComponentsToExport.Append(LandscapeComponents);
// If requested, export all proxies
if (InExportLandscapeProxies && (GetLandscapeActor() == this))
{
ULandscapeInfo* LandscapeInfo = GetLandscapeInfo();
for (ALandscapeProxy* Proxy : LandscapeInfo->Proxies)
{
LandscapeComponentsToExport.Append(Proxy->LandscapeComponents);
}
}
if (LandscapeComponentsToExport.Num() == 0)
{
return true;
}
UWorld* World = GEditor->GetEditorWorldContext().World();
FTextureRenderTargetResource* RenderTargetResource = InRenderTarget->GameThread_GetRenderTargetResource();
// Create a canvas for the render target and clear it to black
FCanvas Canvas(RenderTargetResource, nullptr, FGameTime(), World->FeatureLevel);
Canvas.Clear(FLinearColor::Black);
// Find exported component's base offset
FIntRect ComponentsExtent(MAX_int32, MAX_int32, MIN_int32, MIN_int32);
for (ULandscapeComponent* Component : LandscapeComponentsToExport)
{
Component->GetComponentExtent(ComponentsExtent.Min.X, ComponentsExtent.Min.Y, ComponentsExtent.Max.X, ComponentsExtent.Max.Y);
}
FIntPoint ExportBaseOffset = ComponentsExtent.Min;
struct FTrianglePerMID
{
UMaterialInstanceDynamic* HeightmapMID;
TArray<FCanvasUVTri> TriangleList;
};
TMap<UTexture*, FTrianglePerMID> TrianglesPerHeightmap;
for (const ULandscapeComponent* Component : LandscapeComponentsToExport)
{
FTrianglePerMID* TrianglesPerMID = TrianglesPerHeightmap.Find(Component->GetHeightmap());
if (TrianglesPerMID == nullptr)
{
FTrianglePerMID Data;
Data.HeightmapMID = UMaterialInstanceDynamic::Create(HeightmapRenderMaterial, this);
Data.HeightmapMID->SetTextureParameterValue(TEXT("Heightmap"), Component->GetHeightmap());
Data.HeightmapMID->SetScalarParameterValue(TEXT("ExportHeightIntoRGChannel"), bInExportHeightIntoRGChannel);
TrianglesPerMID = &TrianglesPerHeightmap.Add(Component->GetHeightmap(), Data);
}
FIntPoint ComponentSectionBase = Component->GetSectionBase();
FIntPoint ComponentHeightmapTextureSize(Component->GetHeightmap()->Source.GetSizeX(), Component->GetHeightmap()->Source.GetSizeY());
int32 SubsectionSizeVerts = Component->SubsectionSizeQuads + 1;
float HeightmapSubsectionOffsetU = (float)(SubsectionSizeVerts) / (float)ComponentHeightmapTextureSize.X;
float HeightmapSubsectionOffsetV = (float)(SubsectionSizeVerts) / (float)ComponentHeightmapTextureSize.Y;
for (int8 SubY = 0; SubY < NumSubsections; ++SubY)
{
for (int8 SubX = 0; SubX < NumSubsections; ++SubX)
{
FIntPoint SubSectionSectionBase = ComponentSectionBase - ExportBaseOffset;
SubSectionSectionBase.X += Component->SubsectionSizeQuads * SubX;
SubSectionSectionBase.Y += Component->SubsectionSizeQuads * SubY;
// Offset for this component's data in heightmap texture
float HeightmapOffsetU = Component->HeightmapScaleBias.Z + HeightmapSubsectionOffsetU * (float)SubX;
float HeightmapOffsetV = Component->HeightmapScaleBias.W + HeightmapSubsectionOffsetV * (float)SubY;
FCanvasUVTri Tri1;
Tri1.V0_Pos = FVector2D(SubSectionSectionBase.X, SubSectionSectionBase.Y);
Tri1.V1_Pos = FVector2D(SubSectionSectionBase.X + SubsectionSizeVerts, SubSectionSectionBase.Y);
Tri1.V2_Pos = FVector2D(SubSectionSectionBase.X + SubsectionSizeVerts, SubSectionSectionBase.Y + SubsectionSizeVerts);
Tri1.V0_UV = FVector2D(HeightmapOffsetU, HeightmapOffsetV);
Tri1.V1_UV = FVector2D(HeightmapOffsetU + HeightmapSubsectionOffsetU, HeightmapOffsetV);
Tri1.V2_UV = FVector2D(HeightmapOffsetU + HeightmapSubsectionOffsetU, HeightmapOffsetV + HeightmapSubsectionOffsetV);
TrianglesPerMID->TriangleList.Add(Tri1);
FCanvasUVTri Tri2;
Tri2.V0_Pos = FVector2D(SubSectionSectionBase.X + SubsectionSizeVerts, SubSectionSectionBase.Y + SubsectionSizeVerts);
Tri2.V1_Pos = FVector2D(SubSectionSectionBase.X, SubSectionSectionBase.Y + SubsectionSizeVerts);
Tri2.V2_Pos = FVector2D(SubSectionSectionBase.X, SubSectionSectionBase.Y);
Tri2.V0_UV = FVector2D(HeightmapOffsetU + HeightmapSubsectionOffsetU, HeightmapOffsetV + HeightmapSubsectionOffsetV);
Tri2.V1_UV = FVector2D(HeightmapOffsetU, HeightmapOffsetV + HeightmapSubsectionOffsetV);
Tri2.V2_UV = FVector2D(HeightmapOffsetU, HeightmapOffsetV);
TrianglesPerMID->TriangleList.Add(Tri2);
}
}
}
for (auto& TriangleList : TrianglesPerHeightmap)
{
FCanvasTriangleItem TriItemList(MoveTemp(TriangleList.Value.TriangleList), nullptr);
TriItemList.MaterialRenderProxy = TriangleList.Value.HeightmapMID->GetRenderProxy();
TriItemList.BlendMode = SE_BLEND_Opaque;
TriItemList.SetColor(FLinearColor::White);
TriItemList.Draw(&Canvas);
}
TrianglesPerHeightmap.Reset();
// Tell the rendering thread to draw any remaining batched elements
Canvas.Flush_GameThread(true);
ENQUEUE_RENDER_COMMAND(DrawHeightmapRTCommand)(
[RenderTargetResource](FRHICommandListImmediate& RHICmdList)
{
TransitionAndCopyTexture(RHICmdList, RenderTargetResource->GetRenderTargetTexture(), RenderTargetResource->TextureRHI, {});
});
FlushRenderingCommands();
double SecondsTaken = FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - StartCycle);
UE_LOG(LogLandscapeBP, Display, TEXT("Took %f seconds to export heightmap to render target."), SecondsTaken);
#endif
return true;
}
#if WITH_EDITOR
bool ALandscapeProxy::LandscapeImportWeightmapFromRenderTarget(UTextureRenderTarget2D* InRenderTarget, FName InLayerName)
{
ALandscape* Landscape = GetLandscapeActor();
if (Landscape != nullptr)
{
if (Landscape->HasLayersContent())
{
//todo: Support an edit layer name input parameter to support import to edit layers.
FMessageLog("Blueprint").Error(LOCTEXT("LandscapeImportWeightmapFromRenderTarget_LandscapeLayersNotSupported", "LandscapeImportWeightmapFromRenderTarget: Cannot import to landscape with Edit Layers enabled."));
return false;
}
ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo();
int32 MinX, MinY, MaxX, MaxY;
if (LandscapeInfo && LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY))
{
const uint32 LandscapeWidth = (uint32)(1 + MaxX - MinX);
const uint32 LandscapeHeight = (uint32)(1 + MaxY - MinY);
FLinearColor SampleRect = FLinearColor(0, 0, LandscapeWidth, LandscapeHeight);
const uint32 RTWidth = InRenderTarget->SizeX;
const uint32 RTHeight = InRenderTarget->SizeY;
ETextureRenderTargetFormat format = (InRenderTarget->RenderTargetFormat);
if (RTWidth >= LandscapeWidth && RTHeight >= LandscapeHeight)
{
TArray<FLinearColor> RTData;
RTData = SampleRTData(InRenderTarget, SampleRect);
TArray<uint8> LayerData;
for (auto i : RTData)
{
LayerData.Add((uint8)(FMath::Clamp((float)i.R, 0.0f, 1.0f) * 255));
}
FLandscapeInfoLayerSettings CurWeightmapInfo;
int32 Index = LandscapeInfo->GetLayerInfoIndex(InLayerName, LandscapeInfo->GetLandscapeProxy());
if (ensure(Index != INDEX_NONE))
{
CurWeightmapInfo = LandscapeInfo->Layers[Index];
}
if (CurWeightmapInfo.LayerInfoObj == nullptr)
{
FMessageLog("Blueprint").Error(LOCTEXT("LandscapeImportRenderTarget_InvalidLayerInfoObject", "LandscapeImportWeightmapFromRenderTarget: Layers must first have Layer Info Objects assigned before importing."));
return false;
}
FScopedTransaction Transaction(LOCTEXT("Undo_ImportWeightmap", "Importing Landscape Layer"));
FAlphamapAccessor<false, false> AlphamapAccessor(LandscapeInfo, CurWeightmapInfo.LayerInfoObj);
AlphamapAccessor.SetData(MinX, MinY, MaxX, MaxY, LayerData.GetData(), ELandscapeLayerPaintingRestriction::None);
uint64 CycleEnd = FPlatformTime::Cycles64();
UE_LOG(LogLandscape, Verbose, TEXT("Took %f seconds to import heightmap from render target"), FPlatformTime::ToSeconds64(CycleEnd));
return true;
}
else
{
FMessageLog("Blueprint").Error(LOCTEXT("LandscapeImportRenderTarget_InvalidRenderTarget", "LandscapeImportWeightmapFromRenderTarget: Render target must be at least as large as landscape on each axis."));
return false;
}
}
else
{
return false;
}
}
FMessageLog("Blueprint").Error(LOCTEXT("LandscapeImportRenderTarget_NullLandscape.", "LandscapeImportWeightmapFromRenderTarget: Landscape must be non-null."));
return false;
}
bool ALandscapeProxy::LandscapeExportWeightmapToRenderTarget(UTextureRenderTarget2D* InRenderTarget, FName InLayerName)
{
return false;
}
#endif //WITH_EDITOR
#undef LOCTEXT_NAMESPACE