// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= LandscapeEdit.cpp: Landscape editing =============================================================================*/ #include "LandscapeEdit.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" #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/WorldPartitionActorDesc.h" #include "WorldPartition/Landscape/LandscapeActorDesc.h" #include "ActorPartition/ActorPartitionSubsystem.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.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); for (int32 LayerParameterIdx = 0; LayerParameterIdx < StaticParameters.TerrainLayerWeightParameters.Num(); ++LayerParameterIdx) { FStaticTerrainLayerWeightParameter& LayerParameter = StaticParameters.TerrainLayerWeightParameters[LayerParameterIdx]; if (LayerParameter.ParameterInfo.Name == LayerName) { LayerParameter.WeightmapIndex = 0; LayerParameter.bOverride = true; } else { LayerParameter.WeightmapIndex = INDEX_NONE; } } // 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; } /** * 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 { check(GIsEditor); 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; #if WITH_EDITOR 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)); } #endif 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, Log, 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; for (const FWeightmapLayerAllocationInfo& Allocation : Allocations) { if (Allocation.LayerInfo) { const FName LayerParameter = (Allocation.LayerInfo == ALandscapeProxy::VisibilityLayer) ? UMaterialExpressionLandscapeVisibilityMask::ParameterName : Allocation.LayerInfo->LayerName; StaticParameters.TerrainLayerWeightParameters.Add(FStaticTerrainLayerWeightParameter(LayerParameter, Allocation.WeightmapTextureIndex, true, FGuid(), !Allocation.LayerInfo->bNoWeightBlend)); } } CombinationMaterialInstance->UpdateStaticPermutation(StaticParameters, InMaterialUpdateContext); CombinationMaterialInstance->PostEditChange(); } return CombinationMaterialInstance; } return nullptr; } void ULandscapeComponent::UpdateMaterialInstances_Internal(FMaterialUpdateContext& Context) { check(GIsEditor); 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; TArray& WeightmapBaseLayerAllocation = GetWeightmapLayerAllocations(); 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++) { FWeightmapLayerAllocationInfo& Allocation = WeightmapBaseLayerAllocation[AllocIdx]; FName LayerName = Allocation.LayerInfo == ALandscapeProxy::VisibilityLayer ? UMaterialExpressionLandscapeVisibilityMask::ParameterName : Allocation.LayerInfo ? Allocation.LayerInfo->LayerName : NAME_None; MaterialInstance->SetVectorParameterValueEditorOnly(FName(*FString::Printf(TEXT("LayerMask_%s"), *LayerName.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) { for (ULandscapeComponent* Component : LandscapeComponents) { Component->UpdateMaterialInstances(InOutMaterialContext, InOutRecreateRenderStateContext); } } void ALandscapeProxy::UpdateAllComponentMaterialInstances() { // 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; } 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; } } bool ULandscapeComponent::ComponentIsTouchingSelectionBox(const FBox& InSelBBox, const FEngineShowFlags& ShowFlags, const bool bConsiderOnlyBSP, const bool bMustEncompassEntireComponent) const { if (ShowFlags.Landscape) { return Super::ComponentIsTouchingSelectionBox(InSelBBox, ShowFlags, bConsiderOnlyBSP, bMustEncompassEntireComponent); } return false; } bool ULandscapeComponent::ComponentIsTouchingSelectionFrustum(const FConvexVolume& InFrustum, const FEngineShowFlags& ShowFlags, const bool bConsiderOnlyBSP, const bool bMustEncompassEntireComponent) const { if (ShowFlags.Landscape) { return Super::ComponentIsTouchingSelectionFrustum(InFrustum, ShowFlags, bConsiderOnlyBSP, bMustEncompassEntireComponent); } return false; } void ULandscapeComponent::PreFeatureLevelChange(ERHIFeatureLevel::Type PendingFeatureLevel) { Super::PreFeatureLevelChange(PendingFeatureLevel); if (PendingFeatureLevel <= ERHIFeatureLevel::ES3_1) { // See if we need to cook platform data for mobile preview in editor CheckGenerateLandscapePlatformData(false, nullptr); } } void ULandscapeComponent::PostEditUndo() { if (!IsPendingKill()) { if (!GetLandscapeProxy()->HasLayersContent()) { UpdateMaterialInstances(); } } Super::PostEditUndo(); if (!IsPendingKill()) { 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()); } void ALandscapeProxy::FixupWeightmaps() { WeightmapUsageMap.Empty(); for (ULandscapeComponent* Component : LandscapeComponents) { Component->FixupWeightmaps(); } } void ULandscapeComponent::FixupWeightmaps() { if (GIsEditor && !HasAnyFlags(RF_ClassDefaultObject)) { ULandscapeInfo* Info = GetLandscapeInfo(); ALandscapeProxy* Proxy = GetLandscapeProxy(); if (Info) { WeightmapTexturesUsage.Empty(); WeightmapTexturesUsage.AddDefaulted(WeightmapTextures.Num()); TArray LayersToDelete; bool bFixedLayerDeletion = false; // make sure the weightmap textures are fully loaded or deleting layers from them will crash! :) for (UTexture* WeightmapTexture : WeightmapTextures) { WeightmapTexture->ConditionalPostLoad(); } // LayerInfo Validation check... for (const auto& Allocation : WeightmapLayerAllocations) { 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 < WeightmapLayerAllocations.Num();) { FWeightmapLayerAllocationInfo& Allocation = WeightmapLayerAllocations[LayerIdx]; if (!Allocation.IsAllocated()) { WeightmapLayerAllocations.RemoveAt(LayerIdx); continue; } // Fix up any problems caused by the layer deletion bug. if (Allocation.WeightmapTextureIndex >= WeightmapTextures.Num()) { Allocation.WeightmapTextureIndex = WeightmapTextures.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 = WeightmapTextures[Allocation.WeightmapTextureIndex]; UE_TRANSITIONAL_OBJECT_PTR(ULandscapeWeightmapUsage)* TempUsage = Proxy->WeightmapUsageMap.Find(WeightmapTexture); if (TempUsage == nullptr) { TempUsage = &Proxy->WeightmapUsageMap.Add(WeightmapTexture, GetLandscapeProxy()->CreateWeightmapUsage()); (*TempUsage)->LayerGuid.Invalidate(); } ULandscapeWeightmapUsage* Usage = *TempUsage; WeightmapTexturesUsage[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)); WeightmapLayerAllocations.RemoveAt(LayerIdx); continue; } else { Usage->ChannelUsage[Allocation.WeightmapTextureChannel] = this; } ++LayerIdx; } RemoveInvalidWeightmaps(); } } } void ULandscapeComponent::UpdateLayerWhitelistFromPaintedLayers() { TArray& ComponentWeightmapLayerAllocations = GetWeightmapLayerAllocations(); for (const auto& Allocation : ComponentWeightmapLayerAllocations) { LayerWhitelist.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->HeightData.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; TArray& ComponentWeightmapLayerAllocations = GetWeightmapLayerAllocations(false); TArray& ComponentWeightmapsTexture = GetWeightmapTextures(false); // Find the layers we're interested in for (int32 AllocIdx = 0; AllocIdx < ComponentWeightmapLayerAllocations.Num(); AllocIdx++) { 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() { 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::AreAllComponentsRegistered() const { const TArray& LandscapeProxies = ALandscapeProxy::GetLandscapeProxies(); for(ALandscapeProxy* LandscapeProxy : LandscapeProxies) { if (LandscapeProxy->IsPendingKill()) { continue; } if (LandscapeProxy->GetLandscapeGuid() == LandscapeGuid) { for (ULandscapeComponent* LandscapeComponent : LandscapeProxy->LandscapeComponents) { if (LandscapeComponent && !LandscapeComponent->IsRegistered()) { return false; } } } } for (TScriptInterface SplineOwner : SplineActors) { if (!SplineOwner.GetObject() || SplineOwner.GetObject()->IsPendingKill()) { 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 { if (!LandscapeActor) { return false; } UWorld* World = LandscapeActor->GetWorld(); int32 ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2; ALandscape::CalcComponentIndicesOverlap(X1, Y1, X2, Y2, ComponentSizeQuads, ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2); UActorPartitionSubsystem::FCellCoord MinCoord = UActorPartitionSubsystem::FCellCoord::GetCellCoord(FIntPoint(ComponentIndexX1 * ComponentSizeQuads, ComponentIndexY1 * ComponentSizeQuads), World->PersistentLevel, LandscapeActor->GridSize); UActorPartitionSubsystem::FCellCoord MaxCoord = UActorPartitionSubsystem::FCellCoord::GetCellCoord(FIntPoint(ComponentIndexX2 * ComponentSizeQuads, ComponentIndexY2 * ComponentSizeQuads), World->PersistentLevel, LandscapeActor->GridSize); for (const FWorldPartitionHandle& Handle : ProxyHandles) { if (FLandscapeActorDesc* LandscapeActorDesc = (FLandscapeActorDesc*)Handle.Get()) { check(LandscapeActorDesc->GridGuid == LandscapeGuid); 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 (!Handle.IsLoaded()) { return true; } } } } return false; } 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; }; TArray ALandscapeProxy::GetLayersFromMaterial(UMaterialInterface* MaterialInterface) { TArray Result; if (MaterialInterface) { TArray OutParameterInfo; TArray Guids; if (UMaterialInstance* Instance = Cast(MaterialInterface)) { Instance->GetAllParameterInfo(OutParameterInfo, Guids); Instance->GetAllParameterInfo(OutParameterInfo, Guids); Instance->GetAllParameterInfo(OutParameterInfo, Guids); Instance->GetAllParameterInfo(OutParameterInfo, Guids); } else if (UMaterial* Material = MaterialInterface->GetMaterial()) { Material->GetAllParameterInfo(OutParameterInfo, Guids); Material->GetAllParameterInfo(OutParameterInfo, Guids); Material->GetAllParameterInfo(OutParameterInfo, Guids); Material->GetAllParameterInfo(OutParameterInfo, Guids); } for (const FMaterialParameterInfo& ParameterInfo : OutParameterInfo) { Result.AddUnique(ParameterInfo.Name); } } return Result; } TArray ALandscapeProxy::GetLayersFromMaterial() const { return GetLayersFromMaterial(LandscapeMaterial); } ULandscapeLayerInfoObject* ALandscapeProxy::CreateLayerInfo(const TCHAR* LayerName, ULevel* Level) { FName LayerObjectName = FName(*FString::Printf(TEXT("LayerInfoObject_%s"), LayerName)); FString Path = Level->GetOutermost()->GetName() + TEXT("_sharedassets/"); if (Path.StartsWith("/Temp/")) { Path = FString("/Game/") + Path.RightChop(FString("/Temp/").Len()); } FString PackageName = Path + LayerObjectName.ToString(); FString PackageFilename; int32 Suffix = 1; while (FPackageName::DoesPackageExist(PackageName, nullptr, &PackageFilename)) { LayerObjectName = FName(*FString::Printf(TEXT("LayerInfoObject_%s_%d"), LayerName, Suffix)); PackageName = Path + LayerObjectName.ToString(); Suffix++; } UPackage* Package = CreatePackage( *PackageName); ULandscapeLayerInfoObject* LayerInfo = NewObject(Package, LayerObjectName, RF_Public | RF_Standalone | RF_Transactional); LayerInfo->LayerName = LayerName; return LayerInfo; } ULandscapeLayerInfoObject* ALandscapeProxy::CreateLayerInfo(const TCHAR* LayerName) { ULandscapeLayerInfoObject* LayerInfo = ALandscapeProxy::CreateLayerInfo(LayerName, GetLevel()); check(LayerInfo); ULandscapeInfo* LandscapeInfo = GetLandscapeInfo(); if (LandscapeInfo) { int32 Index = LandscapeInfo->GetLayerInfoIndex(LayerName, 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, Log, 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, Log, 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, Log, 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, Log, 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 FVector2D LandscapeUVScale = FVector2D(1.0f, 1.0f) / FVector2D(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 FVector2D ComponentUVOffsetLOD = FVector2D(ComponentOffsetQuads)*((float)ComponentSizeQuadsLOD / ComponentSizeQuads); const FVector2D 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; TArray& ComponentWeightmapLayerAllocations = Component->GetWeightmapLayerAllocations(); for (int32 AllocIdx = 0; AllocIdx < ComponentWeightmapLayerAllocations.Num(); AllocIdx++) { 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] = 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]] = LocalTangentX; VertexInstanceBinormalSigns[VertexInstanceIDs[i]] = GetBasisDeterminantSign(LocalTangentX, LocalTangentY, LocalTangentZ); VertexInstanceNormals[VertexInstanceIDs[i]] = LocalTangentZ; FVector2D UV = (ComponentUVOffsetLOD + FVector2D(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 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 FVector2D 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) { const TArray& AllocInfos = Component->GetWeightmapLayerAllocations(InLayerGuid); for (const FWeightmapLayerAllocationInfo& AllocInfo : AllocInfos) { if (AllocInfo.LayerInfo != nullptr) { OutUsedLayerInfos.AddUnique(AllocInfo.LayerInfo); } } } }); } 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 : FVector2D 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); FVector2D NewScale(FVector2D::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); } 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) { if (!bDuplicateForPIE) { // Need to generate new GUID when duplicating LandscapeGuid = FGuid::NewGuid(); // This makes sure at least we have a LandscapeInfo mapped for this GUID. CreateLandscapeInfo(); // 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::PostDuplicate(bDuplicateForPIE); } #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()) { uint8 Hash[20]; FString PathNameString = GetPathName(); FSHA1::HashBuffer(*PathNameString, PathNameString.Len() * sizeof(PathNameString[0]), Hash); LayerUsageDebugColor = 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() { ForAllLandscapeProxies([](ALandscapeProxy* Proxy) { Proxy->UpdateAllComponentMaterialInstances(); }); } uint32 ULandscapeInfo::GetGridSize(uint32 InGridSizeInComponents) const { return InGridSizeInComponents * ComponentSizeQuads; } 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 ones are need for height map change TSet OldHeightmapTextures; for (ULandscapeComponent* Component : TargetSelectedComponents) { Component->Modify(); OldHeightmapTextures.Add(Component->GetHeightmap()); } // 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 it is no referenced textures... for (UTexture2D* Texture : OldHeightmapTextures) { 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 Repacking all the Weight map (to make it packed well...) 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; }); ULandscapeInfo* Info = GetLandscapeInfo(); bool bRemovedAnyLayers = false; for (ULandscapeComponent* Component : LandscapeComponents) { TArray& ComponentWeightmapLayerAllocations = Component->GetWeightmapLayerAllocations(false); int32 NumNullLayers = Algo::CountIf(ComponentWeightmapLayerAllocations, [](const FWeightmapLayerAllocationInfo& Allocation) { return Allocation.LayerInfo == nullptr; }); if (NumNullLayers > 0) { FLandscapeEditDataInterface LandscapeEdit(Info); for (int32 i = 0; i < NumNullLayers; ++i) { // DeleteLayer doesn't expect duplicates, so we need to call it once for each null Component->DeleteLayer(nullptr, LandscapeEdit); } bRemovedAnyLayers = true; } } if (bRemovedAnyLayers) { ALandscape* LandscapeActor = GetLandscapeActor(); if(LandscapeActor != nullptr && LandscapeActor->HasLayersContent()) { LandscapeActor->RequestLayersContentUpdate(ELandscapeLayerUpdateMode::Update_All); } else { ALandscapeProxy::InvalidateGeneratedComponentData(LandscapeComponents); } } // 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("LandscapeMaterialsOverride"))) { bool RecreateMaterialInstances = true; if (PropertyName == FName(TEXT("LandscapeMaterialsOverride")) && 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 && World->FeatureLevel <= ERHIFeatureLevel::ES3_1) { 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()) { const TSet& SplineHandles = LandscapeInfo->GetSplineHandles(); if (SplineHandles.Num()) { FVector ActorLocation = GetActorLocation(); FBox Bounds(ActorLocation, ActorLocation + (GridSize * LandscapeInfo->DrawScale)); // Get a reference to Spline Actors that have intersecting bounds with us for (const FWorldPartitionHandle& SplineHandle : LandscapeInfo->GetSplineHandles()) { if (SplineHandle.IsValid() && Bounds.IntersectXY(SplineHandle->GetBounds())) { ActorDescReferences.Add(SplineHandle); } } } } } } AActor* ALandscapeStreamingProxy::GetSceneOutlinerParent() const { if (ULandscapeInfo* LandscapeInfo = GetLandscapeInfo()) { return LandscapeInfo->LandscapeActor.Get(); } return Super::GetSceneOutlinerParent(); } bool ALandscapeStreamingProxy::CanDeleteSelectedActor(FText& OutReason) const { return true; } 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(!RegisteredProxy->IsPendingKill()); UndeletedProxyCount++; } // Then check for Unloaded Proxies for (const FWorldPartitionHandle& Handle : ProxyHandles) { ALandscapeProxy* LandscapeProxy = Cast(Handle->GetActor()); if (LandscapeProxy == LandscapeActor) { continue; } // 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) == !LandscapeProxy->IsPendingKill()); } } // 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(!SplineActor->IsPendingKill()); UndeletedSplineCount++; } } // Then check for Unloaded Splines for (const FWorldPartitionHandle& Handle : SplineHandles) { ALandscapeSplineActor* SplineActor = Cast(Handle->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) == !SplineActor->IsPendingKill()); } } 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; PreEditLandscapeMaterialsOverride = LandscapeMaterialsOverride; 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, LandscapeMaterialsOverride)) && PropertyChangedEvent.ChangeType != EPropertyChangeType::ArrayAdd) { bool HasMaterialChanged = false; if (PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive) { if (PreEditLandscapeMaterial != LandscapeMaterial || PreEditLandscapeHoleMaterial != LandscapeHoleMaterial || PreEditLandscapeMaterialsOverride.Num() != LandscapeMaterialsOverride.Num() || bIsPerformingInteractiveActionOnLandscapeMaterialOverride) { HasMaterialChanged = true; } if (!HasMaterialChanged) { for (int32 i = 0; i < LandscapeMaterialsOverride.Num(); ++i) { const FLandscapeProxyMaterialOverride& NewMaterialOverride = LandscapeMaterialsOverride[i]; const FLandscapeProxyMaterialOverride& PreEditMaterialOverride = PreEditLandscapeMaterialsOverride[i]; if (!(PreEditMaterialOverride == NewMaterialOverride)) { HasMaterialChanged = true; break; } } } bIsPerformingInteractiveActionOnLandscapeMaterialOverride = false; } else { // We are probably using a slider or something similar in LandscapeMaterialsOverride bIsPerformingInteractiveActionOnLandscapeMaterialOverride = MemberPropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LandscapeMaterialsOverride); } 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 == FName(TEXT("MaxLODLevel"))) { MaxLODLevel = FMath::Clamp(MaxLODLevel, -1, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1); bPropagateToProxies = true; } else if (PropertyName == FName(TEXT("ComponentScreenSizeToUseSubSections"))) { ComponentScreenSizeToUseSubSections = FMath::Clamp(ComponentScreenSizeToUseSubSections, 0.01f, 1.0f); bPropagateToProxies = true; } else if (PropertyName == FName(TEXT("LODDistributionSetting"))) { LODDistributionSetting = FMath::Clamp(LODDistributionSetting, 1.0f, 10.0f); bPropagateToProxies = true; } else if (PropertyName == FName(TEXT("LOD0DistributionSetting"))) { LOD0DistributionSetting = FMath::Clamp(LOD0DistributionSetting, 1.0f, 10.0f); bPropagateToProxies = true; } else if (PropertyName == FName(TEXT("LOD0ScreenSize"))) { LOD0ScreenSize = FMath::Clamp(LOD0ScreenSize, 0.1f, 10.0f); bPropagateToProxies = true; } else if (PropertyName == FName(TEXT("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 (GIsEditor && PropertyName == FName(TEXT("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 && World->FeatureLevel <= ERHIFeatureLevel::ES3_1) { 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; PreEditLandscapeMaterialsOverride.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::SetLOD(bool bForcedLODChanged, int32 InLODValue) { if (bForcedLODChanged) { ForcedLOD = InLODValue; if (ForcedLOD >= 0) { ForcedLOD = FMath::Clamp(ForcedLOD, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1); } else { ForcedLOD = -1; } } else { int32 MaxLOD = FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1; LODBias = FMath::Clamp(InLODValue, -MaxLOD, MaxLOD); } InvalidateLightingCache(); MarkRenderStateDirty(); // Update neighbor components ULandscapeInfo* Info = GetLandscapeInfo(); if (Info) { FIntPoint ComponentBase = GetSectionBase() / ComponentSizeQuads; FIntPoint LandscapeKey[8] = { ComponentBase + FIntPoint(-1, -1), ComponentBase + FIntPoint(+0, -1), ComponentBase + FIntPoint(+1, -1), ComponentBase + FIntPoint(-1, +0), ComponentBase + FIntPoint(+1, +0), ComponentBase + FIntPoint(-1, +1), ComponentBase + FIntPoint(+0, +1), ComponentBase + FIntPoint(+1, +1) }; for (int32 Idx = 0; Idx < 8; ++Idx) { ULandscapeComponent* Comp = Info->XYtoComponentMap.FindRef(LandscapeKey[Idx]); if (Comp) { Comp->Modify(); Comp->InvalidateLightingCache(); Comp->MarkRenderStateDirty(); } } } } 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("OverrideMaterials")) || MemberPropertyName == FName(TEXT("MaterialPerLOD_Key"))) { bool RecreateMaterialInstances = true; if (PropertyName == FName(TEXT("OverrideMaterials")) && PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayAdd) { RecreateMaterialInstances = false; } if (RecreateMaterialInstances) { UpdateMaterialInstances(); UWorld* World = GetWorld(); if (World != nullptr && World->FeatureLevel <= ERHIFeatureLevel::ES3_1) { 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; if (InSaveToTransactionBuffer) { Usage->Modify(bMarkPackageDirty); } 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()); if (InSaveToTransactionBuffer) { CurrentWeightmapUsage->Modify(bMarkPackageDirty); } 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]; if (InSaveToTransactionBuffer) { OldWeightmapUsage->Modify(bMarkPackageDirty); } OldWeightmapUsage->ChannelUsage[AllocInfo.WeightmapTextureChannel] = nullptr; } // Assign the new allocation if (InSaveToTransactionBuffer) { CurrentWeightmapUsage->Modify(bMarkPackageDirty); } 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); } 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() { TArray& ComponentWeightmapLayerAllocations = GetWeightmapLayerAllocations(); TArray& ComponentWeightmapTextures = GetWeightmapTextures(); TArray& ComponentWeightmapTexturesUsage = GetWeightmapTexturesUsage(); // 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) { 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; } void ULandscapeComponent::ExportCustomProperties(FOutputDevice& Out, uint32 Indent) { if (HasAnyFlags(RF_ClassDefaultObject)) { return; } // Height map int32 NumVertices = FMath::Square(NumSubsections*(SubsectionSizeQuads + 1)); FLandscapeComponentDataInterface DataInterface(this); TArray Heightmap; DataInterface.GetHeightmapTextureData(Heightmap); check(Heightmap.Num() == NumVertices); Out.Logf(TEXT("%sCustomProperties LandscapeHeightData "), FCString::Spc(Indent)); for (int32 i = 0; i < NumVertices; i++) { Out.Logf(TEXT("%x "), Heightmap[i].DWColor()); } TArray Weightmap; // Weight map Out.Logf(TEXT("LayerNum=%d "), WeightmapLayerAllocations.Num()); for (int32 i = 0; i < WeightmapLayerAllocations.Num(); i++) { if (DataInterface.GetWeightmapTextureData(WeightmapLayerAllocations[i].LayerInfo, Weightmap)) { Out.Logf(TEXT("LayerInfo=%s "), *WeightmapLayerAllocations[i].LayerInfo->GetPathName()); for (int32 VertexIndex = 0; VertexIndex < NumVertices; VertexIndex++) { Out.Logf(TEXT("%x "), Weightmap[VertexIndex]); } } } Out.Logf(TEXT("\r\n")); } void ULandscapeComponent::ImportCustomProperties(const TCHAR* SourceText, FFeedbackContext* Warn) { if (FParse::Command(&SourceText, TEXT("LandscapeHeightData"))) { int32 NumVertices = FMath::Square(NumSubsections*(SubsectionSizeQuads + 1)); TArray Heights; Heights.Empty(NumVertices); Heights.AddZeroed(NumVertices); FParse::Next(&SourceText); int32 i = 0; TCHAR* StopStr; while (FChar::IsHexDigit(*SourceText)) { if (i < NumVertices) { Heights[i++].DWColor() = FCString::Strtoi(SourceText, &StopStr, 16); while (FChar::IsHexDigit(*SourceText)) { SourceText++; } } FParse::Next(&SourceText); } if (i != NumVertices) { Warn->Log(*NSLOCTEXT("Core", "SyntaxError", "Syntax Error").ToString()); } int32 ComponentSizeVerts = NumSubsections * (SubsectionSizeQuads + 1); InitHeightmapData(Heights, false); // Weight maps int32 LayerNum = 0; if (FParse::Value(SourceText, TEXT("LayerNum="), LayerNum)) { while (*SourceText && (!FChar::IsWhitespace(*SourceText))) { ++SourceText; } FParse::Next(&SourceText); } if (LayerNum <= 0) { return; } // Init memory TArray LayerInfos; LayerInfos.Empty(LayerNum); TArray> WeightmapData; for (int32 LayerIndex = 0; LayerIndex < LayerNum; ++LayerIndex) { TArray Weights; Weights.Empty(NumVertices); Weights.AddUninitialized(NumVertices); WeightmapData.Add(Weights); } int32 LayerIdx = 0; FString LayerInfoPath; while (*SourceText) { if (FParse::Value(SourceText, TEXT("LayerInfo="), LayerInfoPath)) { LayerInfos.Add(LoadObject(nullptr, *LayerInfoPath)); while (*SourceText && (!FChar::IsWhitespace(*SourceText))) { ++SourceText; } FParse::Next(&SourceText); check(*SourceText); i = 0; while (FChar::IsHexDigit(*SourceText)) { if (i < NumVertices) { (WeightmapData[LayerIdx])[i++] = (uint8)FCString::Strtoi(SourceText, &StopStr, 16); while (FChar::IsHexDigit(*SourceText)) { SourceText++; } } FParse::Next(&SourceText); } if (i != NumVertices) { Warn->Log(*NSLOCTEXT("Core", "SyntaxError", "Syntax Error").ToString()); } LayerIdx++; } else { break; } } InitWeightmapData(LayerInfos, WeightmapData); } } 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 ParameterInfos; TArray ParameterIds; TArray ES31Expressions; InMaterial->GetAllReferencedExpressions(ES31Expressions, nullptr, ERHIFeatureLevel::ES3_1); TArray MobileExpressions = MoveTemp(ES31Expressions); for (UMaterialExpression* Expression : MobileExpressions) { UMaterialExpressionLandscapeLayerWeight* LayerWeightExpression = Cast(Expression); UMaterialExpressionLandscapeLayerSwitch* LayerSwitchExpression = Cast(Expression); UMaterialExpressionLandscapeLayerSample* LayerSampleExpression = Cast(Expression); UMaterialExpressionLandscapeLayerBlend* LayerBlendExpression = Cast(Expression); UMaterialExpressionLandscapeVisibilityMask* VisibilityMaskExpression = Cast(Expression); FMaterialParameterInfo BaseParameterInfo; BaseParameterInfo.Association = EMaterialParameterAssociation::GlobalParameter; BaseParameterInfo.Index = INDEX_NONE; if(LayerWeightExpression != nullptr) { LayerWeightExpression->GetAllParameterInfo(ParameterInfos, ParameterIds, BaseParameterInfo); } if (LayerSwitchExpression != nullptr) { LayerSwitchExpression->GetAllParameterInfo(ParameterInfos, ParameterIds, BaseParameterInfo); } if (LayerSampleExpression != nullptr) { LayerSampleExpression->GetAllParameterInfo(ParameterInfos, ParameterIds, BaseParameterInfo); } if (LayerBlendExpression != nullptr) { LayerBlendExpression->GetAllParameterInfo(ParameterInfos, ParameterIds, BaseParameterInfo); } if (VisibilityMaskExpression != nullptr) { VisibilityMaskExpression->GetAllParameterInfo(ParameterInfos, ParameterIds, BaseParameterInfo); } } for (FMaterialParameterInfo& Info : ParameterInfos) { OutLayerNames.Add(Info.Name); } } void ULandscapeComponent::GenerateMobileWeightmapLayerAllocations() { TSet LayerNames; GetAllMobileRelevantLayerNames(LayerNames, GetLandscapeMaterial()->GetMaterial()); MobileWeightmapLayerAllocations = WeightmapLayerAllocations.FilterByPredicate([&](const FWeightmapLayerAllocationInfo& Allocation) -> bool { return Allocation.LayerInfo && LayerNames.Contains(Allocation.LayerInfo == ALandscapeProxy::VisibilityLayer ? UMaterialExpressionLandscapeVisibilityMask::ParameterName : 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() { 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) { FName LayerName = Allocation.LayerInfo == ALandscapeProxy::VisibilityLayer ? UMaterialExpressionLandscapeVisibilityMask::ParameterName : Allocation.LayerInfo->LayerName; NewMobileMaterialInstance->SetVectorParameterValue(FName(*FString::Printf(TEXT("LayerMask_%s"), *LayerName.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); UMaterialInstanceConstant* NewMobileMaterialInstance = NewObject(this); NewMobileMaterialInstance->SetParentEditorOnly(MobileCombinationMaterialInstances[MaterialIndex]); // Set the layer mask for (const auto& Allocation : MobileWeightmapLayerAllocations) { if (Allocation.LayerInfo) { FName LayerName = Allocation.LayerInfo == ALandscapeProxy::VisibilityLayer ? UMaterialExpressionLandscapeVisibilityMask::ParameterName : Allocation.LayerInfo->LayerName; NewMobileMaterialInstance->SetVectorParameterValueEditorOnly(FName(*FString::Printf(TEXT("LayerMask_%s"), *LayerName.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) { TArray& ComponentWeightmapLayerAllocations = GetWeightmapLayerAllocations(); for (int32 AllocIdx = 0; AllocIdx < ComponentWeightmapLayerAllocations.Num(); AllocIdx++) { 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); const int32 NumStreamingLODs = bStreamLandscapeMeshLODs ? FMath::Min(MaxLOD, MaxLODClamp) : 0; 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); } 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); 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); 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() { return NewObject(this, ULandscapeWeightmapUsage::StaticClass(), NAME_None, RF_Transactional); } 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, 0, 0, 0, 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) { // Copy (resolve) the rendered image from the frame buffer to its render target texture RHICmdList.CopyToResolveTarget( RenderTargetResource->GetRenderTargetTexture(), // Source texture RenderTargetResource->TextureRHI, // Dest texture FResolveParams()); // Resolve parameters }); 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, Log, 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