// 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(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& TerrainLayerWeightParameters = InCombinationMaterial->GetEditorOnlyStaticParameters().TerrainLayerWeightParameters; const TArray& 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& Allocations, UMaterialInterface* LandscapeMaterial, bool bMobile /*= false*/) { if (!LandscapeMaterial) { return FString(); } FString Result = LandscapeMaterial->GetPathName(); // Generate a string to describe each allocation TArray 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()); 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& 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 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(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(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& WeightmapBaseLayerAllocation = GetWeightmapLayerAllocations(); const TArray& 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(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 RecreateRenderStateContext; RecreateRenderStateContext.Emplace(this); TOptional 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& InOutRecreateRenderStateContext) { InOutRecreateRenderStateContext.Add(this); UpdateMaterialInstances_Internal(InOutMaterialContext); } void ALandscapeProxy::UpdateAllComponentMaterialInstances(FMaterialUpdateContext& InOutMaterialContext, TArray& 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 RecreateRenderStateContexts; RecreateRenderStateContexts.Reserve(LandscapeComponents.Num()); for (ULandscapeComponent* Component : LandscapeComponents) { RecreateRenderStateContexts.Emplace(Component); } TOptional 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 Components; Components.Add(this); GetLandscapeProxy()->FlushGrassComponents(&Components); } } TUniquePtr ALandscapeProxy::CreateClassActorDesc() const { return TUniquePtr(new FLandscapeActorDesc()); } bool ALandscapeProxy::GetReferencedContentObjects(TArray& 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 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 ULandscapeComponent::RepairInvalidTextures() { TArray AllTextures = GetGeneratedTextures(); TArray 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(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& LocalWeightmapTextures = GetWeightmapTextures(InEditLayerGuid); TArray& LocalWeightmapTextureUsages = GetWeightmapTexturesUsage(InEditLayerGuid); TArray& LocalWeightmapLayerAllocations = GetWeightmapLayerAllocations(InEditLayerGuid); if (Info) { LocalWeightmapTextureUsages.Empty(); LocalWeightmapTextureUsages.AddDefaulted(LocalWeightmapTextures.Num()); TArray 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* 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& ComponentWeightmapLayerAllocations = GetWeightmapLayerAllocations(); for (const auto& Allocation : ComponentWeightmapLayerAllocations) { LayerAllowList.AddUnique(Allocation.LayerInfo); } } // // LandscapeComponentAlphaInfo // struct FLandscapeComponentAlphaInfo { int32 LayerIndex; TArray 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(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(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 CollisionMipData; TArray64 SimpleCollisionMipData; TArray64 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 DominantLayerData; TArray 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(Proxy, NAME_None, RF_Transactional); CollisionComp = MeshCollisionComponent; } else { MeshCollisionComponent = nullptr; CollisionComp = NewObject(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(CollisionSubX1, 0, CollisionSize.SubsectionSizeQuads); const int32 VertY1 = FMath::Clamp(CollisionSubY1, 0, CollisionSize.SubsectionSizeQuads); const int32 VertX2 = FMath::Clamp(CollisionSubX2, 0, CollisionSize.SubsectionSizeQuads); const int32 VertY2 = FMath::Clamp(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& InCollisionDataPtrs, const TArray& 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(CollisionSubX1, 0, CollisionSize.SubsectionSizeQuads); const int32 VertY1 = FMath::Clamp(CollisionSubY1, 0, CollisionSize.SubsectionSizeQuads); const int32 VertX2 = FMath::Clamp(CollisionSubX2, 0, CollisionSize.SubsectionSizeQuads); const int32 VertY2 = FMath::Clamp(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 CandidateLayers; TArray CandidateDataPtrs; TArray SimpleCollisionDataPtrs; bool bExistingLayerMismatch = false; int32 DataLayerIdx = INDEX_NONE; const TArray& ComponentWeightmapLayerAllocations = GetWeightmapLayerAllocations(false); const TArray& 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& ComponentWeightmapsTexture = GetWeightmapTextures(); // Generate the dominant layer data TArray> WeightmapTextureMipData; TArray WeightmapTextureMipDataParam; WeightmapTextureMipData.Reserve(ComponentWeightmapsTexture.Num()); WeightmapTextureMipDataParam.Reserve(ComponentWeightmapsTexture.Num()); for (int32 WeightmapIdx = 0; WeightmapIdx < ComponentWeightmapsTexture.Num(); ++WeightmapIdx) { TArray64& MipData = WeightmapTextureMipData.AddDefaulted_GetRef(); ComponentWeightmapsTexture[WeightmapIdx]->Source.GetMipData(MipData, CollisionMipLevel); WeightmapTextureMipDataParam.Add((FColor*)MipData.GetData()); } TArray> SimpleCollisionWeightmapMipData; TArray SimpleCollisionWeightmapMipDataParam; if (SimpleCollisionMipLevel > CollisionMipLevel) { SimpleCollisionWeightmapMipData.Reserve(ComponentWeightmapsTexture.Num()); SimpleCollisionWeightmapMipDataParam.Reserve(ComponentWeightmapsTexture.Num()); for (int32 WeightmapIdx = 0; WeightmapIdx < ComponentWeightmapsTexture.Num(); ++WeightmapIdx) { TArray64& 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(Material); MIC; MIC = Cast(Material)) { Hash = FCrc::TypeCrc32(MIC->ParameterStateId, Hash); Material = MIC->Parent; } UMaterial* MaterialBase = Cast(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 const& InPhysicalMaterials, TArray 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& 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(MipSubX1, 0, MipSubsectionSizeQuads); int32 VertY1 = FMath::Clamp(MipSubY1, 0, MipSubsectionSizeQuads); int32 VertX2 = FMath::Clamp(MipSubX2, 0, MipSubsectionSizeQuads); int32 VertY2 = FMath::Clamp(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(PrevMipTexX + 1, PrevMipSizeU - 1); int32 PrevMipTexY1 = FMath::Min(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 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 void ULandscapeComponent::GenerateMipsTempl(int32 InNumSubsections, int32 InSubsectionSizeQuads, UTexture2D* Texture, DataType* BaseMipData) { // Stores pointers to the locked mip data TArray 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(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(InNumSubsections, InSubsectionSizeQuads, WeightmapTexture, BaseMipData); } namespace { template 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 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 void ULandscapeComponent::UpdateMipsTempl(int32 InNumSubsections, int32 InSubsectionSizeQuads, UTexture2D* Texture, TArray& 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(MipSubX1, 0, MipSubsectionSizeQuads); int32 VertY1 = FMath::Clamp(MipSubY1, 0, MipSubsectionSizeQuads); int32 VertX2 = FMath::Clamp(MipSubX2, 0, MipSubsectionSizeQuads); int32 VertY2 = FMath::Clamp(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(PrevMipTexX + 1, PrevMipSizeU - 1); int32 PrevMipTexY1 = FMath::Min(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(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(WeightmapSizeU >> Mip, 1); int32 MipSizeV = FMath::Max(WeightmapSizeV >> Mip, 1); int32 PrevMipSizeU = FMath::Max(WeightmapSizeU >> (Mip - 1), 1); int32 PrevMipSizeV = FMath::Max(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(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& WeightmapTextureMipData, int32 ComponentX1/*=0*/, int32 ComponentY1/*=0*/, int32 ComponentX2/*=MAX_int32*/, int32 ComponentY2/*=MAX_int32*/, struct FLandscapeTextureDataInfo* TextureDataInfo/*=nullptr*/) { UpdateMipsTempl(InNumSubsections, InSubsectionSizeQuads, WeightmapTexture, WeightmapTextureMipData, ComponentX1, ComponentY1, ComponentX2, ComponentY2, TextureDataInfo); } void ULandscapeComponent::UpdateDataMips(int32 InNumSubsections, int32 InSubsectionSizeQuads, UTexture2D* Texture, TArray& TextureMipData, int32 ComponentX1/*=0*/, int32 ComponentY1/*=0*/, int32 ComponentX2/*=MAX_int32*/, int32 ComponentY2/*=MAX_int32*/, struct FLandscapeTextureDataInfo* TextureDataInfo/*=nullptr*/) { UpdateMipsTempl(InNumSubsections, InSubsectionSizeQuads, Texture, TextureMipData, ComponentX1, ComponentY1, ComponentX2, ComponentY2, TextureDataInfo); } float ULandscapeComponent::GetLayerWeightAtLocation(const FVector& InLocation, ULandscapeLayerInfoObject* LayerInfo, TArray* LayerCache, bool bUseEditingWeightmap) { // Allocate and discard locally if no external cache is passed in. TArray 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(((X1 / SubsectionSizeQuads) * (SubsectionSizeQuads + 1)) + (X1 % SubsectionSizeQuads), Stride - 1); int32 IdxY1 = FMath::Min(((Y1 / SubsectionSizeQuads) * (SubsectionSizeQuads + 1)) + (Y1 % SubsectionSizeQuads), Stride - 1); int32 IdxX2 = FMath::Min(((X2 / SubsectionSizeQuads) * (SubsectionSizeQuads + 1)) + (X2 % SubsectionSizeQuads), Stride - 1); int32 IdxY2 = FMath::Min(((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& 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 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(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& 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 HeightmapTextureMipData; }; const TArray& ALandscapeProxy::GetLayersFromMaterial(UMaterialInterface* MaterialInterface) { if (MaterialInterface) { const FMaterialCachedExpressionData& CachedExpressionData = MaterialInterface->GetCachedExpressionData(); if (CachedExpressionData.EditorOnlyData) { return CachedExpressionData.EditorOnlyData->LandscapeLayerNames; } } return FMaterialCachedExpressionEditorOnlyData::EmptyData.LandscapeLayerNames; } const TArray& 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(InTemplate, Package, LayerObjectName); } else { LayerInfo = NewObject(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(Y,0,VertsY) * VertsX + FMath::Clamp(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>& InImportHeightData, const TCHAR* const InHeightmapFileName, const TMap>& InImportMaterialLayerInfos, ELandscapeImportAlphamapType InImportMaterialLayerType, const TArray* 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(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 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& HeightData = InImportHeightData.FindChecked(FinalLayerGuid); const TArray& ImportLayerInfos = InImportMaterialLayerInfos.FindChecked(FinalLayerGuid); // Calculate the normals for each of the two triangles per quad. TArray 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>> 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>& WeightValues = ComponentWeightValues[ComponentX + ComponentY*NumComponentsX]; // Import alphamap data into local array and check for unused layers for this component. TArray> 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& ComponentWeightmapLayerAllocations = LandscapeComponent->GetWeightmapLayerAllocations(); // Calculate weightmap weights for this component WeightValues.Empty(EditingAlphaLayerData.Num()); WeightValues.AddZeroed(EditingAlphaLayerData.Num()); ComponentWeightmapLayerAllocations.Empty(EditingAlphaLayerData.Num()); TArray> 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 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>& 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 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& ComponentWeightmapLayerAllocations = LandscapeComponent->GetWeightmapLayerAllocations(); TArray& ComponentWeightmapTextures = LandscapeComponent->GetWeightmapTextures(); TArray& 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(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 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 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 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 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 ComponentsToProcess; struct FLayerImportSettings { FGuid SourceLayerGuid; FGuid DestinationLayerGuid; }; TArray 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 LayersTextures; for (const FLayerImportSettings& ImportSettings : LayerImportSettings) { FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo, false); FScopedSetLandscapeEditingLayer Scope(LandscapeActor, ImportSettings.DestinationLayerGuid); const TArray* ImportHeightData = InImportHeightData.Find(ImportSettings.SourceLayerGuid); if (ImportHeightData != nullptr) { LandscapeEdit.SetHeightData(InMinX, InMinY, InMaxX, InMaxY, (uint16*)ImportHeightData->GetData(), 0, false, nullptr); } const TArray* 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 RegisteredLandscapeComponents; GetComponents(RegisteredLandscapeComponents); const FIntRect LandscapeSectionRect = GetBoundingRect(); const FVector2f LandscapeUVScale = FVector2f(1.0f, 1.0f) / FVector2f(LandscapeSectionRect.Size()); FStaticMeshAttributes Attributes(OutRawMesh); TVertexAttributesRef VertexPositions = Attributes.GetVertexPositions(); TEdgeAttributesRef EdgeHardnesses = Attributes.GetEdgeHardnesses(); TPolygonGroupAttributesRef PolygonGroupImportedMaterialSlotNames = Attributes.GetPolygonGroupMaterialSlotNames(); TVertexInstanceAttributesRef VertexInstanceNormals = Attributes.GetVertexInstanceNormals(); TVertexInstanceAttributesRef VertexInstanceTangents = Attributes.GetVertexInstanceTangents(); TVertexInstanceAttributesRef VertexInstanceBinormalSigns = Attributes.GetVertexInstanceBinormalSigns(); TVertexInstanceAttributesRef VertexInstanceColors = Attributes.GetVertexInstanceColors(); TVertexInstanceAttributesRef 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(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 VisDataMap; const TArray& 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 VertIndexAndZ; VertIndexAndZ.Reserve(ComponentSizeQuadsLOD*ComponentSizeQuadsLOD*UE_ARRAY_COUNT(QuadPattern)); int32 CurrentIndex = 0; TMap 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 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 VertexIDs; VertexIDs.Reserve(UE_ARRAY_COUNT(QuadPattern)); TArray 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 PerimeterVertexInstances; PerimeterVertexInstances.SetNum(3); for (int32 Corner = 0; Corner < 3; ++Corner) { PerimeterVertexInstances[Corner] = VertexInstanceIDs[BaseIndex + Corner]; } // Insert a polygon into the mesh TArray 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 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("LandscapeEditor"); FLandscapeEditDataInterface LandscapeEdit(this); TArray 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("LandscapeEditor"); TArray 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& 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(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(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(Hardness, 0.0f, 1.0f); } else if (PropertyName == NAME_PhysMaterial || PropertyName == NAME_MinimumCollisionRelevanceWeight) { for (TObjectIterator 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 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 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(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 CollisionComps; GetComponents(CollisionComps); for (ULandscapeHeightfieldCollisionComponent* Component : CollisionComps) { Component->DestroyComponent(); } TArray AttachedCollisionComponents = RootComponent->GetAttachChildren().FilterByPredicate( [](USceneComponent* Component) { return Cast(Component); }); // Destroy any attached but un-owned collision components for (USceneComponent* Component : AttachedCollisionComponents) { Component->DestroyComponent(); } // Recreate collision CollisionMipLevel = FMath::Clamp(CollisionMipLevel, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1); SimpleCollisionMipLevel = FMath::Clamp(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& 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(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& 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 ComponentsToMove(InComponents); ComponentsToMove.Sort(FCompareULandscapeComponentBySectionBase()); const int32 ComponentSizeVerts = Landscape->NumSubsections * (Landscape->SubsectionSizeQuads + 1); const int32 NeedHeightmapSize = 1 << FMath::CeilLogTwo(ComponentSizeVerts); TSet SelectProxies; TSet TargetSelectedComponents; TArray 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 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 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* 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 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 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 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 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 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 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()->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(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& 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(WorldPartition, [this, &UndeletedProxyCount](const FWorldPartitionActorDesc* ActorDesc) { FLandscapeActorDesc* LandscapeActorDesc = (FLandscapeActorDesc*)ActorDesc; if (LandscapeActorDesc->GridGuid == LandscapeGuid) { ALandscapeProxy* LandscapeProxy = Cast(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 SplineOwner : SplineActors) { // Only check for ALandscapeSplineActor type because ALandscapeProxy also implement the ILandscapeSplineInterface for non WP worlds if(ALandscapeSplineActor* SplineActor = Cast(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(WorldPartition, [this, &UndeletedSplineCount](const FWorldPartitionActorDesc* ActorDesc) { FLandscapeSplineActorDesc* LandscapeSplineActorDesc = (FLandscapeSplineActorDesc*)ActorDesc; if (LandscapeSplineActorDesc->LandscapeGuid == LandscapeGuid) { ALandscapeSplineActor* SplineActor = Cast(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(MaxLODLevel, -1, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1); bPropagateToProxies = true; } else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, ComponentScreenSizeToUseSubSections)) { ComponentScreenSizeToUseSubSections = FMath::Clamp(ComponentScreenSizeToUseSubSections, 0.01f, 1.0f); bPropagateToProxies = true; } else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LODDistributionSetting)) { LODDistributionSetting = FMath::Clamp(LODDistributionSetting, 1.0f, 10.0f); bPropagateToProxies = true; } else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LOD0DistributionSetting)) { LOD0DistributionSetting = FMath::Clamp(LOD0DistributionSetting, 1.0f, 10.0f); bPropagateToProxies = true; } else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LOD0ScreenSize)) { LOD0ScreenSize = FMath::Clamp(LOD0ScreenSize, 0.1f, 10.0f); bPropagateToProxies = true; } else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, CollisionMipLevel)) { CollisionMipLevel = FMath::Clamp(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(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(StaticLightingLOD, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1); bChangedLighting = true; } else if (GIsEditor && PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, ExportLOD)) { ExportLOD = FMath::Clamp(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 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(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(LightingLODBias, -1, MaxLOD); InvalidateLightingCache(); } else if (GIsEditor && (PropertyName == GET_MEMBER_NAME_CHECKED(ULandscapeComponent, CollisionMipLevel) || PropertyName == GET_MEMBER_NAME_CHECKED(ULandscapeComponent, SimpleCollisionMipLevel))) { CollisionMipLevel = FMath::Clamp(CollisionMipLevel, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1); SimpleCollisionMipLevel = FMath::Clamp(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 ULandscapeInfo::GetSelectedComponents() const { return SelectedComponents; } TSet ULandscapeInfo::GetSelectedRegionComponents() const { return SelectedRegionComponents; } void ULandscapeInfo::UpdateSelectedComponents(TSet& NewComponents, bool bIsComponentwise /*=true*/) { int32 InSelectType = bIsComponentwise ? FLandscapeEditToolRenderData::ST_COMPONENT : FLandscapeEditToolRenderData::ST_REGION; if (bIsComponentwise) { for (TSet::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 RemovedComponents = SelectedComponents.Difference(NewComponents); for (TSet::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::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::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 NewComponents; UpdateSelectedComponents(NewComponents, bIsComponentwise); if (!bIsComponentwise) { SelectedRegion.Empty(); } } void ULandscapeComponent::ReallocateWeightmaps(FLandscapeEditDataInterface* DataInterface, bool InCanUseEditingWeightmap, bool InSaveToTransactionBuffer, bool InForceReallocate, ALandscapeProxy* InTargetProxy, TArray* OutNewCreatedTextures) { int32 NeededNewChannels = 0; ALandscapeProxy* TargetProxy = InTargetProxy ? InTargetProxy : GetLandscapeProxy(); FGuid EditingLayerGUID = GetEditingLayerGUID(); check(!TargetProxy->HasLayersContent() || !InCanUseEditingWeightmap || EditingLayerGUID.IsValid()); FGuid TargetLayerGuid = InCanUseEditingWeightmap ? EditingLayerGUID : FGuid(); TArray& ComponentWeightmapLayerAllocations = GetWeightmapLayerAllocations(InCanUseEditingWeightmap); TArray& ComponentWeightmapTextures = GetWeightmapTextures(InCanUseEditingWeightmap); TArray& 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 NewWeightmapTextures; TArray 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 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& ComponentWeightmapLayerAllocations = GetWeightmapLayerAllocations(InEditLayerGuid); TArray& ComponentWeightmapTextures = GetWeightmapTextures(InEditLayerGuid); TArray& ComponentWeightmapTexturesUsage = GetWeightmapTexturesUsage(InEditLayerGuid); // Adjust WeightmapTextureIndex index for other layers TSet UnUsedTextureIndices; { TSet 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& 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 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& LayerInfos, TArray >& 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 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 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& OutLayerNames, UMaterial* InMaterial) { TArray LayerNames; TArray ES31Expressions; InMaterial->GetAllReferencedExpressions(ES31Expressions, nullptr, ERHIFeatureLevel::ES3_1); TArray MobileExpressions = MoveTemp(ES31Expressions); for (UMaterialExpression* Expression : MobileExpressions) { if (Expression) { Expression->GetLandscapeLayerNames(LayerNames); } } for (const FName& Name : LayerNames) { OutLayerNames.Add(Name); } } void ULandscapeComponent::GenerateMobileWeightmapLayerAllocations() { TSet 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(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 const& InVisibilityData, TArray& OutHoleBounds) { check(InVisibilityData.Num() == InSize * InSize); TArray HoleSegmentLabels; HoleSegmentLabels.AddZeroed(InSize*InSize); TArray> 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 const& InHoleBounds, TArray& 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& 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 const& InHoleBounds, TArray& OutHoleVertexLods) { // Generate hole bounds for each Lod level from Lod0 InHoleBounds TArray< TArray > 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 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 struct FLandscapeHoleRenderData { TArray HoleIndices; int32 MinIndex; int32 MaxIndex; }; // Serialize the hole render data. template void SerializeHoleRenderData(FMemoryArchive& Ar, FLandscapeHoleRenderData& 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 void BuildHoleRenderData(int32 InNumSubsections, int32 InSubsectionSizeVerts, TArray const& InVisibilityData, TArray& InVertexToIndexMap, FLandscapeHoleRenderData& OutHoleRenderData) { const int32 SizeVerts = InNumSubsections * InSubsectionSizeVerts; const int32 SubsectionSizeQuads = InSubsectionSizeVerts - 1; const uint8 VisThreshold = 170; INDEX_TYPE MaxIndex = 0; INDEX_TYPE MinIndex = TNumericLimits::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(MaxIndex, i00); MinIndex = FMath::Min(MinIndex, i00); MaxIndex = FMath::Max(MaxIndex, i10); MinIndex = FMath::Min(MinIndex, i10); MaxIndex = FMath::Max(MaxIndex, i11); MinIndex = FMath::Min(MinIndex, i11); MaxIndex = FMath::Max(MaxIndex, i01); MinIndex = FMath::Min(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 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> HeightmapMipRawData; TArray64 HeightmapMipData; for (int32 MipIdx = 0; MipIdx < NumMips; MipIdx++) { int32 MipSubsectionSizeVerts = (SubsectionSizeVerts) >> MipIdx; if (MipSubsectionSizeVerts > 1) { new(HeightmapMipRawData) TArray64(); 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& ComponentWeightmapLayerAllocations = GetWeightmapLayerAllocations(); for (int32 AllocIdx = 0; AllocIdx < ComponentWeightmapLayerAllocations.Num(); AllocIdx++) { const FWeightmapLayerAllocationInfo& AllocInfo = ComponentWeightmapLayerAllocations[AllocIdx]; if (AllocInfo.LayerInfo == ALandscapeProxy::VisibilityLayer) { NumHoleLods = FMath::Clamp(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 VertexToIndexMap; VertexToIndexMap.AddUninitialized(NumVertices); FMemory::Memset(VertexToIndexMap.GetData(), 0xFF, NumVertices * sizeof(uint32)); TArray 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 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 HoleVertexLods; PlatformAr << NumHoleLods; if (NumHoleLods > 0) { TArray HoleBounds; GetHoleBounds(SubsectionSizeVerts * NumSubsections, VisibilityData, HoleBounds); BuildHoleVertexLods(SubsectionSizeVerts * NumSubsections, NumHoleLods, HoleBounds, HoleVertexLods); if (NumVertices <= UINT16_MAX) { FLandscapeHoleRenderData HoleRenderData; BuildHoleRenderData(NumSubsections, SubsectionSizeVerts, VisibilityData, VertexToIndexMap, HoleRenderData); SerializeHoleRenderData(PlatformAr, HoleRenderData); } else { FLandscapeHoleRenderData 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 InlineMobileVertices; InlineMobileVertices.AddZeroed(NumInlineMobileVertices); FLandscapeMobileVertex* DstVert = InlineMobileVertices.GetData(); int32 StreamingLODIdx = NumStreamingLODs - 1; TArray> 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& 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 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(this); UTexture2D* NewTexture = NewObject(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(this); UTexture2D* NewTexture = NewObject(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(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 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 OutLDR; TArray OutHDR; TArray 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 HeightData; switch (InRenderTarget->RenderTargetFormat) { case RTF_RGBA16f: case RTF_RGBA32f: { TArray 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 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 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(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 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 TriangleList; }; TMap 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 RTData; RTData = SampleRTData(InRenderTarget, SampleRect); TArray 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 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