You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#rb Francis.Hurteau #preflight 609e5182ef86d30001ad0a18 #rnx [CL 16328103 by danny couture in ue5-main branch]
7632 lines
279 KiB
C++
7632 lines
279 KiB
C++
// 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<ULandscapeMaterialInstanceConstant>(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<FWeightmapLayerAllocationInfo>& Allocations, UMaterialInterface* LandscapeMaterial, bool bMobile /*= false*/)
|
|
{
|
|
if (!LandscapeMaterial)
|
|
{
|
|
return FString();
|
|
}
|
|
|
|
FString Result = LandscapeMaterial->GetPathName();
|
|
|
|
// Generate a string to describe each allocation
|
|
TArray<FString> LayerStrings;
|
|
for (int32 LayerIdx = 0; LayerIdx < Allocations.Num(); LayerIdx++)
|
|
{
|
|
const bool bNoWeightBlend = Allocations[LayerIdx].LayerInfo && Allocations[LayerIdx].LayerInfo->bNoWeightBlend;
|
|
LayerStrings.Add(FString::Printf(TEXT("_%s_%s%d"), *Allocations[LayerIdx].GetLayerName().ToString(), bNoWeightBlend ? TEXT("n") : TEXT("w"), Allocations[LayerIdx].WeightmapTextureIndex));
|
|
}
|
|
// Sort them alphabetically so we can share across components even if the order is different
|
|
LayerStrings.Sort(TGreater<FString>());
|
|
|
|
for (int32 LayerIdx = 0; LayerIdx < LayerStrings.Num(); LayerIdx++)
|
|
{
|
|
Result += LayerStrings[LayerIdx];
|
|
}
|
|
|
|
if (bMobile)
|
|
{
|
|
Result += TEXT("M");
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
UMaterialInstanceConstant* ULandscapeComponent::GetCombinationMaterial(FMaterialUpdateContext* InMaterialUpdateContext, const TArray<FWeightmapLayerAllocationInfo>& Allocations, int8 InLODIndex, bool bMobile /*= false*/) const
|
|
{
|
|
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<SNotificationItem> ExistingNotification;
|
|
if (!ExistingNotification.IsValid())
|
|
{
|
|
// let the user know why they are not seeing holes
|
|
FNotificationInfo Info(LOCTEXT("AssignLandscapeMaterial", "You must assign a regular, non-engine material to your landscape in order to see holes created with the visibility tool."));
|
|
Info.ExpireDuration = 5.0f;
|
|
Info.bUseSuccessFailIcons = true;
|
|
ExistingNotification = TWeakPtr<SNotificationItem>(FSlateNotificationManager::Get().AddNotification(Info));
|
|
}
|
|
#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<ULandscapeMaterialInstanceConstant>(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<FWeightmapLayerAllocationInfo>& WeightmapBaseLayerAllocation = GetWeightmapLayerAllocations();
|
|
TArray<UTexture2D*>& WeightmapBaseTexture = GetWeightmapTextures();
|
|
UTexture2D* BaseHeightmap = GetHeightmap();
|
|
|
|
for (auto It = MaterialPerLOD.CreateConstIterator(); It; ++It)
|
|
{
|
|
const int8 MaterialLOD = It.Value();
|
|
|
|
// Find or set a matching MIC in the Landscape's map.
|
|
UMaterialInstanceConstant* CombinationMaterialInstance = GetCombinationMaterial(&Context, WeightmapBaseLayerAllocation, MaterialLOD, false);
|
|
|
|
if (CombinationMaterialInstance != nullptr)
|
|
{
|
|
// Create the instance for this component, that will use the layer combination instance.
|
|
UMaterialInstanceConstant* MaterialInstance = NewObject<ULandscapeMaterialInstanceConstant>(GetOuter());
|
|
MaterialInstances[MaterialIndex] = MaterialInstance;
|
|
|
|
// Material Instances don't support Undo/Redo (the shader map goes out of sync and crashes happen)
|
|
// so we call UpdateMaterialInstances() from ULandscapeComponent::PostEditUndo instead
|
|
//MaterialInstance->SetFlags(RF_Transactional);
|
|
//MaterialInstance->Modify();
|
|
|
|
MaterialInstance->SetParentEditorOnly(CombinationMaterialInstance);
|
|
MaterialInstance->ClearParameterValuesEditorOnly();
|
|
Context.AddMaterialInstance(MaterialInstance); // must be done after SetParent
|
|
|
|
FLinearColor Masks[4] = { FLinearColor(1.0f, 0.0f, 0.0f, 0.0f), FLinearColor(0.0f, 1.0f, 0.0f, 0.0f), FLinearColor(0.0f, 0.0f, 1.0f, 0.0f), FLinearColor(0.0f, 0.0f, 0.0f, 1.0f) };
|
|
|
|
// Set the layer mask
|
|
for (int32 AllocIdx = 0; AllocIdx < WeightmapBaseLayerAllocation.Num(); AllocIdx++)
|
|
{
|
|
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<FComponentRecreateRenderStateContext> RecreateRenderStateContext;
|
|
RecreateRenderStateContext.Emplace(this);
|
|
TOptional<FMaterialUpdateContext> MaterialUpdateContext;
|
|
MaterialUpdateContext.Emplace(FMaterialUpdateContext::EOptions::Default & ~FMaterialUpdateContext::EOptions::RecreateRenderStates);
|
|
|
|
UpdateMaterialInstances_Internal(MaterialUpdateContext.GetValue());
|
|
|
|
// End material update
|
|
MaterialUpdateContext.Reset();
|
|
|
|
// Recreate the render state for this component, needed to update the static drawlist which has cached the MaterialRenderProxies
|
|
// Must be after the FMaterialUpdateContext is destroyed
|
|
RecreateRenderStateContext.Reset();
|
|
}
|
|
|
|
void ULandscapeComponent::UpdateMaterialInstances(FMaterialUpdateContext& InOutMaterialContext, TArray<FComponentRecreateRenderStateContext>& InOutRecreateRenderStateContext)
|
|
{
|
|
InOutRecreateRenderStateContext.Add(this);
|
|
UpdateMaterialInstances_Internal(InOutMaterialContext);
|
|
}
|
|
|
|
void ALandscapeProxy::UpdateAllComponentMaterialInstances(FMaterialUpdateContext& InOutMaterialContext, TArray<FComponentRecreateRenderStateContext>& InOutRecreateRenderStateContext)
|
|
{
|
|
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<FComponentRecreateRenderStateContext> RecreateRenderStateContexts;
|
|
RecreateRenderStateContexts.Reserve(LandscapeComponents.Num());
|
|
|
|
for (ULandscapeComponent* Component : LandscapeComponents)
|
|
{
|
|
RecreateRenderStateContexts.Emplace(Component);
|
|
}
|
|
TOptional<FMaterialUpdateContext> MaterialUpdateContext;
|
|
MaterialUpdateContext.Emplace(FMaterialUpdateContext::EOptions::Default & ~FMaterialUpdateContext::EOptions::RecreateRenderStates);
|
|
|
|
for (ULandscapeComponent* Component : LandscapeComponents)
|
|
{
|
|
Component->UpdateMaterialInstances_Internal(MaterialUpdateContext.GetValue());
|
|
}
|
|
|
|
// End material update
|
|
MaterialUpdateContext.Reset();
|
|
|
|
// Recreate the render state for our components, needed to update the static drawlist which has cached the MaterialRenderProxies
|
|
// Must be after the FMaterialUpdateContext is destroyed
|
|
RecreateRenderStateContexts.Empty();
|
|
}
|
|
|
|
int32 ULandscapeComponent::GetNumMaterials() const
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
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<ULandscapeComponent*> Components;
|
|
Components.Add(this);
|
|
GetLandscapeProxy()->FlushGrassComponents(&Components);
|
|
}
|
|
}
|
|
|
|
TUniquePtr<FWorldPartitionActorDesc> ALandscapeProxy::CreateClassActorDesc() const
|
|
{
|
|
return TUniquePtr<FWorldPartitionActorDesc>(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<ULandscapeLayerInfoObject*> 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<FWeightmapLayerAllocationInfo>& ComponentWeightmapLayerAllocations = GetWeightmapLayerAllocations();
|
|
|
|
for (const auto& Allocation : ComponentWeightmapLayerAllocations)
|
|
{
|
|
LayerWhitelist.AddUnique(Allocation.LayerInfo);
|
|
}
|
|
}
|
|
|
|
//
|
|
// LandscapeComponentAlphaInfo
|
|
//
|
|
struct FLandscapeComponentAlphaInfo
|
|
{
|
|
int32 LayerIndex;
|
|
TArray<uint8> AlphaValues;
|
|
|
|
// tor
|
|
FLandscapeComponentAlphaInfo(ULandscapeComponent* InOwner, int32 InLayerIndex)
|
|
: LayerIndex(InLayerIndex)
|
|
{
|
|
AlphaValues.Empty(FMath::Square(InOwner->ComponentSizeQuads + 1));
|
|
AlphaValues.AddZeroed(FMath::Square(InOwner->ComponentSizeQuads + 1));
|
|
}
|
|
|
|
bool IsLayerAllZero() const
|
|
{
|
|
for (int32 Index = 0; Index < AlphaValues.Num(); Index++)
|
|
{
|
|
if (AlphaValues[Index] != 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
struct FCollisionSize
|
|
{
|
|
public:
|
|
static FCollisionSize CreateSimple(bool bUseSimpleCollision, int32 InNumSubSections, int32 InSubsectionSizeQuads, int32 InMipLevel)
|
|
{
|
|
return bUseSimpleCollision ? Create(InNumSubSections, InSubsectionSizeQuads, InMipLevel) : FCollisionSize();
|
|
}
|
|
|
|
static FCollisionSize Create(int32 InNumSubSections, int32 InSubsectionSizeQuads, int32 InMipLevel)
|
|
{
|
|
return FCollisionSize(InNumSubSections, InSubsectionSizeQuads, InMipLevel);
|
|
}
|
|
|
|
FCollisionSize(FCollisionSize&& Other) = default;
|
|
FCollisionSize& operator=(FCollisionSize&& Other) = default;
|
|
private:
|
|
FCollisionSize(int32 InNumSubsections, int32 InSubsectionSizeQuads, int32 InMipLevel)
|
|
{
|
|
SubsectionSizeVerts = ((InSubsectionSizeQuads + 1) >> InMipLevel);
|
|
SubsectionSizeQuads = SubsectionSizeVerts - 1;
|
|
SizeVerts = InNumSubsections * SubsectionSizeQuads + 1;
|
|
SizeVertsSquare = FMath::Square(SizeVerts);
|
|
}
|
|
|
|
FCollisionSize()
|
|
{
|
|
}
|
|
|
|
public:
|
|
int32 SubsectionSizeVerts = 0;
|
|
int32 SubsectionSizeQuads = 0;
|
|
int32 SizeVerts = 0;
|
|
int32 SizeVertsSquare = 0;
|
|
};
|
|
|
|
void ULandscapeComponent::UpdateDirtyCollisionHeightData(FIntRect Region)
|
|
{
|
|
// Take first value as is
|
|
if (LayerDirtyCollisionHeightData.IsEmpty())
|
|
{
|
|
LayerDirtyCollisionHeightData = Region;
|
|
}
|
|
else
|
|
{
|
|
// Merge min/max region
|
|
LayerDirtyCollisionHeightData.Include(Region.Min);
|
|
LayerDirtyCollisionHeightData.Include(Region.Max);
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::ClearDirtyCollisionHeightData()
|
|
{
|
|
LayerDirtyCollisionHeightData = FIntRect();
|
|
}
|
|
|
|
void ULandscapeComponent::UpdateCollisionHeightData(const FColor* const HeightmapTextureMipData, const FColor* const SimpleCollisionHeightmapTextureData, int32 ComponentX1/*=0*/, int32 ComponentY1/*=0*/, int32 ComponentX2/*=MAX_int32*/, int32 ComponentY2/*=MAX_int32*/, bool bUpdateBounds/*=false*/, const FColor* XYOffsetTextureMipData/*=nullptr*/, bool bInUpdateHeightfieldRegion/*=true*/)
|
|
{
|
|
ULandscapeInfo* Info = GetLandscapeInfo();
|
|
ALandscapeProxy* Proxy = GetLandscapeProxy();
|
|
FIntPoint ComponentKey = GetSectionBase() / ComponentSizeQuads;
|
|
ULandscapeHeightfieldCollisionComponent* CollisionComp = CollisionComponent.Get();
|
|
ULandscapeMeshCollisionComponent* MeshCollisionComponent = Cast<ULandscapeMeshCollisionComponent>(CollisionComp);
|
|
ULandscapeHeightfieldCollisionComponent* OldCollisionComponent = CollisionComp;
|
|
|
|
// Simple collision is not currently supported with mesh collision components
|
|
const bool bUsingSimpleCollision = (SimpleCollisionMipLevel > CollisionMipLevel && SimpleCollisionHeightmapTextureData && !XYOffsetmapTexture);
|
|
|
|
FCollisionSize CollisionSize = FCollisionSize::Create(NumSubsections, SubsectionSizeQuads, CollisionMipLevel);
|
|
FCollisionSize SimpleCollisionSize = FCollisionSize::CreateSimple(bUsingSimpleCollision, NumSubsections, SubsectionSizeQuads, SimpleCollisionMipLevel);
|
|
|
|
const int32 TotalCollisionSize = CollisionSize.SizeVertsSquare + SimpleCollisionSize.SizeVertsSquare;
|
|
|
|
uint16* CollisionHeightData = nullptr;
|
|
uint16* CollisionXYOffsetData = nullptr;
|
|
bool CreatedNew = false;
|
|
bool ChangeType = false;
|
|
|
|
// In Landscape Layers the Collision Component is slave and doesn't need to be transacted
|
|
if (!Proxy->HasLayersContent())
|
|
{
|
|
if (CollisionComp)
|
|
{
|
|
CollisionComp->Modify();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// In Landscape Layers, only update dirtied collision height data
|
|
if (bInUpdateHeightfieldRegion && ComponentX1 == 0 && ComponentY1 == 0 && ComponentX2 == MAX_int32 && ComponentY2 == MAX_int32 && !LayerDirtyCollisionHeightData.IsEmpty())
|
|
{
|
|
ComponentX1 = LayerDirtyCollisionHeightData.Min.X;
|
|
ComponentY1 = LayerDirtyCollisionHeightData.Min.Y;
|
|
ComponentX2 = LayerDirtyCollisionHeightData.Max.X;
|
|
ComponentY2 = LayerDirtyCollisionHeightData.Max.Y;
|
|
}
|
|
ClearDirtyCollisionHeightData();
|
|
}
|
|
|
|
// Existing collision component is same type with collision
|
|
if (CollisionComp && ((XYOffsetmapTexture == nullptr) == (MeshCollisionComponent == nullptr)))
|
|
{
|
|
ComponentX1 = FMath::Clamp(ComponentX1, 0, ComponentSizeQuads);
|
|
ComponentY1 = FMath::Clamp(ComponentY1, 0, ComponentSizeQuads);
|
|
ComponentX2 = FMath::Clamp(ComponentX2, 0, ComponentSizeQuads);
|
|
ComponentY2 = FMath::Clamp(ComponentY2, 0, ComponentSizeQuads);
|
|
|
|
if (ComponentX2 < ComponentX1 || ComponentY2 < ComponentY1)
|
|
{
|
|
// nothing to do
|
|
return;
|
|
}
|
|
|
|
if (bUpdateBounds)
|
|
{
|
|
CollisionComp->CachedLocalBox = CachedLocalBox;
|
|
CollisionComp->UpdateComponentToWorld();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CreatedNew = true;
|
|
ChangeType = CollisionComp != nullptr;
|
|
ComponentX1 = 0;
|
|
ComponentY1 = 0;
|
|
ComponentX2 = ComponentSizeQuads;
|
|
ComponentY2 = ComponentSizeQuads;
|
|
|
|
RecreateCollisionComponent(bUsingSimpleCollision);
|
|
CollisionComp = CollisionComponent.Get();
|
|
MeshCollisionComponent = Cast<ULandscapeMeshCollisionComponent>(CollisionComp);
|
|
}
|
|
|
|
CollisionHeightData = (uint16*)CollisionComp->CollisionHeightData.Lock(LOCK_READ_WRITE);
|
|
|
|
if (XYOffsetmapTexture && MeshCollisionComponent)
|
|
{
|
|
CollisionXYOffsetData = (uint16*)MeshCollisionComponent->CollisionXYOffsetData.Lock(LOCK_READ_WRITE);
|
|
}
|
|
|
|
const int32 HeightmapSizeU = GetHeightmap()->Source.GetSizeX();
|
|
const int32 HeightmapSizeV = GetHeightmap()->Source.GetSizeY();
|
|
|
|
// Handle Material WPO baked into heightfield collision
|
|
// Material WPO is not currently supported for mesh collision components
|
|
const bool bUsingGrassMapHeights = Proxy->bBakeMaterialPositionOffsetIntoCollision && !MeshCollisionComponent && GrassData->HasData() && !IsGrassMapOutdated();
|
|
uint16* GrassHeights = nullptr;
|
|
if (bUsingGrassMapHeights)
|
|
{
|
|
if (CollisionMipLevel == 0)
|
|
{
|
|
GrassHeights = GrassData->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<uint8> CollisionMipData;
|
|
TArray64<uint8> SimpleCollisionMipData;
|
|
TArray64<uint8> XYOffsetMipData;
|
|
|
|
GetHeightmap()->Source.GetMipData(CollisionMipData, CollisionMipLevel);
|
|
if (SimpleCollisionMipLevel > CollisionMipLevel)
|
|
{
|
|
GetHeightmap()->Source.GetMipData(SimpleCollisionMipData, SimpleCollisionMipLevel);
|
|
}
|
|
if (XYOffsetmapTexture)
|
|
{
|
|
XYOffsetmapTexture->Source.GetMipData(XYOffsetMipData, CollisionMipLevel);
|
|
}
|
|
|
|
UpdateCollisionHeightData(
|
|
(FColor*)CollisionMipData.GetData(),
|
|
SimpleCollisionMipLevel > CollisionMipLevel ? (FColor*)SimpleCollisionMipData.GetData() : nullptr,
|
|
0, 0, MAX_int32, MAX_int32, true,
|
|
XYOffsetmapTexture ? (FColor*)XYOffsetMipData.GetData() : nullptr, bInUpdateHeightfieldRegion);
|
|
}
|
|
|
|
void ULandscapeComponent::RecreateCollisionComponent(bool bUseSimpleCollision)
|
|
{
|
|
ULandscapeHeightfieldCollisionComponent* CollisionComp = CollisionComponent.Get();
|
|
ULandscapeMeshCollisionComponent* MeshCollisionComponent = nullptr;
|
|
TArray<uint8> DominantLayerData;
|
|
TArray<ULandscapeLayerInfoObject*> LayerInfos;
|
|
ALandscapeProxy* Proxy = GetLandscapeProxy();
|
|
ULandscapeInfo* Info = GetLandscapeInfo();
|
|
const FCollisionSize CollisionSize = FCollisionSize::Create(NumSubsections, SubsectionSizeQuads, CollisionMipLevel);
|
|
const FCollisionSize SimpleCollisionSize = FCollisionSize::CreateSimple(bUseSimpleCollision, NumSubsections, SubsectionSizeQuads, SimpleCollisionMipLevel);
|
|
const int32 TotalCollisionSize = CollisionSize.SizeVertsSquare + SimpleCollisionSize.SizeVertsSquare;
|
|
|
|
if (CollisionComp) // remove old component before changing to other type collision...
|
|
{
|
|
if (CollisionComp->DominantLayerData.GetElementCount())
|
|
{
|
|
check(CollisionComp->DominantLayerData.GetElementCount() >= TotalCollisionSize);
|
|
DominantLayerData.AddUninitialized(TotalCollisionSize);
|
|
|
|
const uint8* SrcDominantLayerData = (uint8*)CollisionComp->DominantLayerData.Lock(LOCK_READ_ONLY);
|
|
FMemory::Memcpy(DominantLayerData.GetData(), SrcDominantLayerData, TotalCollisionSize * CollisionComp->DominantLayerData.GetElementSize());
|
|
CollisionComp->DominantLayerData.Unlock();
|
|
}
|
|
|
|
if (CollisionComp->ComponentLayerInfos.Num())
|
|
{
|
|
LayerInfos = CollisionComp->ComponentLayerInfos;
|
|
}
|
|
|
|
if (Info)
|
|
{
|
|
Info->Modify();
|
|
}
|
|
Proxy->Modify();
|
|
CollisionComp->DestroyComponent();
|
|
CollisionComp = nullptr;
|
|
}
|
|
|
|
if (XYOffsetmapTexture)
|
|
{
|
|
MeshCollisionComponent = NewObject<ULandscapeMeshCollisionComponent>(Proxy, NAME_None, RF_Transactional);
|
|
CollisionComp = MeshCollisionComponent;
|
|
}
|
|
else
|
|
{
|
|
MeshCollisionComponent = nullptr;
|
|
CollisionComp = NewObject<ULandscapeHeightfieldCollisionComponent>(Proxy, NAME_None, RF_Transactional);
|
|
}
|
|
|
|
CollisionComp->SetRelativeLocation(GetRelativeLocation());
|
|
CollisionComp->SetupAttachment(Proxy->GetRootComponent(), NAME_None);
|
|
Proxy->CollisionComponents.Add(CollisionComp);
|
|
|
|
CollisionComp->RenderComponent = this;
|
|
CollisionComp->SetSectionBase(GetSectionBase());
|
|
CollisionComp->CollisionSizeQuads = CollisionSize.SubsectionSizeQuads * NumSubsections;
|
|
CollisionComp->CollisionScale = (float)(ComponentSizeQuads) / (float)(CollisionComp->CollisionSizeQuads);
|
|
CollisionComp->SimpleCollisionSizeQuads = SimpleCollisionSize.SubsectionSizeQuads * NumSubsections;
|
|
CollisionComp->CachedLocalBox = CachedLocalBox;
|
|
CollisionComp->SetGenerateOverlapEvents(Proxy->bGenerateOverlapEvents);
|
|
|
|
// Reallocate raw collision data
|
|
CollisionComp->CollisionHeightData.Lock(LOCK_READ_WRITE);
|
|
uint16* CollisionHeightData = (uint16*)CollisionComp->CollisionHeightData.Realloc(TotalCollisionSize);
|
|
FMemory::Memzero(CollisionHeightData, TotalCollisionSize * CollisionComp->CollisionHeightData.GetElementSize());
|
|
CollisionComp->CollisionHeightData.Unlock();
|
|
|
|
if (XYOffsetmapTexture && MeshCollisionComponent)
|
|
{
|
|
// Need XYOffsetData for Collision Component
|
|
MeshCollisionComponent->CollisionXYOffsetData.Lock(LOCK_READ_WRITE);
|
|
uint16* CollisionXYOffsetData = (uint16*)MeshCollisionComponent->CollisionXYOffsetData.Realloc(TotalCollisionSize * 2);
|
|
FMemory::Memzero(CollisionXYOffsetData, TotalCollisionSize * 2 * MeshCollisionComponent->CollisionXYOffsetData.GetElementSize());
|
|
MeshCollisionComponent->CollisionXYOffsetData.Unlock();
|
|
}
|
|
|
|
if (DominantLayerData.Num())
|
|
{
|
|
CollisionComp->DominantLayerData.Lock(LOCK_READ_WRITE);
|
|
uint8* DestDominantLayerData = (uint8*)CollisionComp->DominantLayerData.Realloc(TotalCollisionSize);
|
|
FMemory::Memcpy(DestDominantLayerData, DominantLayerData.GetData(), TotalCollisionSize * CollisionComp->DominantLayerData.GetElementSize());
|
|
CollisionComp->DominantLayerData.Unlock();
|
|
}
|
|
|
|
if (LayerInfos.Num())
|
|
{
|
|
CollisionComp->ComponentLayerInfos = MoveTemp(LayerInfos);
|
|
}
|
|
CollisionComponent = CollisionComp;
|
|
}
|
|
|
|
void ULandscapeComponent::UpdateCollisionHeightBuffer( int32 InComponentX1, int32 InComponentY1, int32 InComponentX2, int32 InComponentY2, int32 InCollisionMipLevel, int32 InHeightmapSizeU, int32 InHeightmapSizeV,
|
|
const FColor* const InHeightmapTextureMipData, uint16* OutCollisionHeightData, uint16* InGrassHeightData,
|
|
const FColor* const InXYOffsetTextureMipData, uint16* OutCollisionXYOffsetData)
|
|
{
|
|
FCollisionSize CollisionSize = FCollisionSize::Create(NumSubsections, SubsectionSizeQuads, InCollisionMipLevel);
|
|
|
|
// Ratio to convert update region coordinate to collision mip coordinates
|
|
const float CollisionQuadRatio = (float)CollisionSize.SubsectionSizeQuads / (float)SubsectionSizeQuads;
|
|
|
|
const int32 SubSectionX1 = FMath::Max(0, FMath::DivideAndRoundDown(InComponentX1 - 1, SubsectionSizeQuads));
|
|
const int32 SubSectionY1 = FMath::Max(0, FMath::DivideAndRoundDown(InComponentY1 - 1, SubsectionSizeQuads));
|
|
const int32 SubSectionX2 = FMath::Min(FMath::DivideAndRoundUp(InComponentX2 + 1, SubsectionSizeQuads), NumSubsections);
|
|
const int32 SubSectionY2 = FMath::Min(FMath::DivideAndRoundUp(InComponentY2 + 1, SubsectionSizeQuads), NumSubsections);
|
|
|
|
const int32 MipSizeU = InHeightmapSizeU >> InCollisionMipLevel;
|
|
const int32 MipSizeV = InHeightmapSizeV >> InCollisionMipLevel;
|
|
|
|
const int32 HeightmapOffsetX = FMath::RoundToInt(HeightmapScaleBias.Z * (float)InHeightmapSizeU) >> InCollisionMipLevel;
|
|
const int32 HeightmapOffsetY = FMath::RoundToInt(HeightmapScaleBias.W * (float)InHeightmapSizeV) >> InCollisionMipLevel;
|
|
|
|
const int32 XYMipSizeU = XYOffsetmapTexture ? XYOffsetmapTexture->Source.GetSizeX() >> InCollisionMipLevel : 0;
|
|
|
|
for (int32 SubsectionY = SubSectionY1; SubsectionY < SubSectionY2; ++SubsectionY)
|
|
{
|
|
for (int32 SubsectionX = SubSectionX1; SubsectionX < SubSectionX2; ++SubsectionX)
|
|
{
|
|
// Area to update in subsection coordinates
|
|
const int32 SubX1 = InComponentX1 - SubsectionSizeQuads * SubsectionX;
|
|
const int32 SubY1 = InComponentY1 - SubsectionSizeQuads * SubsectionY;
|
|
const int32 SubX2 = InComponentX2 - SubsectionSizeQuads * SubsectionX;
|
|
const int32 SubY2 = InComponentY2 - SubsectionSizeQuads * SubsectionY;
|
|
|
|
// Area to update in collision mip level coords
|
|
const int32 CollisionSubX1 = FMath::FloorToInt((float)SubX1 * CollisionQuadRatio);
|
|
const int32 CollisionSubY1 = FMath::FloorToInt((float)SubY1 * CollisionQuadRatio);
|
|
const int32 CollisionSubX2 = FMath::CeilToInt((float)SubX2 * CollisionQuadRatio);
|
|
const int32 CollisionSubY2 = FMath::CeilToInt((float)SubY2 * CollisionQuadRatio);
|
|
|
|
// Clamp area to update
|
|
const int32 VertX1 = FMath::Clamp<int32>(CollisionSubX1, 0, CollisionSize.SubsectionSizeQuads);
|
|
const int32 VertY1 = FMath::Clamp<int32>(CollisionSubY1, 0, CollisionSize.SubsectionSizeQuads);
|
|
const int32 VertX2 = FMath::Clamp<int32>(CollisionSubX2, 0, CollisionSize.SubsectionSizeQuads);
|
|
const int32 VertY2 = FMath::Clamp<int32>(CollisionSubY2, 0, CollisionSize.SubsectionSizeQuads);
|
|
|
|
for (int32 VertY = VertY1; VertY <= VertY2; VertY++)
|
|
{
|
|
for (int32 VertX = VertX1; VertX <= VertX2; VertX++)
|
|
{
|
|
// this uses Quads as we don't want the duplicated vertices
|
|
const int32 CompVertX = CollisionSize.SubsectionSizeQuads * SubsectionX + VertX;
|
|
const int32 CompVertY = CollisionSize.SubsectionSizeQuads * SubsectionY + VertY;
|
|
|
|
if (InGrassHeightData)
|
|
{
|
|
uint16& CollisionHeight = OutCollisionHeightData[CompVertX + CompVertY * CollisionSize.SizeVerts];
|
|
const uint16& NewHeight = InGrassHeightData[CompVertX + CompVertY * CollisionSize.SizeVerts];
|
|
CollisionHeight = NewHeight;
|
|
}
|
|
else
|
|
{
|
|
// X/Y of the vertex we're looking indexed into the texture data
|
|
const int32 TexX = HeightmapOffsetX + CollisionSize.SubsectionSizeVerts * SubsectionX + VertX;
|
|
const int32 TexY = HeightmapOffsetY + CollisionSize.SubsectionSizeVerts * SubsectionY + VertY;
|
|
const FColor& TexData = InHeightmapTextureMipData[TexX + TexY * MipSizeU];
|
|
|
|
// Copy collision data
|
|
uint16& CollisionHeight = OutCollisionHeightData[CompVertX + CompVertY * CollisionSize.SizeVerts];
|
|
const uint16 NewHeight = TexData.R << 8 | TexData.G;
|
|
|
|
CollisionHeight = NewHeight;
|
|
}
|
|
|
|
if (XYOffsetmapTexture && InXYOffsetTextureMipData && OutCollisionXYOffsetData)
|
|
{
|
|
const int32 TexX = CollisionSize.SubsectionSizeVerts * SubsectionX + VertX;
|
|
const int32 TexY = CollisionSize.SubsectionSizeVerts * SubsectionY + VertY;
|
|
const FColor& TexData = InXYOffsetTextureMipData[TexX + TexY * XYMipSizeU];
|
|
|
|
// Copy collision data
|
|
const uint16 NewXOffset = TexData.R << 8 | TexData.G;
|
|
const uint16 NewYOffset = TexData.B << 8 | TexData.A;
|
|
|
|
const int32 XYIndex = CompVertX + CompVertY * CollisionSize.SizeVerts;
|
|
OutCollisionXYOffsetData[XYIndex * 2] = NewXOffset;
|
|
OutCollisionXYOffsetData[XYIndex * 2 + 1] = NewYOffset;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::UpdateDominantLayerBuffer(int32 InComponentX1, int32 InComponentY1, int32 InComponentX2, int32 InComponentY2, int32 InCollisionMipLevel, int32 InWeightmapSizeU, int32 InDataLayerIdx, const TArray<uint8*>& InCollisionDataPtrs, const TArray<ULandscapeLayerInfoObject*>& InLayerInfos, uint8* OutDominantLayerData)
|
|
{
|
|
const int32 MipSizeU = InWeightmapSizeU >> InCollisionMipLevel;
|
|
|
|
FCollisionSize CollisionSize = FCollisionSize::Create(NumSubsections, SubsectionSizeQuads, InCollisionMipLevel);
|
|
|
|
// Ratio to convert update region coordinate to collision mip coordinates
|
|
const float CollisionQuadRatio = (float)CollisionSize.SubsectionSizeQuads / (float)SubsectionSizeQuads;
|
|
|
|
const int32 SubSectionX1 = FMath::Max(0, FMath::DivideAndRoundDown(InComponentX1 - 1, SubsectionSizeQuads));
|
|
const int32 SubSectionY1 = FMath::Max(0, FMath::DivideAndRoundDown(InComponentY1 - 1, SubsectionSizeQuads));
|
|
const int32 SubSectionX2 = FMath::Min(FMath::DivideAndRoundUp(InComponentX2 + 1, SubsectionSizeQuads), NumSubsections);
|
|
const int32 SubSectionY2 = FMath::Min(FMath::DivideAndRoundUp(InComponentY2 + 1, SubsectionSizeQuads), NumSubsections);
|
|
for (int32 SubsectionY = SubSectionY1; SubsectionY < SubSectionY2; ++SubsectionY)
|
|
{
|
|
for (int32 SubsectionX = SubSectionX1; SubsectionX < SubSectionX2; ++SubsectionX)
|
|
{
|
|
// Area to update in subsection coordinates
|
|
const int32 SubX1 = InComponentX1 - SubsectionSizeQuads * SubsectionX;
|
|
const int32 SubY1 = InComponentY1 - SubsectionSizeQuads * SubsectionY;
|
|
const int32 SubX2 = InComponentX2 - SubsectionSizeQuads * SubsectionX;
|
|
const int32 SubY2 = InComponentY2 - SubsectionSizeQuads * SubsectionY;
|
|
|
|
// Area to update in collision mip level coords
|
|
const int32 CollisionSubX1 = FMath::FloorToInt((float)SubX1 * CollisionQuadRatio);
|
|
const int32 CollisionSubY1 = FMath::FloorToInt((float)SubY1 * CollisionQuadRatio);
|
|
const int32 CollisionSubX2 = FMath::CeilToInt((float)SubX2 * CollisionQuadRatio);
|
|
const int32 CollisionSubY2 = FMath::CeilToInt((float)SubY2 * CollisionQuadRatio);
|
|
|
|
// Clamp area to update
|
|
const int32 VertX1 = FMath::Clamp<int32>(CollisionSubX1, 0, CollisionSize.SubsectionSizeQuads);
|
|
const int32 VertY1 = FMath::Clamp<int32>(CollisionSubY1, 0, CollisionSize.SubsectionSizeQuads);
|
|
const int32 VertX2 = FMath::Clamp<int32>(CollisionSubX2, 0, CollisionSize.SubsectionSizeQuads);
|
|
const int32 VertY2 = FMath::Clamp<int32>(CollisionSubY2, 0, CollisionSize.SubsectionSizeQuads);
|
|
|
|
for (int32 VertY = VertY1; VertY <= VertY2; VertY++)
|
|
{
|
|
for (int32 VertX = VertX1; VertX <= VertX2; VertX++)
|
|
{
|
|
// X/Y of the vertex we're looking indexed into the texture data
|
|
const int32 TexX = CollisionSize.SubsectionSizeVerts * SubsectionX + VertX;
|
|
const int32 TexY = CollisionSize.SubsectionSizeVerts * SubsectionY + VertY;
|
|
const int32 DataOffset = (TexX + TexY * MipSizeU) * sizeof(FColor);
|
|
|
|
uint8 DominantLayer = 255; // 255 as invalid value
|
|
int32 DominantWeight = 0;
|
|
for (int32 LayerIdx = 0; LayerIdx < InCollisionDataPtrs.Num(); LayerIdx++)
|
|
{
|
|
const uint8 LayerWeight = InCollisionDataPtrs[LayerIdx][DataOffset];
|
|
const uint8 LayerMinimumWeight = InLayerInfos[LayerIdx] ? (uint8)(InLayerInfos[LayerIdx]->MinimumCollisionRelevanceWeight * 255) : 0;
|
|
|
|
if (LayerIdx == InDataLayerIdx) // Override value for hole
|
|
{
|
|
if (LayerWeight > 170) // 255 * 0.66...
|
|
{
|
|
DominantLayer = LayerIdx;
|
|
DominantWeight = INT_MAX;
|
|
}
|
|
}
|
|
else if (LayerWeight > DominantWeight && LayerWeight >= LayerMinimumWeight)
|
|
{
|
|
DominantLayer = LayerIdx;
|
|
DominantWeight = LayerWeight;
|
|
}
|
|
}
|
|
|
|
// this uses Quads as we don't want the duplicated vertices
|
|
const int32 CompVertX = CollisionSize.SubsectionSizeQuads * SubsectionX + VertX;
|
|
const int32 CompVertY = CollisionSize.SubsectionSizeQuads * SubsectionY + VertY;
|
|
|
|
// Set collision data
|
|
OutDominantLayerData[CompVertX + CompVertY * CollisionSize.SizeVerts] = DominantLayer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::UpdateCollisionLayerData(const FColor* const* const WeightmapTextureMipData, const FColor* const* const SimpleCollisionWeightmapTextureMipData, int32 ComponentX1, int32 ComponentY1, int32 ComponentX2, int32 ComponentY2)
|
|
{
|
|
ULandscapeInfo* Info = GetLandscapeInfo();
|
|
ALandscapeProxy* Proxy = GetLandscapeProxy();
|
|
FIntPoint ComponentKey = GetSectionBase() / ComponentSizeQuads;
|
|
|
|
ULandscapeHeightfieldCollisionComponent* CollisionComp = CollisionComponent.Get();
|
|
|
|
if (CollisionComp)
|
|
{
|
|
if (!Proxy->HasLayersContent())
|
|
{
|
|
CollisionComp->Modify();
|
|
}
|
|
|
|
// Simple collision is not currently supported with mesh collision components
|
|
const bool bUsingSimpleCollision = (SimpleCollisionMipLevel > CollisionMipLevel && SimpleCollisionWeightmapTextureMipData && !XYOffsetmapTexture);
|
|
|
|
TArray<ULandscapeLayerInfoObject*> CandidateLayers;
|
|
TArray<uint8*> CandidateDataPtrs;
|
|
TArray<uint8*> SimpleCollisionDataPtrs;
|
|
|
|
bool bExistingLayerMismatch = false;
|
|
int32 DataLayerIdx = INDEX_NONE;
|
|
|
|
TArray<FWeightmapLayerAllocationInfo>& ComponentWeightmapLayerAllocations = GetWeightmapLayerAllocations(false);
|
|
TArray<UTexture2D*>& 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<UTexture2D*>& ComponentWeightmapsTexture = GetWeightmapTextures();
|
|
|
|
// Generate the dominant layer data
|
|
TArray<TArray64<uint8>> WeightmapTextureMipData;
|
|
TArray<FColor*> WeightmapTextureMipDataParam;
|
|
WeightmapTextureMipData.Reserve(ComponentWeightmapsTexture.Num());
|
|
WeightmapTextureMipDataParam.Reserve(ComponentWeightmapsTexture.Num());
|
|
for (int32 WeightmapIdx = 0; WeightmapIdx < ComponentWeightmapsTexture.Num(); ++WeightmapIdx)
|
|
{
|
|
TArray64<uint8>& MipData = WeightmapTextureMipData.AddDefaulted_GetRef();
|
|
ComponentWeightmapsTexture[WeightmapIdx]->Source.GetMipData(MipData, CollisionMipLevel);
|
|
WeightmapTextureMipDataParam.Add((FColor*)MipData.GetData());
|
|
}
|
|
|
|
TArray<TArray64<uint8>> SimpleCollisionWeightmapMipData;
|
|
TArray<FColor*> SimpleCollisionWeightmapMipDataParam;
|
|
if (SimpleCollisionMipLevel > CollisionMipLevel)
|
|
{
|
|
SimpleCollisionWeightmapMipData.Reserve(ComponentWeightmapsTexture.Num());
|
|
SimpleCollisionWeightmapMipDataParam.Reserve(ComponentWeightmapsTexture.Num());
|
|
for (int32 WeightmapIdx = 0; WeightmapIdx < ComponentWeightmapsTexture.Num(); ++WeightmapIdx)
|
|
{
|
|
TArray64<uint8>& MipData = SimpleCollisionWeightmapMipData.AddDefaulted_GetRef();
|
|
ComponentWeightmapsTexture[WeightmapIdx]->Source.GetMipData(MipData, SimpleCollisionMipLevel);
|
|
SimpleCollisionWeightmapMipDataParam.Add((FColor*)MipData.GetData());
|
|
}
|
|
}
|
|
|
|
UpdateCollisionLayerData(WeightmapTextureMipDataParam.GetData(), SimpleCollisionWeightmapMipDataParam.GetData());
|
|
}
|
|
|
|
uint32 ULandscapeComponent::CalculatePhysicalMaterialTaskHash() const
|
|
{
|
|
uint32 Hash = 0;
|
|
|
|
// Take into account any material changes.
|
|
UMaterialInterface* Material = GetLandscapeMaterial();
|
|
for (UMaterialInstanceConstant* MIC = Cast<UMaterialInstanceConstant>(Material); MIC; MIC = Cast<UMaterialInstanceConstant>(Material))
|
|
{
|
|
Hash = FCrc::TypeCrc32(MIC->ParameterStateId, Hash);
|
|
Material = MIC->Parent;
|
|
}
|
|
UMaterial* MaterialBase = Cast<UMaterial>(Material);
|
|
if (MaterialBase != nullptr)
|
|
{
|
|
Hash = FCrc::TypeCrc32(MaterialBase->StateId, Hash);
|
|
}
|
|
|
|
// We could take into account heightmap and weightmap changes here by adding to the hash.
|
|
// Instead we are resetting the stored hash in UpdateCollisionHeightData() and UpdateCollisionLayerData().
|
|
|
|
return Hash;
|
|
}
|
|
|
|
void ULandscapeComponent::UpdatePhysicalMaterialTasks()
|
|
{
|
|
uint32 Hash = CalculatePhysicalMaterialTaskHash();
|
|
if (PhysicalMaterialHash != Hash)
|
|
{
|
|
PhysicalMaterialTask.Init(this);
|
|
PhysicalMaterialHash = Hash;
|
|
}
|
|
|
|
if (PhysicalMaterialTask.IsValid())
|
|
{
|
|
if (PhysicalMaterialTask.IsComplete())
|
|
{
|
|
UpdateCollisionPhysicalMaterialData(PhysicalMaterialTask.GetResultMaterials(), PhysicalMaterialTask.GetResultIds());
|
|
|
|
PhysicalMaterialTask.Release();
|
|
|
|
// We do not force an update of the physics data here.
|
|
// We don't need the information immediately in the editor and update will happen on cook or PIE.
|
|
}
|
|
else
|
|
{
|
|
PhysicalMaterialTask.Tick();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::UpdateCollisionPhysicalMaterialData(TArray<UPhysicalMaterial*> const& InPhysicalMaterials, TArray<uint8> const& InMaterialIds)
|
|
{
|
|
// Copy the physical material array
|
|
CollisionComponent->PhysicalMaterialRenderObjects = InPhysicalMaterials;
|
|
|
|
// Copy the physical material IDs for both the full and (optional) simple collision.
|
|
const int32 SizeVerts = SubsectionSizeQuads * NumSubsections + 1;
|
|
check(InMaterialIds.Num() == SizeVerts * SizeVerts);
|
|
const int32 FullCollisionSizeVerts = CollisionComponent->CollisionSizeQuads + 1;
|
|
const int32 SimpleCollisionSizeVerts = CollisionComponent->SimpleCollisionSizeQuads > 0 ? CollisionComponent->SimpleCollisionSizeQuads + 1 : 0;
|
|
const int32 BulkDataSize = FullCollisionSizeVerts * FullCollisionSizeVerts + SimpleCollisionSizeVerts * SimpleCollisionSizeVerts;
|
|
|
|
void* Data = CollisionComponent->PhysicalMaterialRenderData.Lock(LOCK_READ_WRITE);
|
|
Data = CollisionComponent->PhysicalMaterialRenderData.Realloc(BulkDataSize);
|
|
uint8* WritePtr = (uint8*)Data;
|
|
|
|
const int32 CollisionSizes[2] = { FullCollisionSizeVerts, SimpleCollisionSizeVerts };
|
|
for (int32 i = 0; i < 2; ++i)
|
|
{
|
|
const int32 CollisionSizeVerts = CollisionSizes[i];
|
|
if (CollisionSizeVerts == SizeVerts)
|
|
{
|
|
FMemory::Memcpy(WritePtr, InMaterialIds.GetData(), SizeVerts * SizeVerts);
|
|
WritePtr += SizeVerts * SizeVerts;
|
|
}
|
|
else if (CollisionSizeVerts > 0)
|
|
{
|
|
const int32 StepSize = SizeVerts / CollisionSizeVerts;
|
|
check(CollisionSizeVerts * StepSize == SizeVerts);
|
|
for (int32 y = 0; y < SizeVerts; y += StepSize)
|
|
{
|
|
for (int32 x = 0; x < SizeVerts; x += StepSize)
|
|
{
|
|
*WritePtr++ = InMaterialIds[y * SizeVerts + x];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
check(WritePtr - (uint8*)Data == BulkDataSize);
|
|
CollisionComponent->PhysicalMaterialRenderData.Unlock();
|
|
}
|
|
|
|
void ULandscapeComponent::GenerateHeightmapMips(TArray<FColor*>& HeightmapTextureMipData, int32 ComponentX1/*=0*/, int32 ComponentY1/*=0*/, int32 ComponentX2/*=MAX_int32*/, int32 ComponentY2/*=MAX_int32*/, FLandscapeTextureDataInfo* TextureDataInfo/*=nullptr*/)
|
|
{
|
|
bool EndX = false;
|
|
bool EndY = false;
|
|
|
|
if (ComponentX1 == MAX_int32)
|
|
{
|
|
EndX = true;
|
|
ComponentX1 = 0;
|
|
}
|
|
|
|
if (ComponentY1 == MAX_int32)
|
|
{
|
|
EndY = true;
|
|
ComponentY1 = 0;
|
|
}
|
|
|
|
if (ComponentX2 == MAX_int32)
|
|
{
|
|
ComponentX2 = ComponentSizeQuads;
|
|
}
|
|
if (ComponentY2 == MAX_int32)
|
|
{
|
|
ComponentY2 = ComponentSizeQuads;
|
|
}
|
|
|
|
int32 HeightmapSizeU = GetHeightmap()->Source.GetSizeX();
|
|
int32 HeightmapSizeV = GetHeightmap()->Source.GetSizeY();
|
|
|
|
int32 HeightmapOffsetX = FMath::RoundToInt(HeightmapScaleBias.Z * (float)HeightmapSizeU);
|
|
int32 HeightmapOffsetY = FMath::RoundToInt(HeightmapScaleBias.W * (float)HeightmapSizeV);
|
|
|
|
for (int32 SubsectionY = 0; SubsectionY < NumSubsections; SubsectionY++)
|
|
{
|
|
// Check if subsection is fully above or below the area we are interested in
|
|
if ((ComponentY2 < SubsectionSizeQuads*SubsectionY) || // above
|
|
(ComponentY1 > SubsectionSizeQuads*(SubsectionY + 1))) // below
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int32 SubsectionX = 0; SubsectionX < NumSubsections; SubsectionX++)
|
|
{
|
|
// Check if subsection is fully to the left or right of the area we are interested in
|
|
if ((ComponentX2 < SubsectionSizeQuads*SubsectionX) || // left
|
|
(ComponentX1 > SubsectionSizeQuads*(SubsectionX + 1))) // right
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Area to update in previous mip level coords
|
|
int32 PrevMipSubX1 = ComponentX1 - SubsectionSizeQuads*SubsectionX;
|
|
int32 PrevMipSubY1 = ComponentY1 - SubsectionSizeQuads*SubsectionY;
|
|
int32 PrevMipSubX2 = ComponentX2 - SubsectionSizeQuads*SubsectionX;
|
|
int32 PrevMipSubY2 = ComponentY2 - SubsectionSizeQuads*SubsectionY;
|
|
|
|
int32 PrevMipSubsectionSizeQuads = SubsectionSizeQuads;
|
|
float InvPrevMipSubsectionSizeQuads = 1.0f / (float)SubsectionSizeQuads;
|
|
|
|
int32 PrevMipSizeU = HeightmapSizeU;
|
|
int32 PrevMipSizeV = HeightmapSizeV;
|
|
|
|
int32 PrevMipHeightmapOffsetX = HeightmapOffsetX;
|
|
int32 PrevMipHeightmapOffsetY = HeightmapOffsetY;
|
|
|
|
for (int32 Mip = 1; Mip < HeightmapTextureMipData.Num(); Mip++)
|
|
{
|
|
int32 MipSizeU = HeightmapSizeU >> Mip;
|
|
int32 MipSizeV = HeightmapSizeV >> Mip;
|
|
|
|
int32 MipSubsectionSizeQuads = ((SubsectionSizeQuads + 1) >> Mip) - 1;
|
|
float InvMipSubsectionSizeQuads = 1.0f / (float)MipSubsectionSizeQuads;
|
|
|
|
int32 MipHeightmapOffsetX = HeightmapOffsetX >> Mip;
|
|
int32 MipHeightmapOffsetY = HeightmapOffsetY >> Mip;
|
|
|
|
// Area to update in current mip level coords
|
|
int32 MipSubX1 = FMath::FloorToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubX1 * InvPrevMipSubsectionSizeQuads);
|
|
int32 MipSubY1 = FMath::FloorToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubY1 * InvPrevMipSubsectionSizeQuads);
|
|
int32 MipSubX2 = FMath::CeilToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubX2 * InvPrevMipSubsectionSizeQuads);
|
|
int32 MipSubY2 = FMath::CeilToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubY2 * InvPrevMipSubsectionSizeQuads);
|
|
|
|
// Clamp area to update
|
|
int32 VertX1 = FMath::Clamp<int32>(MipSubX1, 0, MipSubsectionSizeQuads);
|
|
int32 VertY1 = FMath::Clamp<int32>(MipSubY1, 0, MipSubsectionSizeQuads);
|
|
int32 VertX2 = FMath::Clamp<int32>(MipSubX2, 0, MipSubsectionSizeQuads);
|
|
int32 VertY2 = FMath::Clamp<int32>(MipSubY2, 0, MipSubsectionSizeQuads);
|
|
|
|
for (int32 VertY = VertY1; VertY <= VertY2; VertY++)
|
|
{
|
|
for (int32 VertX = VertX1; VertX <= VertX2; VertX++)
|
|
{
|
|
// Convert VertX/Y into previous mip's coords
|
|
float PrevMipVertX = (float)PrevMipSubsectionSizeQuads * (float)VertX * InvMipSubsectionSizeQuads;
|
|
float PrevMipVertY = (float)PrevMipSubsectionSizeQuads * (float)VertY * InvMipSubsectionSizeQuads;
|
|
|
|
#if 0
|
|
// Validate that the vertex we skip wouldn't use the updated data in the parent mip.
|
|
// Note this validation is doesn't do anything unless you change the VertY/VertX loops
|
|
// above to process all verts from 0 .. MipSubsectionSizeQuads.
|
|
if (VertX < VertX1 || VertX > VertX2)
|
|
{
|
|
check(FMath::CeilToInt(PrevMipVertX) < PrevMipSubX1 || FMath::FloorToInt(PrevMipVertX) > PrevMipSubX2);
|
|
continue;
|
|
}
|
|
|
|
if (VertY < VertY1 || VertY > VertY2)
|
|
{
|
|
check(FMath::CeilToInt(PrevMipVertY) < PrevMipSubY1 || FMath::FloorToInt(PrevMipVertY) > PrevMipSubY2);
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
// X/Y of the vertex we're looking indexed into the texture data
|
|
int32 TexX = (MipHeightmapOffsetX)+(MipSubsectionSizeQuads + 1) * SubsectionX + VertX;
|
|
int32 TexY = (MipHeightmapOffsetY)+(MipSubsectionSizeQuads + 1) * SubsectionY + VertY;
|
|
|
|
float fPrevMipTexX = (float)(PrevMipHeightmapOffsetX)+(float)((PrevMipSubsectionSizeQuads + 1) * SubsectionX) + PrevMipVertX;
|
|
float fPrevMipTexY = (float)(PrevMipHeightmapOffsetY)+(float)((PrevMipSubsectionSizeQuads + 1) * SubsectionY) + PrevMipVertY;
|
|
|
|
int32 PrevMipTexX = FMath::FloorToInt(fPrevMipTexX);
|
|
float fPrevMipTexFracX = FMath::Fractional(fPrevMipTexX);
|
|
int32 PrevMipTexY = FMath::FloorToInt(fPrevMipTexY);
|
|
float fPrevMipTexFracY = FMath::Fractional(fPrevMipTexY);
|
|
|
|
checkSlow(TexX >= 0 && TexX < MipSizeU);
|
|
checkSlow(TexY >= 0 && TexY < MipSizeV);
|
|
checkSlow(PrevMipTexX >= 0 && PrevMipTexX < PrevMipSizeU);
|
|
checkSlow(PrevMipTexY >= 0 && PrevMipTexY < PrevMipSizeV);
|
|
|
|
int32 PrevMipTexX1 = FMath::Min<int32>(PrevMipTexX + 1, PrevMipSizeU - 1);
|
|
int32 PrevMipTexY1 = FMath::Min<int32>(PrevMipTexY + 1, PrevMipSizeV - 1);
|
|
|
|
// Padding for missing data for MIP 0
|
|
if (Mip == 1)
|
|
{
|
|
if (EndX && SubsectionX == NumSubsections - 1 && VertX == VertX2)
|
|
{
|
|
for (int32 PaddingIdx = PrevMipTexX + PrevMipTexY * PrevMipSizeU; PaddingIdx + 1 < PrevMipTexY1 * PrevMipSizeU; ++PaddingIdx)
|
|
{
|
|
HeightmapTextureMipData[Mip - 1][PaddingIdx + 1] = HeightmapTextureMipData[Mip - 1][PaddingIdx];
|
|
}
|
|
}
|
|
|
|
if (EndY && SubsectionX == NumSubsections - 1 && SubsectionY == NumSubsections - 1 && VertY == VertY2 && VertX == VertX2)
|
|
{
|
|
for (int32 PaddingYIdx = PrevMipTexY; PaddingYIdx + 1 < PrevMipSizeV; ++PaddingYIdx)
|
|
{
|
|
for (int32 PaddingXIdx = 0; PaddingXIdx < PrevMipSizeU; ++PaddingXIdx)
|
|
{
|
|
HeightmapTextureMipData[Mip - 1][PaddingXIdx + (PaddingYIdx + 1) * PrevMipSizeU] = HeightmapTextureMipData[Mip - 1][PaddingXIdx + PaddingYIdx * PrevMipSizeU];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FColor* TexData = &(HeightmapTextureMipData[Mip])[TexX + TexY * MipSizeU];
|
|
FColor *PreMipTexData00 = &(HeightmapTextureMipData[Mip - 1])[PrevMipTexX + PrevMipTexY * PrevMipSizeU];
|
|
FColor *PreMipTexData01 = &(HeightmapTextureMipData[Mip - 1])[PrevMipTexX + PrevMipTexY1 * PrevMipSizeU];
|
|
FColor *PreMipTexData10 = &(HeightmapTextureMipData[Mip - 1])[PrevMipTexX1 + PrevMipTexY * PrevMipSizeU];
|
|
FColor *PreMipTexData11 = &(HeightmapTextureMipData[Mip - 1])[PrevMipTexX1 + PrevMipTexY1 * PrevMipSizeU];
|
|
|
|
// Lerp height values
|
|
uint16 PrevMipHeightValue00 = PreMipTexData00->R << 8 | PreMipTexData00->G;
|
|
uint16 PrevMipHeightValue01 = PreMipTexData01->R << 8 | PreMipTexData01->G;
|
|
uint16 PrevMipHeightValue10 = PreMipTexData10->R << 8 | PreMipTexData10->G;
|
|
uint16 PrevMipHeightValue11 = PreMipTexData11->R << 8 | PreMipTexData11->G;
|
|
uint16 HeightValue = FMath::RoundToInt(
|
|
FMath::Lerp(
|
|
FMath::Lerp((float)PrevMipHeightValue00, (float)PrevMipHeightValue10, fPrevMipTexFracX),
|
|
FMath::Lerp((float)PrevMipHeightValue01, (float)PrevMipHeightValue11, fPrevMipTexFracX),
|
|
fPrevMipTexFracY));
|
|
|
|
TexData->R = HeightValue >> 8;
|
|
TexData->G = HeightValue & 255;
|
|
|
|
// Lerp tangents
|
|
TexData->B = FMath::RoundToInt(
|
|
FMath::Lerp(
|
|
FMath::Lerp((float)PreMipTexData00->B, (float)PreMipTexData10->B, fPrevMipTexFracX),
|
|
FMath::Lerp((float)PreMipTexData01->B, (float)PreMipTexData11->B, fPrevMipTexFracX),
|
|
fPrevMipTexFracY));
|
|
|
|
TexData->A = FMath::RoundToInt(
|
|
FMath::Lerp(
|
|
FMath::Lerp((float)PreMipTexData00->A, (float)PreMipTexData10->A, fPrevMipTexFracX),
|
|
FMath::Lerp((float)PreMipTexData01->A, (float)PreMipTexData11->A, fPrevMipTexFracX),
|
|
fPrevMipTexFracY));
|
|
|
|
// Padding for missing data
|
|
if (EndX && SubsectionX == NumSubsections - 1 && VertX == VertX2)
|
|
{
|
|
for (int32 PaddingIdx = TexX + TexY * MipSizeU; PaddingIdx + 1 < (TexY + 1) * MipSizeU; ++PaddingIdx)
|
|
{
|
|
HeightmapTextureMipData[Mip][PaddingIdx + 1] = HeightmapTextureMipData[Mip][PaddingIdx];
|
|
}
|
|
}
|
|
|
|
if (EndY && SubsectionX == NumSubsections - 1 && SubsectionY == NumSubsections - 1 && VertY == VertY2 && VertX == VertX2)
|
|
{
|
|
for (int32 PaddingYIdx = TexY; PaddingYIdx + 1 < MipSizeV; ++PaddingYIdx)
|
|
{
|
|
for (int32 PaddingXIdx = 0; PaddingXIdx < MipSizeU; ++PaddingXIdx)
|
|
{
|
|
HeightmapTextureMipData[Mip][PaddingXIdx + (PaddingYIdx + 1) * MipSizeU] = HeightmapTextureMipData[Mip][PaddingXIdx + PaddingYIdx * MipSizeU];
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// Record the areas we updated
|
|
if (TextureDataInfo)
|
|
{
|
|
int32 TexX1 = (MipHeightmapOffsetX)+(MipSubsectionSizeQuads + 1) * SubsectionX + VertX1;
|
|
int32 TexY1 = (MipHeightmapOffsetY)+(MipSubsectionSizeQuads + 1) * SubsectionY + VertY1;
|
|
int32 TexX2 = (MipHeightmapOffsetX)+(MipSubsectionSizeQuads + 1) * SubsectionX + VertX2;
|
|
int32 TexY2 = (MipHeightmapOffsetY)+(MipSubsectionSizeQuads + 1) * SubsectionY + VertY2;
|
|
TextureDataInfo->AddMipUpdateRegion(Mip, TexX1, TexY1, TexX2, TexY2);
|
|
}
|
|
|
|
// Copy current mip values to prev as we move to the next mip.
|
|
PrevMipSubsectionSizeQuads = MipSubsectionSizeQuads;
|
|
InvPrevMipSubsectionSizeQuads = InvMipSubsectionSizeQuads;
|
|
|
|
PrevMipSizeU = MipSizeU;
|
|
PrevMipSizeV = MipSizeV;
|
|
|
|
PrevMipHeightmapOffsetX = MipHeightmapOffsetX;
|
|
PrevMipHeightmapOffsetY = MipHeightmapOffsetY;
|
|
|
|
// Use this mip's area as we move to the next mip
|
|
PrevMipSubX1 = MipSubX1;
|
|
PrevMipSubY1 = MipSubY1;
|
|
PrevMipSubX2 = MipSubX2;
|
|
PrevMipSubY2 = MipSubY2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::CreateEmptyTextureMips(UTexture2D* Texture, bool bClear /*= false*/)
|
|
{
|
|
ETextureSourceFormat Format = Texture->Source.GetFormat();
|
|
int32 SizeU = Texture->Source.GetSizeX();
|
|
int32 SizeV = Texture->Source.GetSizeY();
|
|
|
|
if (bClear)
|
|
{
|
|
Texture->Source.Init2DWithMipChain(SizeU, SizeV, Format);
|
|
int32 NumMips = Texture->Source.GetNumMips();
|
|
for (int32 MipIndex = 0; MipIndex < NumMips; ++MipIndex)
|
|
{
|
|
uint8* MipData = Texture->Source.LockMip(MipIndex);
|
|
FMemory::Memzero(MipData, Texture->Source.CalcMipSize(MipIndex));
|
|
Texture->Source.UnlockMip(MipIndex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TArray64<uint8> TopMipData;
|
|
Texture->Source.GetMipData(TopMipData, 0);
|
|
Texture->Source.Init2DWithMipChain(SizeU, SizeV, Format);
|
|
int32 NumMips = Texture->Source.GetNumMips();
|
|
uint8* MipData = Texture->Source.LockMip(0);
|
|
FMemory::Memcpy(MipData, TopMipData.GetData(), TopMipData.Num());
|
|
Texture->Source.UnlockMip(0);
|
|
}
|
|
}
|
|
|
|
template<typename DataType>
|
|
void ULandscapeComponent::GenerateMipsTempl(int32 InNumSubsections, int32 InSubsectionSizeQuads, UTexture2D* Texture, DataType* BaseMipData)
|
|
{
|
|
// Stores pointers to the locked mip data
|
|
TArray<DataType*> MipData;
|
|
MipData.Add(BaseMipData);
|
|
for (int32 MipIndex = 1; MipIndex < Texture->Source.GetNumMips(); ++MipIndex)
|
|
{
|
|
MipData.Add((DataType*)Texture->Source.LockMip(MipIndex));
|
|
}
|
|
|
|
// Update the newly created mips
|
|
UpdateMipsTempl<DataType>(InNumSubsections, InSubsectionSizeQuads, Texture, MipData);
|
|
|
|
// Unlock all the new mips, but not the base mip's data
|
|
for (int32 i = 1; i < MipData.Num(); i++)
|
|
{
|
|
Texture->Source.UnlockMip(i);
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::GenerateWeightmapMips(int32 InNumSubsections, int32 InSubsectionSizeQuads, UTexture2D* WeightmapTexture, FColor* BaseMipData)
|
|
{
|
|
GenerateMipsTempl<FColor>(InNumSubsections, InSubsectionSizeQuads, WeightmapTexture, BaseMipData);
|
|
}
|
|
|
|
namespace
|
|
{
|
|
template<typename DataType>
|
|
void BiLerpTextureData(DataType* Output, const DataType* Data00, const DataType* Data10, const DataType* Data01, const DataType* Data11, float FracX, float FracY)
|
|
{
|
|
*Output = FMath::RoundToInt(
|
|
FMath::Lerp(
|
|
FMath::Lerp((float)*Data00, (float)*Data10, FracX),
|
|
FMath::Lerp((float)*Data01, (float)*Data11, FracX),
|
|
FracY));
|
|
}
|
|
|
|
template<>
|
|
void BiLerpTextureData(FColor* Output, const FColor* Data00, const FColor* Data10, const FColor* Data01, const FColor* Data11, float FracX, float FracY)
|
|
{
|
|
Output->R = FMath::RoundToInt(
|
|
FMath::Lerp(
|
|
FMath::Lerp((float)Data00->R, (float)Data10->R, FracX),
|
|
FMath::Lerp((float)Data01->R, (float)Data11->R, FracX),
|
|
FracY));
|
|
Output->G = FMath::RoundToInt(
|
|
FMath::Lerp(
|
|
FMath::Lerp((float)Data00->G, (float)Data10->G, FracX),
|
|
FMath::Lerp((float)Data01->G, (float)Data11->G, FracX),
|
|
FracY));
|
|
Output->B = FMath::RoundToInt(
|
|
FMath::Lerp(
|
|
FMath::Lerp((float)Data00->B, (float)Data10->B, FracX),
|
|
FMath::Lerp((float)Data01->B, (float)Data11->B, FracX),
|
|
FracY));
|
|
Output->A = FMath::RoundToInt(
|
|
FMath::Lerp(
|
|
FMath::Lerp((float)Data00->A, (float)Data10->A, FracX),
|
|
FMath::Lerp((float)Data01->A, (float)Data11->A, FracX),
|
|
FracY));
|
|
}
|
|
|
|
template<typename DataType>
|
|
void AverageTexData(DataType* Output, const DataType* Data00, const DataType* Data10, const DataType* Data01, const DataType* Data11)
|
|
{
|
|
*Output = (((int32)(*Data00) + (int32)(*Data10) + (int32)(*Data01) + (int32)(*Data11)) >> 2);
|
|
}
|
|
|
|
template<>
|
|
void AverageTexData(FColor* Output, const FColor* Data00, const FColor* Data10, const FColor* Data01, const FColor* Data11)
|
|
{
|
|
Output->R = (((int32)Data00->R + (int32)Data10->R + (int32)Data01->R + (int32)Data11->R) >> 2);
|
|
Output->G = (((int32)Data00->G + (int32)Data10->G + (int32)Data01->G + (int32)Data11->G) >> 2);
|
|
Output->B = (((int32)Data00->B + (int32)Data10->B + (int32)Data01->B + (int32)Data11->B) >> 2);
|
|
Output->A = (((int32)Data00->A + (int32)Data10->A + (int32)Data01->A + (int32)Data11->A) >> 2);
|
|
}
|
|
|
|
};
|
|
|
|
template<typename DataType>
|
|
void ULandscapeComponent::UpdateMipsTempl(int32 InNumSubsections, int32 InSubsectionSizeQuads, UTexture2D* Texture, TArray<DataType*>& TextureMipData, int32 ComponentX1/*=0*/, int32 ComponentY1/*=0*/, int32 ComponentX2/*=MAX_int32*/, int32 ComponentY2/*=MAX_int32*/, struct FLandscapeTextureDataInfo* TextureDataInfo/*=nullptr*/)
|
|
{
|
|
int32 WeightmapSizeU = Texture->Source.GetSizeX();
|
|
int32 WeightmapSizeV = Texture->Source.GetSizeY();
|
|
|
|
// Find the maximum mip where each texel's data comes from just one subsection.
|
|
int32 MaxWholeSubsectionMip = FMath::FloorLog2(InSubsectionSizeQuads + 1) - 1;
|
|
|
|
// Update the mip where each texel's data comes from just one subsection.
|
|
for (int32 SubsectionY = 0; SubsectionY < InNumSubsections; SubsectionY++)
|
|
{
|
|
// Check if subsection is fully above or below the area we are interested in
|
|
if ((ComponentY2 < InSubsectionSizeQuads*SubsectionY) || // above
|
|
(ComponentY1 > InSubsectionSizeQuads*(SubsectionY + 1))) // below
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int32 SubsectionX = 0; SubsectionX < InNumSubsections; SubsectionX++)
|
|
{
|
|
// Check if subsection is fully to the left or right of the area we are interested in
|
|
if ((ComponentX2 < InSubsectionSizeQuads*SubsectionX) || // left
|
|
(ComponentX1 > InSubsectionSizeQuads*(SubsectionX + 1))) // right
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Area to update in previous mip level coords
|
|
int32 PrevMipSubX1 = ComponentX1 - InSubsectionSizeQuads*SubsectionX;
|
|
int32 PrevMipSubY1 = ComponentY1 - InSubsectionSizeQuads*SubsectionY;
|
|
int32 PrevMipSubX2 = ComponentX2 - InSubsectionSizeQuads*SubsectionX;
|
|
int32 PrevMipSubY2 = ComponentY2 - InSubsectionSizeQuads*SubsectionY;
|
|
|
|
int32 PrevMipSubsectionSizeQuads = InSubsectionSizeQuads;
|
|
float InvPrevMipSubsectionSizeQuads = 1.0f / (float)InSubsectionSizeQuads;
|
|
|
|
int32 PrevMipSizeU = WeightmapSizeU;
|
|
int32 PrevMipSizeV = WeightmapSizeV;
|
|
|
|
for (int32 Mip = 1; Mip <= MaxWholeSubsectionMip; Mip++)
|
|
{
|
|
int32 MipSizeU = WeightmapSizeU >> Mip;
|
|
int32 MipSizeV = WeightmapSizeV >> Mip;
|
|
|
|
int32 MipSubsectionSizeQuads = ((InSubsectionSizeQuads + 1) >> Mip) - 1;
|
|
float InvMipSubsectionSizeQuads = 1.0f / (float)MipSubsectionSizeQuads;
|
|
|
|
// Area to update in current mip level coords
|
|
int32 MipSubX1 = FMath::FloorToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubX1 * InvPrevMipSubsectionSizeQuads);
|
|
int32 MipSubY1 = FMath::FloorToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubY1 * InvPrevMipSubsectionSizeQuads);
|
|
int32 MipSubX2 = FMath::CeilToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubX2 * InvPrevMipSubsectionSizeQuads);
|
|
int32 MipSubY2 = FMath::CeilToInt((float)MipSubsectionSizeQuads * (float)PrevMipSubY2 * InvPrevMipSubsectionSizeQuads);
|
|
|
|
// Clamp area to update
|
|
int32 VertX1 = FMath::Clamp<int32>(MipSubX1, 0, MipSubsectionSizeQuads);
|
|
int32 VertY1 = FMath::Clamp<int32>(MipSubY1, 0, MipSubsectionSizeQuads);
|
|
int32 VertX2 = FMath::Clamp<int32>(MipSubX2, 0, MipSubsectionSizeQuads);
|
|
int32 VertY2 = FMath::Clamp<int32>(MipSubY2, 0, MipSubsectionSizeQuads);
|
|
|
|
for (int32 VertY = VertY1; VertY <= VertY2; VertY++)
|
|
{
|
|
for (int32 VertX = VertX1; VertX <= VertX2; VertX++)
|
|
{
|
|
// Convert VertX/Y into previous mip's coords
|
|
float PrevMipVertX = (float)PrevMipSubsectionSizeQuads * (float)VertX * InvMipSubsectionSizeQuads;
|
|
float PrevMipVertY = (float)PrevMipSubsectionSizeQuads * (float)VertY * InvMipSubsectionSizeQuads;
|
|
|
|
// X/Y of the vertex we're looking indexed into the texture data
|
|
int32 TexX = (MipSubsectionSizeQuads + 1) * SubsectionX + VertX;
|
|
int32 TexY = (MipSubsectionSizeQuads + 1) * SubsectionY + VertY;
|
|
|
|
float fPrevMipTexX = (float)((PrevMipSubsectionSizeQuads + 1) * SubsectionX) + PrevMipVertX;
|
|
float fPrevMipTexY = (float)((PrevMipSubsectionSizeQuads + 1) * SubsectionY) + PrevMipVertY;
|
|
|
|
int32 PrevMipTexX = FMath::FloorToInt(fPrevMipTexX);
|
|
float fPrevMipTexFracX = FMath::Fractional(fPrevMipTexX);
|
|
int32 PrevMipTexY = FMath::FloorToInt(fPrevMipTexY);
|
|
float fPrevMipTexFracY = FMath::Fractional(fPrevMipTexY);
|
|
|
|
check(TexX >= 0 && TexX < MipSizeU);
|
|
check(TexY >= 0 && TexY < MipSizeV);
|
|
check(PrevMipTexX >= 0 && PrevMipTexX < PrevMipSizeU);
|
|
check(PrevMipTexY >= 0 && PrevMipTexY < PrevMipSizeV);
|
|
|
|
int32 PrevMipTexX1 = FMath::Min<int32>(PrevMipTexX + 1, PrevMipSizeU - 1);
|
|
int32 PrevMipTexY1 = FMath::Min<int32>(PrevMipTexY + 1, PrevMipSizeV - 1);
|
|
|
|
DataType* TexData = &(TextureMipData[Mip])[TexX + TexY * MipSizeU];
|
|
DataType *PreMipTexData00 = &(TextureMipData[Mip - 1])[PrevMipTexX + PrevMipTexY * PrevMipSizeU];
|
|
DataType *PreMipTexData01 = &(TextureMipData[Mip - 1])[PrevMipTexX + PrevMipTexY1 * PrevMipSizeU];
|
|
DataType *PreMipTexData10 = &(TextureMipData[Mip - 1])[PrevMipTexX1 + PrevMipTexY * PrevMipSizeU];
|
|
DataType *PreMipTexData11 = &(TextureMipData[Mip - 1])[PrevMipTexX1 + PrevMipTexY1 * PrevMipSizeU];
|
|
|
|
// Lerp weightmap data
|
|
BiLerpTextureData<DataType>(TexData, PreMipTexData00, PreMipTexData10, PreMipTexData01, PreMipTexData11, fPrevMipTexFracX, fPrevMipTexFracY);
|
|
}
|
|
}
|
|
|
|
// Record the areas we updated
|
|
if (TextureDataInfo)
|
|
{
|
|
int32 TexX1 = (MipSubsectionSizeQuads + 1) * SubsectionX + VertX1;
|
|
int32 TexY1 = (MipSubsectionSizeQuads + 1) * SubsectionY + VertY1;
|
|
int32 TexX2 = (MipSubsectionSizeQuads + 1) * SubsectionX + VertX2;
|
|
int32 TexY2 = (MipSubsectionSizeQuads + 1) * SubsectionY + VertY2;
|
|
TextureDataInfo->AddMipUpdateRegion(Mip, TexX1, TexY1, TexX2, TexY2);
|
|
}
|
|
|
|
// Copy current mip values to prev as we move to the next mip.
|
|
PrevMipSubsectionSizeQuads = MipSubsectionSizeQuads;
|
|
InvPrevMipSubsectionSizeQuads = InvMipSubsectionSizeQuads;
|
|
|
|
PrevMipSizeU = MipSizeU;
|
|
PrevMipSizeV = MipSizeV;
|
|
|
|
// Use this mip's area as we move to the next mip
|
|
PrevMipSubX1 = MipSubX1;
|
|
PrevMipSubY1 = MipSubY1;
|
|
PrevMipSubX2 = MipSubX2;
|
|
PrevMipSubY2 = MipSubY2;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle mips that have texels from multiple subsections
|
|
// not valid weight data, so just average the texels of the previous mip.
|
|
for (int32 Mip = MaxWholeSubsectionMip + 1;; ++Mip)
|
|
{
|
|
int32 MipSubsectionSizeQuads = ((InSubsectionSizeQuads + 1) >> Mip) - 1;
|
|
checkSlow(MipSubsectionSizeQuads <= 0);
|
|
|
|
int32 MipSizeU = FMath::Max<int32>(WeightmapSizeU >> Mip, 1);
|
|
int32 MipSizeV = FMath::Max<int32>(WeightmapSizeV >> Mip, 1);
|
|
|
|
int32 PrevMipSizeU = FMath::Max<int32>(WeightmapSizeU >> (Mip - 1), 1);
|
|
int32 PrevMipSizeV = FMath::Max<int32>(WeightmapSizeV >> (Mip - 1), 1);
|
|
|
|
for (int32 Y = 0; Y < MipSizeV; Y++)
|
|
{
|
|
for (int32 X = 0; X < MipSizeU; X++)
|
|
{
|
|
DataType* TexData = &(TextureMipData[Mip])[X + Y * MipSizeU];
|
|
|
|
DataType *PreMipTexData00 = &(TextureMipData[Mip - 1])[(X * 2 + 0) + (Y * 2 + 0) * PrevMipSizeU];
|
|
DataType *PreMipTexData01 = &(TextureMipData[Mip - 1])[(X * 2 + 0) + (Y * 2 + 1) * PrevMipSizeU];
|
|
DataType *PreMipTexData10 = &(TextureMipData[Mip - 1])[(X * 2 + 1) + (Y * 2 + 0) * PrevMipSizeU];
|
|
DataType *PreMipTexData11 = &(TextureMipData[Mip - 1])[(X * 2 + 1) + (Y * 2 + 1) * PrevMipSizeU];
|
|
|
|
AverageTexData<DataType>(TexData, PreMipTexData00, PreMipTexData10, PreMipTexData01, PreMipTexData11);
|
|
}
|
|
}
|
|
|
|
if (TextureDataInfo)
|
|
{
|
|
// These mip sizes are small enough that we may as well just update the whole mip.
|
|
TextureDataInfo->AddMipUpdateRegion(Mip, 0, 0, MipSizeU - 1, MipSizeV - 1);
|
|
}
|
|
|
|
if (MipSizeU == 1 && MipSizeV == 1)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::UpdateWeightmapMips(int32 InNumSubsections, int32 InSubsectionSizeQuads, UTexture2D* WeightmapTexture, TArray<FColor*>& WeightmapTextureMipData, int32 ComponentX1/*=0*/, int32 ComponentY1/*=0*/, int32 ComponentX2/*=MAX_int32*/, int32 ComponentY2/*=MAX_int32*/, struct FLandscapeTextureDataInfo* TextureDataInfo/*=nullptr*/)
|
|
{
|
|
UpdateMipsTempl<FColor>(InNumSubsections, InSubsectionSizeQuads, WeightmapTexture, WeightmapTextureMipData, ComponentX1, ComponentY1, ComponentX2, ComponentY2, TextureDataInfo);
|
|
}
|
|
|
|
void ULandscapeComponent::UpdateDataMips(int32 InNumSubsections, int32 InSubsectionSizeQuads, UTexture2D* Texture, TArray<uint8*>& TextureMipData, int32 ComponentX1/*=0*/, int32 ComponentY1/*=0*/, int32 ComponentX2/*=MAX_int32*/, int32 ComponentY2/*=MAX_int32*/, struct FLandscapeTextureDataInfo* TextureDataInfo/*=nullptr*/)
|
|
{
|
|
UpdateMipsTempl<uint8>(InNumSubsections, InSubsectionSizeQuads, Texture, TextureMipData, ComponentX1, ComponentY1, ComponentX2, ComponentY2, TextureDataInfo);
|
|
}
|
|
|
|
float ULandscapeComponent::GetLayerWeightAtLocation(const FVector& InLocation, ULandscapeLayerInfoObject* LayerInfo, TArray<uint8>* LayerCache, bool bUseEditingWeightmap)
|
|
{
|
|
// Allocate and discard locally if no external cache is passed in.
|
|
TArray<uint8> LocalCache;
|
|
if (LayerCache == nullptr)
|
|
{
|
|
LayerCache = &LocalCache;
|
|
}
|
|
|
|
// Fill the cache if necessary
|
|
if (LayerCache->Num() == 0)
|
|
{
|
|
FLandscapeComponentDataInterface CDI(this);
|
|
if (!CDI.GetWeightmapTextureData(LayerInfo, *LayerCache, bUseEditingWeightmap))
|
|
{
|
|
// no data for this layer for this component.
|
|
return 0.0f;
|
|
}
|
|
}
|
|
|
|
// Find location
|
|
const FVector TestLocation = GetComponentToWorld().InverseTransformPosition(InLocation);
|
|
|
|
// Abort if the test location is not on this component
|
|
if (TestLocation.X < 0 || TestLocation.Y < 0 || TestLocation.X > ComponentSizeQuads || TestLocation.Y > ComponentSizeQuads)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
// Find data
|
|
int32 X1 = FMath::FloorToInt(TestLocation.X);
|
|
int32 Y1 = FMath::FloorToInt(TestLocation.Y);
|
|
int32 X2 = FMath::CeilToInt(TestLocation.X);
|
|
int32 Y2 = FMath::CeilToInt(TestLocation.Y);
|
|
|
|
int32 Stride = (SubsectionSizeQuads + 1) * NumSubsections;
|
|
|
|
// Min is to prevent the sampling of the final column from overflowing
|
|
int32 IdxX1 = FMath::Min<int32>(((X1 / SubsectionSizeQuads) * (SubsectionSizeQuads + 1)) + (X1 % SubsectionSizeQuads), Stride - 1);
|
|
int32 IdxY1 = FMath::Min<int32>(((Y1 / SubsectionSizeQuads) * (SubsectionSizeQuads + 1)) + (Y1 % SubsectionSizeQuads), Stride - 1);
|
|
int32 IdxX2 = FMath::Min<int32>(((X2 / SubsectionSizeQuads) * (SubsectionSizeQuads + 1)) + (X2 % SubsectionSizeQuads), Stride - 1);
|
|
int32 IdxY2 = FMath::Min<int32>(((Y2 / SubsectionSizeQuads) * (SubsectionSizeQuads + 1)) + (Y2 % SubsectionSizeQuads), Stride - 1);
|
|
|
|
// sample
|
|
float Sample11 = (float)((*LayerCache)[IdxX1 + Stride * IdxY1]) / 255.0f;
|
|
float Sample21 = (float)((*LayerCache)[IdxX2 + Stride * IdxY1]) / 255.0f;
|
|
float Sample12 = (float)((*LayerCache)[IdxX1 + Stride * IdxY2]) / 255.0f;
|
|
float Sample22 = (float)((*LayerCache)[IdxX2 + Stride * IdxY2]) / 255.0f;
|
|
|
|
float LerpX = FMath::Fractional(TestLocation.X);
|
|
float LerpY = FMath::Fractional(TestLocation.Y);
|
|
|
|
// Bilinear interpolate
|
|
return FMath::Lerp(
|
|
FMath::Lerp(Sample11, Sample21, LerpX),
|
|
FMath::Lerp(Sample12, Sample22, LerpX),
|
|
LerpY);
|
|
|
|
}
|
|
|
|
void ULandscapeComponent::GetComponentExtent(int32& MinX, int32& MinY, int32& MaxX, int32& MaxY) const
|
|
{
|
|
MinX = FMath::Min(SectionBaseX, MinX);
|
|
MinY = FMath::Min(SectionBaseY, MinY);
|
|
MaxX = FMath::Max(SectionBaseX + ComponentSizeQuads, MaxX);
|
|
MaxY = FMath::Max(SectionBaseY + ComponentSizeQuads, MaxY);
|
|
}
|
|
|
|
FIntRect ULandscapeComponent::GetComponentExtent() const
|
|
{
|
|
int32 MinX = MAX_int32, MinY = MAX_int32, MaxX = MIN_int32, MaxY = MIN_int32;
|
|
GetComponentExtent(MinX, MinY, MaxX, MaxY);
|
|
return FIntRect(MinX, MinY, MaxX, MaxY);
|
|
}
|
|
|
|
//
|
|
// ALandscape
|
|
//
|
|
bool ULandscapeInfo::AreAllComponentsRegistered() const
|
|
{
|
|
const TArray<ALandscapeProxy*>& 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<ILandscapeSplineInterface> 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<ULandscapeComponent*>& OutComponents, bool bOverlap) const
|
|
{
|
|
// Find component range for this block of data
|
|
// X2/Y2 Coordinates are "inclusive" max values
|
|
int32 ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2;
|
|
if (bOverlap)
|
|
{
|
|
ALandscape::CalcComponentIndicesOverlap(X1, Y1, X2, Y2, ComponentSizeQuads, ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2);
|
|
}
|
|
else
|
|
{
|
|
ALandscape::CalcComponentIndicesNoOverlap(X1, Y1, X2, Y2, ComponentSizeQuads, ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2);
|
|
}
|
|
|
|
for (int32 ComponentIndexY = ComponentIndexY1; ComponentIndexY <= ComponentIndexY2; ComponentIndexY++)
|
|
{
|
|
for (int32 ComponentIndexX = ComponentIndexX1; ComponentIndexX <= ComponentIndexX2; ComponentIndexX++)
|
|
{
|
|
ULandscapeComponent* Component = XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX, ComponentIndexY));
|
|
if (Component && !FLevelUtils::IsLevelLocked(Component->GetLandscapeProxy()->GetLevel()) && FLevelUtils::IsLevelVisible(Component->GetLandscapeProxy()->GetLevel()))
|
|
{
|
|
OutComponents.Add(Component);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// A struct to remember where we have spare texture channels.
|
|
struct FWeightmapTextureAllocation
|
|
{
|
|
int32 X;
|
|
int32 Y;
|
|
int32 ChannelsInUse;
|
|
UTexture2D* Texture;
|
|
FColor* TextureData;
|
|
|
|
FWeightmapTextureAllocation(int32 InX, int32 InY, int32 InChannels, UTexture2D* InTexture, FColor* InTextureData)
|
|
: X(InX)
|
|
, Y(InY)
|
|
, ChannelsInUse(InChannels)
|
|
, Texture(InTexture)
|
|
, TextureData(InTextureData)
|
|
{}
|
|
};
|
|
|
|
// A struct to hold the info about each texture chunk of the total heightmap
|
|
struct FHeightmapInfo
|
|
{
|
|
int32 HeightmapSizeU;
|
|
int32 HeightmapSizeV;
|
|
UTexture2D* HeightmapTexture;
|
|
TArray<FColor*> HeightmapTextureMipData;
|
|
};
|
|
|
|
TArray<FName> ALandscapeProxy::GetLayersFromMaterial(UMaterialInterface* MaterialInterface)
|
|
{
|
|
TArray<FName> Result;
|
|
|
|
if (MaterialInterface)
|
|
{
|
|
TArray<FMaterialParameterInfo> OutParameterInfo;
|
|
TArray<FGuid> Guids;
|
|
if (UMaterialInstance* Instance = Cast<UMaterialInstance>(MaterialInterface))
|
|
{
|
|
Instance->GetAllParameterInfo<UMaterialExpressionLandscapeLayerBlend>(OutParameterInfo, Guids);
|
|
Instance->GetAllParameterInfo<UMaterialExpressionLandscapeLayerWeight>(OutParameterInfo, Guids);
|
|
Instance->GetAllParameterInfo<UMaterialExpressionLandscapeLayerSwitch>(OutParameterInfo, Guids);
|
|
Instance->GetAllParameterInfo<UMaterialExpressionLandscapeLayerSample>(OutParameterInfo, Guids);
|
|
}
|
|
else if (UMaterial* Material = MaterialInterface->GetMaterial())
|
|
{
|
|
Material->GetAllParameterInfo<UMaterialExpressionLandscapeLayerBlend>(OutParameterInfo, Guids);
|
|
Material->GetAllParameterInfo<UMaterialExpressionLandscapeLayerWeight>(OutParameterInfo, Guids);
|
|
Material->GetAllParameterInfo<UMaterialExpressionLandscapeLayerSwitch>(OutParameterInfo, Guids);
|
|
Material->GetAllParameterInfo<UMaterialExpressionLandscapeLayerSample>(OutParameterInfo, Guids);
|
|
}
|
|
|
|
for (const FMaterialParameterInfo& ParameterInfo : OutParameterInfo)
|
|
{
|
|
Result.AddUnique(ParameterInfo.Name);
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
TArray<FName> 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<ULandscapeLayerInfoObject>(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<int32>(Y,0,VertsY) * VertsX + FMath::Clamp<int32>(X,0,VertsX) ])
|
|
ENGINE_API extern bool GDisableAutomaticTextureMaterialUpdateDependencies;
|
|
|
|
LANDSCAPE_API void ALandscapeProxy::Import(const FGuid& InGuid, int32 InMinX, int32 InMinY, int32 InMaxX, int32 InMaxY, int32 InNumSubsections, int32 InSubsectionSizeQuads, const TMap<FGuid, TArray<uint16>>& InImportHeightData,
|
|
const TCHAR* const InHeightmapFileName, const TMap<FGuid, TArray<FLandscapeImportLayerInfo>>& InImportMaterialLayerInfos, ELandscapeImportAlphamapType InImportMaterialLayerType, const TArray<FLandscapeLayer>* InImportLayers)
|
|
{
|
|
check(InGuid.IsValid());
|
|
check(InImportHeightData.Num() == InImportMaterialLayerInfos.Num());
|
|
|
|
check(CanHaveLayersContent() || InImportLayers == nullptr);
|
|
|
|
FScopedSlowTask SlowTask(2, LOCTEXT("BeingImportingLandscapeTask", "Importing Landscape"));
|
|
SlowTask.MakeDialog();
|
|
|
|
SlowTask.EnterProgressFrame(1.0f);
|
|
|
|
const int32 VertsX = InMaxX - InMinX + 1;
|
|
const int32 VertsY = InMaxY - InMinY + 1;
|
|
|
|
ComponentSizeQuads = InNumSubsections * InSubsectionSizeQuads;
|
|
NumSubsections = InNumSubsections;
|
|
SubsectionSizeQuads = InSubsectionSizeQuads;
|
|
LandscapeGuid = InGuid;
|
|
|
|
Modify();
|
|
|
|
const int32 NumPatchesX = (VertsX - 1);
|
|
const int32 NumPatchesY = (VertsY - 1);
|
|
|
|
const int32 NumComponentsX = NumPatchesX / ComponentSizeQuads;
|
|
const int32 NumComponentsY = NumPatchesY / ComponentSizeQuads;
|
|
|
|
// currently only support importing into a new/blank landscape actor/proxy
|
|
check(LandscapeComponents.Num() == 0);
|
|
LandscapeComponents.Empty(NumComponentsX * NumComponentsY);
|
|
|
|
for (int32 Y = 0; Y < NumComponentsY; Y++)
|
|
{
|
|
for (int32 X = 0; X < NumComponentsX; X++)
|
|
{
|
|
const int32 BaseX = InMinX + X * ComponentSizeQuads;
|
|
const int32 BaseY = InMinY + Y * ComponentSizeQuads;
|
|
|
|
ULandscapeComponent* LandscapeComponent = NewObject<ULandscapeComponent>(this, NAME_None, RF_Transactional);
|
|
LandscapeComponent->Init(BaseX, BaseY, ComponentSizeQuads, NumSubsections, SubsectionSizeQuads);
|
|
}
|
|
}
|
|
|
|
// Ensure that we don't pack so many heightmaps into a texture that their lowest LOD isn't guaranteed to be resident
|
|
#define MAX_HEIGHTMAP_TEXTURE_SIZE 512
|
|
const int32 ComponentSizeVerts = NumSubsections * (SubsectionSizeQuads + 1);
|
|
const int32 ComponentsPerHeightmap = FMath::Min(MAX_HEIGHTMAP_TEXTURE_SIZE / ComponentSizeVerts, 1 << (UTexture2D::GetStaticMinTextureResidentMipCount() - 2));
|
|
check(ComponentsPerHeightmap > 0);
|
|
|
|
// Count how many heightmaps we need and the X dimension of the final heightmap
|
|
int32 NumHeightmapsX = 1;
|
|
int32 FinalComponentsX = NumComponentsX;
|
|
while (FinalComponentsX > ComponentsPerHeightmap)
|
|
{
|
|
FinalComponentsX -= ComponentsPerHeightmap;
|
|
NumHeightmapsX++;
|
|
}
|
|
// Count how many heightmaps we need and the Y dimension of the final heightmap
|
|
int32 NumHeightmapsY = 1;
|
|
int32 FinalComponentsY = NumComponentsY;
|
|
while (FinalComponentsY > ComponentsPerHeightmap)
|
|
{
|
|
FinalComponentsY -= ComponentsPerHeightmap;
|
|
NumHeightmapsY++;
|
|
}
|
|
|
|
TArray<FHeightmapInfo> HeightmapInfos;
|
|
|
|
for (int32 HmY = 0; HmY < NumHeightmapsY; HmY++)
|
|
{
|
|
for (int32 HmX = 0; HmX < NumHeightmapsX; HmX++)
|
|
{
|
|
FHeightmapInfo& HeightmapInfo = HeightmapInfos[HeightmapInfos.AddZeroed()];
|
|
|
|
// make sure the heightmap UVs are powers of two.
|
|
HeightmapInfo.HeightmapSizeU = (1 << FMath::CeilLogTwo(((HmX == NumHeightmapsX - 1) ? FinalComponentsX : ComponentsPerHeightmap) * ComponentSizeVerts));
|
|
HeightmapInfo.HeightmapSizeV = (1 << FMath::CeilLogTwo(((HmY == NumHeightmapsY - 1) ? FinalComponentsY : ComponentsPerHeightmap) * ComponentSizeVerts));
|
|
|
|
// Construct the heightmap textures
|
|
HeightmapInfo.HeightmapTexture = CreateLandscapeTexture(HeightmapInfo.HeightmapSizeU, HeightmapInfo.HeightmapSizeV, TEXTUREGROUP_Terrain_Heightmap, TSF_BGRA8);
|
|
|
|
int32 MipSubsectionSizeQuads = SubsectionSizeQuads;
|
|
int32 MipSizeU = HeightmapInfo.HeightmapSizeU;
|
|
int32 MipSizeV = HeightmapInfo.HeightmapSizeV;
|
|
while (MipSizeU > 1 && MipSizeV > 1 && MipSubsectionSizeQuads >= 1)
|
|
{
|
|
int32 MipIndex = HeightmapInfo.HeightmapTextureMipData.Num();
|
|
FColor* HeightmapTextureData = (FColor*)HeightmapInfo.HeightmapTexture->Source.LockMip(MipIndex);
|
|
FMemory::Memzero(HeightmapTextureData, MipSizeU*MipSizeV*sizeof(FColor));
|
|
HeightmapInfo.HeightmapTextureMipData.Add(HeightmapTextureData);
|
|
|
|
MipSizeU >>= 1;
|
|
MipSizeV >>= 1;
|
|
|
|
MipSubsectionSizeQuads = ((MipSubsectionSizeQuads + 1) >> 1) - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
const FVector DrawScale3D = GetRootComponent()->GetRelativeScale3D();
|
|
|
|
// layer to import data (Final or 1st layer)
|
|
const FGuid FinalLayerGuid = FGuid();
|
|
const TArray<uint16>& HeightData = InImportHeightData.FindChecked(FinalLayerGuid);
|
|
const TArray<FLandscapeImportLayerInfo>& ImportLayerInfos = InImportMaterialLayerInfos.FindChecked(FinalLayerGuid);
|
|
|
|
// Calculate the normals for each of the two triangles per quad.
|
|
TArray<FVector> VertexNormals;
|
|
VertexNormals.AddZeroed(VertsX * VertsY);
|
|
for (int32 QuadY = 0; QuadY < NumPatchesY; QuadY++)
|
|
{
|
|
for (int32 QuadX = 0; QuadX < NumPatchesX; QuadX++)
|
|
{
|
|
const FVector Vert00 = FVector(0.0f, 0.0f, ((float)HEIGHTDATA(QuadX + 0, QuadY + 0) - 32768.0f)*LANDSCAPE_ZSCALE) * DrawScale3D;
|
|
const FVector Vert01 = FVector(0.0f, 1.0f, ((float)HEIGHTDATA(QuadX + 0, QuadY + 1) - 32768.0f)*LANDSCAPE_ZSCALE) * DrawScale3D;
|
|
const FVector Vert10 = FVector(1.0f, 0.0f, ((float)HEIGHTDATA(QuadX + 1, QuadY + 0) - 32768.0f)*LANDSCAPE_ZSCALE) * DrawScale3D;
|
|
const FVector Vert11 = FVector(1.0f, 1.0f, ((float)HEIGHTDATA(QuadX + 1, QuadY + 1) - 32768.0f)*LANDSCAPE_ZSCALE) * DrawScale3D;
|
|
|
|
const FVector FaceNormal1 = ((Vert00 - Vert10) ^ (Vert10 - Vert11)).GetSafeNormal();
|
|
const FVector FaceNormal2 = ((Vert11 - Vert01) ^ (Vert01 - Vert00)).GetSafeNormal();
|
|
|
|
// contribute to the vertex normals.
|
|
VertexNormals[(QuadX + 1 + VertsX * (QuadY + 0))] += FaceNormal1;
|
|
VertexNormals[(QuadX + 0 + VertsX * (QuadY + 1))] += FaceNormal2;
|
|
VertexNormals[(QuadX + 0 + VertsX * (QuadY + 0))] += FaceNormal1 + FaceNormal2;
|
|
VertexNormals[(QuadX + 1 + VertsX * (QuadY + 1))] += FaceNormal1 + FaceNormal2;
|
|
}
|
|
}
|
|
|
|
// Weight values for each layer for each component.
|
|
TArray<TArray<TArray<uint8>>> ComponentWeightValues;
|
|
ComponentWeightValues.AddZeroed(NumComponentsX * NumComponentsY);
|
|
|
|
for (int32 ComponentY = 0; ComponentY < NumComponentsY; ComponentY++)
|
|
{
|
|
for (int32 ComponentX = 0; ComponentX < NumComponentsX; ComponentX++)
|
|
{
|
|
ULandscapeComponent* const LandscapeComponent = LandscapeComponents[ComponentX + ComponentY*NumComponentsX];
|
|
TArray<TArray<uint8>>& WeightValues = ComponentWeightValues[ComponentX + ComponentY*NumComponentsX];
|
|
|
|
// Import alphamap data into local array and check for unused layers for this component.
|
|
TArray<FLandscapeComponentAlphaInfo, TInlineAllocator<16>> EditingAlphaLayerData;
|
|
for (int32 LayerIndex = 0; LayerIndex < ImportLayerInfos.Num(); LayerIndex++)
|
|
{
|
|
FLandscapeComponentAlphaInfo* NewAlphaInfo = new(EditingAlphaLayerData) FLandscapeComponentAlphaInfo(LandscapeComponent, LayerIndex);
|
|
|
|
if (ImportLayerInfos[LayerIndex].LayerData.Num())
|
|
{
|
|
for (int32 AlphaY = 0; AlphaY <= LandscapeComponent->ComponentSizeQuads; AlphaY++)
|
|
{
|
|
const uint8* const OldAlphaRowStart = &ImportLayerInfos[LayerIndex].LayerData[(AlphaY + LandscapeComponent->GetSectionBase().Y - InMinY) * VertsX + (LandscapeComponent->GetSectionBase().X - InMinX)];
|
|
uint8* const NewAlphaRowStart = &NewAlphaInfo->AlphaValues[AlphaY * (LandscapeComponent->ComponentSizeQuads + 1)];
|
|
FMemory::Memcpy(NewAlphaRowStart, OldAlphaRowStart, LandscapeComponent->ComponentSizeQuads + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int32 AlphaMapIndex = 0; AlphaMapIndex < EditingAlphaLayerData.Num(); AlphaMapIndex++)
|
|
{
|
|
if (EditingAlphaLayerData[AlphaMapIndex].IsLayerAllZero())
|
|
{
|
|
EditingAlphaLayerData.RemoveAt(AlphaMapIndex);
|
|
AlphaMapIndex--;
|
|
}
|
|
}
|
|
|
|
|
|
UE_LOG(LogLandscape, Log, TEXT("%s needs %d alphamaps"), *LandscapeComponent->GetName(), EditingAlphaLayerData.Num());
|
|
|
|
TArray<FWeightmapLayerAllocationInfo>& ComponentWeightmapLayerAllocations = LandscapeComponent->GetWeightmapLayerAllocations();
|
|
|
|
// Calculate weightmap weights for this component
|
|
WeightValues.Empty(EditingAlphaLayerData.Num());
|
|
WeightValues.AddZeroed(EditingAlphaLayerData.Num());
|
|
ComponentWeightmapLayerAllocations.Empty(EditingAlphaLayerData.Num());
|
|
|
|
TArray<bool, TInlineAllocator<16>> IsNoBlendArray;
|
|
IsNoBlendArray.Empty(EditingAlphaLayerData.Num());
|
|
IsNoBlendArray.AddZeroed(EditingAlphaLayerData.Num());
|
|
|
|
for (int32 WeightLayerIndex = 0; WeightLayerIndex < WeightValues.Num(); WeightLayerIndex++)
|
|
{
|
|
// Lookup the original layer name
|
|
WeightValues[WeightLayerIndex] = EditingAlphaLayerData[WeightLayerIndex].AlphaValues;
|
|
new(ComponentWeightmapLayerAllocations) FWeightmapLayerAllocationInfo(ImportLayerInfos[EditingAlphaLayerData[WeightLayerIndex].LayerIndex].LayerInfo);
|
|
IsNoBlendArray[WeightLayerIndex] = ImportLayerInfos[EditingAlphaLayerData[WeightLayerIndex].LayerIndex].LayerInfo->bNoWeightBlend;
|
|
}
|
|
|
|
// Discard the temporary alpha data
|
|
EditingAlphaLayerData.Empty();
|
|
|
|
if (InImportMaterialLayerType == ELandscapeImportAlphamapType::Layered)
|
|
{
|
|
// For each layer...
|
|
for (int32 WeightLayerIndex = WeightValues.Num() - 1; WeightLayerIndex >= 0; WeightLayerIndex--)
|
|
{
|
|
// ... multiply all lower layers'...
|
|
for (int32 BelowWeightLayerIndex = WeightLayerIndex - 1; BelowWeightLayerIndex >= 0; BelowWeightLayerIndex--)
|
|
{
|
|
int32 TotalWeight = 0;
|
|
|
|
if (IsNoBlendArray[BelowWeightLayerIndex])
|
|
{
|
|
continue; // skip no blend
|
|
}
|
|
|
|
// ... values by...
|
|
for (int32 Idx = 0; Idx < WeightValues[WeightLayerIndex].Num(); Idx++)
|
|
{
|
|
// ... one-minus the current layer's values
|
|
int32 NewValue = (int32)WeightValues[BelowWeightLayerIndex][Idx] * (int32)(255 - WeightValues[WeightLayerIndex][Idx]) / 255;
|
|
WeightValues[BelowWeightLayerIndex][Idx] = (uint8)NewValue;
|
|
TotalWeight += NewValue;
|
|
}
|
|
|
|
if (TotalWeight == 0)
|
|
{
|
|
// Remove the layer as it has no contribution
|
|
WeightValues.RemoveAt(BelowWeightLayerIndex);
|
|
ComponentWeightmapLayerAllocations.RemoveAt(BelowWeightLayerIndex);
|
|
IsNoBlendArray.RemoveAt(BelowWeightLayerIndex);
|
|
|
|
// The current layer has been re-numbered
|
|
WeightLayerIndex--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Weight normalization for total should be 255...
|
|
if (WeightValues.Num())
|
|
{
|
|
for (int32 Idx = 0; Idx < WeightValues[0].Num(); Idx++)
|
|
{
|
|
int32 TotalWeight = 0;
|
|
int32 MaxLayerIdx = -1;
|
|
int32 MaxWeight = INT_MIN;
|
|
|
|
for (int32 WeightLayerIndex = 0; WeightLayerIndex < WeightValues.Num(); WeightLayerIndex++)
|
|
{
|
|
if (!IsNoBlendArray[WeightLayerIndex])
|
|
{
|
|
int32 Weight = WeightValues[WeightLayerIndex][Idx];
|
|
TotalWeight += Weight;
|
|
if (MaxWeight < Weight)
|
|
{
|
|
MaxWeight = Weight;
|
|
MaxLayerIdx = WeightLayerIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (TotalWeight > 0 && TotalWeight != 255)
|
|
{
|
|
// normalization...
|
|
float Factor = 255.0f / TotalWeight;
|
|
TotalWeight = 0;
|
|
for (int32 WeightLayerIndex = 0; WeightLayerIndex < WeightValues.Num(); WeightLayerIndex++)
|
|
{
|
|
if (!IsNoBlendArray[WeightLayerIndex])
|
|
{
|
|
WeightValues[WeightLayerIndex][Idx] = (uint8)(Factor * WeightValues[WeightLayerIndex][Idx]);
|
|
TotalWeight += WeightValues[WeightLayerIndex][Idx];
|
|
}
|
|
}
|
|
|
|
if (255 - TotalWeight && MaxLayerIdx >= 0)
|
|
{
|
|
WeightValues[MaxLayerIdx][Idx] += 255 - TotalWeight;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remember where we have spare texture channels.
|
|
TArray<FWeightmapTextureAllocation> TextureAllocations;
|
|
|
|
for (int32 ComponentY = 0; ComponentY < NumComponentsY; ComponentY++)
|
|
{
|
|
const int32 HmY = ComponentY / ComponentsPerHeightmap;
|
|
const int32 HeightmapOffsetY = (ComponentY - ComponentsPerHeightmap*HmY) * NumSubsections * (SubsectionSizeQuads + 1);
|
|
|
|
for (int32 ComponentX = 0; ComponentX < NumComponentsX; ComponentX++)
|
|
{
|
|
const int32 HmX = ComponentX / ComponentsPerHeightmap;
|
|
const FHeightmapInfo& HeightmapInfo = HeightmapInfos[HmX + HmY * NumHeightmapsX];
|
|
|
|
ULandscapeComponent* LandscapeComponent = LandscapeComponents[ComponentX + ComponentY*NumComponentsX];
|
|
|
|
// Lookup array of weight values for this component.
|
|
const TArray<TArray<uint8>>& WeightValues = ComponentWeightValues[ComponentX + ComponentY*NumComponentsX];
|
|
|
|
// Heightmap offsets
|
|
const int32 HeightmapOffsetX = (ComponentX - ComponentsPerHeightmap*HmX) * NumSubsections * (SubsectionSizeQuads + 1);
|
|
|
|
LandscapeComponent->HeightmapScaleBias = FVector4(1.0f / (float)HeightmapInfo.HeightmapSizeU, 1.0f / (float)HeightmapInfo.HeightmapSizeV, (float)((HeightmapOffsetX)) / (float)HeightmapInfo.HeightmapSizeU, ((float)(HeightmapOffsetY)) / (float)HeightmapInfo.HeightmapSizeV);
|
|
LandscapeComponent->SetHeightmap(HeightmapInfo.HeightmapTexture);
|
|
|
|
// Weightmap is sized the same as the component
|
|
const int32 WeightmapSize = (SubsectionSizeQuads + 1) * NumSubsections;
|
|
// Should be power of two
|
|
check(FMath::IsPowerOfTwo(WeightmapSize));
|
|
|
|
LandscapeComponent->WeightmapScaleBias = FVector4(1.0f / (float)WeightmapSize, 1.0f / (float)WeightmapSize, 0.5f / (float)WeightmapSize, 0.5f / (float)WeightmapSize);
|
|
LandscapeComponent->WeightmapSubsectionOffset = (float)(SubsectionSizeQuads + 1) / (float)WeightmapSize;
|
|
|
|
// Pointers to the texture data where we'll store each layer. Stride is 4 (FColor)
|
|
TArray<uint8*> WeightmapTextureDataPointers;
|
|
|
|
UE_LOG(LogLandscape, 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<FWeightmapLayerAllocationInfo>& ComponentWeightmapLayerAllocations = LandscapeComponent->GetWeightmapLayerAllocations();
|
|
TArray<UTexture2D*>& ComponentWeightmapTextures = LandscapeComponent->GetWeightmapTextures();
|
|
TArray<ULandscapeWeightmapUsage*>& ComponentWeightmapTexturesUsage = LandscapeComponent->GetWeightmapTexturesUsage();
|
|
|
|
if (BestAllocationIndex != -1)
|
|
{
|
|
FWeightmapTextureAllocation& Allocation = TextureAllocations[BestAllocationIndex];
|
|
ULandscapeWeightmapUsage* WeightmapUsage = WeightmapUsageMap.FindChecked(Allocation.Texture);
|
|
ComponentWeightmapTexturesUsage.Add(WeightmapUsage);
|
|
|
|
UE_LOG(LogLandscape, 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<int32>(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<UTexture2D*> PendingTexturePlatformDataCreation;
|
|
|
|
// Unlock the weightmaps' base mips
|
|
for (int32 AllocationIndex = 0; AllocationIndex < TextureAllocations.Num(); AllocationIndex++)
|
|
{
|
|
UTexture2D* const WeightmapTexture = TextureAllocations[AllocationIndex].Texture;
|
|
FColor* const BaseMipData = TextureAllocations[AllocationIndex].TextureData;
|
|
|
|
// Generate mips for weightmaps
|
|
ULandscapeComponent::GenerateWeightmapMips(NumSubsections, SubsectionSizeQuads, WeightmapTexture, BaseMipData);
|
|
|
|
WeightmapTexture->Source.UnlockMip(0);
|
|
|
|
WeightmapTexture->BeginCachePlatformData();
|
|
WeightmapTexture->ClearAllCachedCookedPlatformData();
|
|
PendingTexturePlatformDataCreation.Add(WeightmapTexture);
|
|
}
|
|
|
|
// Generate mipmaps for the components, and create the collision components
|
|
for (int32 ComponentY = 0; ComponentY < NumComponentsY; ComponentY++)
|
|
{
|
|
for (int32 ComponentX = 0; ComponentX < NumComponentsX; ComponentX++)
|
|
{
|
|
const int32 HmX = ComponentX / ComponentsPerHeightmap;
|
|
const int32 HmY = ComponentY / ComponentsPerHeightmap;
|
|
FHeightmapInfo& HeightmapInfo = HeightmapInfos[HmX + HmY * NumHeightmapsX];
|
|
|
|
ULandscapeComponent* LandscapeComponent = LandscapeComponents[ComponentX + ComponentY*NumComponentsX];
|
|
LandscapeComponent->GenerateHeightmapMips(HeightmapInfo.HeightmapTextureMipData, ComponentX == NumComponentsX - 1 ? MAX_int32 : 0, ComponentY == NumComponentsY - 1 ? MAX_int32 : 0);
|
|
LandscapeComponent->UpdateCollisionHeightData(
|
|
HeightmapInfo.HeightmapTextureMipData[LandscapeComponent->CollisionMipLevel],
|
|
LandscapeComponent->SimpleCollisionMipLevel > LandscapeComponent->CollisionMipLevel ? HeightmapInfo.HeightmapTextureMipData[LandscapeComponent->SimpleCollisionMipLevel] : nullptr);
|
|
LandscapeComponent->UpdateCollisionLayerData();
|
|
}
|
|
}
|
|
|
|
for (int32 HmIdx = 0; HmIdx < HeightmapInfos.Num(); HmIdx++)
|
|
{
|
|
FHeightmapInfo& HeightmapInfo = HeightmapInfos[HmIdx];
|
|
|
|
// Add remaining mips down to 1x1 to heightmap texture. These do not represent quads and are just a simple averages of the previous mipmaps.
|
|
// These mips are not used for sampling in the vertex shader but could be sampled in the pixel shader.
|
|
int32 Mip = HeightmapInfo.HeightmapTextureMipData.Num();
|
|
int32 MipSizeU = (HeightmapInfo.HeightmapTexture->Source.GetSizeX()) >> Mip;
|
|
int32 MipSizeV = (HeightmapInfo.HeightmapTexture->Source.GetSizeY()) >> Mip;
|
|
while (MipSizeU > 1 && MipSizeV > 1)
|
|
{
|
|
HeightmapInfo.HeightmapTextureMipData.Add((FColor*)HeightmapInfo.HeightmapTexture->Source.LockMip(Mip));
|
|
const int32 PrevMipSizeU = (HeightmapInfo.HeightmapTexture->Source.GetSizeX()) >> (Mip - 1);
|
|
const int32 PrevMipSizeV = (HeightmapInfo.HeightmapTexture->Source.GetSizeY()) >> (Mip - 1);
|
|
|
|
for (int32 Y = 0; Y < MipSizeV; Y++)
|
|
{
|
|
for (int32 X = 0; X < MipSizeU; X++)
|
|
{
|
|
FColor* const TexData = &(HeightmapInfo.HeightmapTextureMipData[Mip])[X + Y * MipSizeU];
|
|
|
|
const FColor* const PreMipTexData00 = &(HeightmapInfo.HeightmapTextureMipData[Mip - 1])[(X * 2 + 0) + (Y * 2 + 0) * PrevMipSizeU];
|
|
const FColor* const PreMipTexData01 = &(HeightmapInfo.HeightmapTextureMipData[Mip - 1])[(X * 2 + 0) + (Y * 2 + 1) * PrevMipSizeU];
|
|
const FColor* const PreMipTexData10 = &(HeightmapInfo.HeightmapTextureMipData[Mip - 1])[(X * 2 + 1) + (Y * 2 + 0) * PrevMipSizeU];
|
|
const FColor* const PreMipTexData11 = &(HeightmapInfo.HeightmapTextureMipData[Mip - 1])[(X * 2 + 1) + (Y * 2 + 1) * PrevMipSizeU];
|
|
|
|
TexData->R = (((int32)PreMipTexData00->R + (int32)PreMipTexData01->R + (int32)PreMipTexData10->R + (int32)PreMipTexData11->R) >> 2);
|
|
TexData->G = (((int32)PreMipTexData00->G + (int32)PreMipTexData01->G + (int32)PreMipTexData10->G + (int32)PreMipTexData11->G) >> 2);
|
|
TexData->B = (((int32)PreMipTexData00->B + (int32)PreMipTexData01->B + (int32)PreMipTexData10->B + (int32)PreMipTexData11->B) >> 2);
|
|
TexData->A = (((int32)PreMipTexData00->A + (int32)PreMipTexData01->A + (int32)PreMipTexData10->A + (int32)PreMipTexData11->A) >> 2);
|
|
}
|
|
}
|
|
Mip++;
|
|
MipSizeU >>= 1;
|
|
MipSizeV >>= 1;
|
|
}
|
|
|
|
for (int32 i = 0; i < HeightmapInfo.HeightmapTextureMipData.Num(); i++)
|
|
{
|
|
HeightmapInfo.HeightmapTexture->Source.UnlockMip(i);
|
|
}
|
|
|
|
HeightmapInfo.HeightmapTexture->BeginCachePlatformData();
|
|
HeightmapInfo.HeightmapTexture->ClearAllCachedCookedPlatformData();
|
|
PendingTexturePlatformDataCreation.Add(HeightmapInfo.HeightmapTexture);
|
|
}
|
|
|
|
// Build a list of all unique materials the landscape uses
|
|
TArray<UMaterialInterface*> LandscapeMaterials;
|
|
|
|
for (ULandscapeComponent* Component : LandscapeComponents)
|
|
{
|
|
int8 MaxLOD = FMath::CeilLogTwo(Component->SubsectionSizeQuads + 1) - 1;
|
|
|
|
for (int8 LODIndex = 0; LODIndex < MaxLOD; ++LODIndex)
|
|
{
|
|
UMaterialInterface* Material = Component->GetLandscapeMaterial(LODIndex);
|
|
LandscapeMaterials.AddUnique(Material);
|
|
}
|
|
}
|
|
|
|
// Update all materials and recreate render state of all landscape components
|
|
TArray<FComponentRecreateRenderStateContext> RecreateRenderStateContexts;
|
|
|
|
SlowTask.EnterProgressFrame(1.0f);
|
|
|
|
{
|
|
// We disable automatic material update context, to manage it manually
|
|
GDisableAutomaticTextureMaterialUpdateDependencies = true;
|
|
|
|
FMaterialUpdateContext UpdateContext(FMaterialUpdateContext::EOptions::Default & ~FMaterialUpdateContext::EOptions::RecreateRenderStates);
|
|
|
|
for (UTexture2D* Texture : PendingTexturePlatformDataCreation)
|
|
{
|
|
Texture->FinishCachePlatformData();
|
|
Texture->PostEditChange();
|
|
|
|
TSet<UMaterial*> BaseMaterialsThatUseThisTexture;
|
|
|
|
for (UMaterialInterface* MaterialInterface : LandscapeMaterials)
|
|
{
|
|
if (DoesMaterialUseTexture(MaterialInterface, Texture))
|
|
{
|
|
UMaterial* Material = MaterialInterface->GetMaterial();
|
|
bool MaterialAlreadyCompute = false;
|
|
BaseMaterialsThatUseThisTexture.Add(Material, &MaterialAlreadyCompute);
|
|
|
|
if (!MaterialAlreadyCompute)
|
|
{
|
|
if (Material->IsTextureForceRecompileCacheRessource(Texture))
|
|
{
|
|
UpdateContext.AddMaterial(Material);
|
|
Material->UpdateMaterialShaderCacheAndTextureReferences();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
GDisableAutomaticTextureMaterialUpdateDependencies = false;
|
|
|
|
// Update MaterialInstances (must be done after textures are fully initialized)
|
|
UpdateAllComponentMaterialInstances(UpdateContext, RecreateRenderStateContexts);
|
|
}
|
|
|
|
// Recreate the render state for this component, needed to update the static drawlist which has cached the MaterialRenderProxies
|
|
// Must be after the FMaterialUpdateContext is destroyed
|
|
RecreateRenderStateContexts.Reset();
|
|
|
|
// Create and initialize landscape info object
|
|
ULandscapeInfo* LandscapeInfo = CreateLandscapeInfo();
|
|
|
|
if (CanHaveLayersContent())
|
|
{
|
|
// Create the default layer first
|
|
ALandscape* LandscapeActor = GetLandscapeActor();
|
|
check(LandscapeActor != nullptr);
|
|
if (LandscapeActor->GetLayerCount() == 0 && InImportLayers == nullptr)
|
|
{
|
|
LandscapeActor->CreateDefaultLayer();
|
|
}
|
|
|
|
// Components need to be registered to be able to import the layer content and we will remove them if they should have not been visible
|
|
bool ShouldComponentBeRegistered = GetLevel()->bIsVisible;
|
|
RegisterAllComponents();
|
|
|
|
TSet<ULandscapeComponent*> ComponentsToProcess;
|
|
|
|
struct FLayerImportSettings
|
|
{
|
|
FGuid SourceLayerGuid;
|
|
FGuid DestinationLayerGuid;
|
|
};
|
|
|
|
TArray<FLayerImportSettings> LayerImportSettings;
|
|
|
|
// Only create Layers on main Landscape
|
|
if (LandscapeActor == this && InImportLayers != nullptr)
|
|
{
|
|
for (const FLandscapeLayer& OldLayer : *InImportLayers)
|
|
{
|
|
FLandscapeLayer* NewLayer = LandscapeActor->DuplicateLayerAndMoveBrushes(OldLayer);
|
|
check(NewLayer != nullptr);
|
|
|
|
FLayerImportSettings ImportSettings;
|
|
ImportSettings.SourceLayerGuid = OldLayer.Guid;
|
|
ImportSettings.DestinationLayerGuid = NewLayer->Guid;
|
|
LayerImportSettings.Add(ImportSettings);
|
|
}
|
|
|
|
LandscapeInfo->GetComponentsInRegion(InMinX, InMinY, InMaxX, InMaxY, ComponentsToProcess);
|
|
}
|
|
else
|
|
{
|
|
// In the case of a streaming proxy, we will generate the layer data for each components that the proxy hold so no need of the grid min/max to calculate the components to update
|
|
if (LandscapeActor != this)
|
|
{
|
|
LandscapeActor->AddLayersToProxy(this);
|
|
}
|
|
|
|
// And we will fill all the landscape components with the provided final layer content put into the default layer (aka layer index 0)
|
|
const FLandscapeLayer* DefaultLayer = LandscapeActor->GetLayer(0);
|
|
check(DefaultLayer != nullptr);
|
|
|
|
FLayerImportSettings ImportSettings;
|
|
ImportSettings.SourceLayerGuid = FinalLayerGuid;
|
|
ImportSettings.DestinationLayerGuid = DefaultLayer->Guid;
|
|
LayerImportSettings.Add(ImportSettings);
|
|
|
|
ComponentsToProcess.Append(ToRawPtrTArrayUnsafe(LandscapeComponents));
|
|
}
|
|
|
|
check(LayerImportSettings.Num() != 0);
|
|
// Currently only supports reimporting heightmap data into a single edit layer, which will always be the default layer
|
|
ReimportDestinationLayerGuid = LayerImportSettings[0].DestinationLayerGuid;
|
|
|
|
TSet<UTexture2D*> LayersTextures;
|
|
|
|
for (const FLayerImportSettings& ImportSettings : LayerImportSettings)
|
|
{
|
|
FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo, false);
|
|
FScopedSetLandscapeEditingLayer Scope(LandscapeActor, ImportSettings.DestinationLayerGuid);
|
|
|
|
const TArray<uint16>* ImportHeightData = InImportHeightData.Find(ImportSettings.SourceLayerGuid);
|
|
|
|
if (ImportHeightData != nullptr)
|
|
{
|
|
LandscapeEdit.SetHeightData(InMinX, InMinY, InMaxX, InMaxY, (uint16*)ImportHeightData->GetData(), 0, false, nullptr);
|
|
}
|
|
|
|
const TArray<FLandscapeImportLayerInfo>* ImportWeightData = InImportMaterialLayerInfos.Find(ImportSettings.SourceLayerGuid);
|
|
|
|
if (ImportWeightData != nullptr)
|
|
{
|
|
for (const FLandscapeImportLayerInfo& MaterialLayerInfo : *ImportWeightData)
|
|
{
|
|
if (MaterialLayerInfo.LayerInfo != nullptr && MaterialLayerInfo.LayerData.Num() > 0)
|
|
{
|
|
LandscapeEdit.SetAlphaData(MaterialLayerInfo.LayerInfo, InMinX, InMinY, InMaxX, InMaxY, MaterialLayerInfo.LayerData.GetData(), 0, ELandscapeLayerPaintingRestriction::None, true, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (ULandscapeComponent* Component : ComponentsToProcess)
|
|
{
|
|
FLandscapeLayerComponentData* ComponentLayerData = Component->GetLayerData(ImportSettings.DestinationLayerGuid);
|
|
check(ComponentLayerData != nullptr);
|
|
|
|
LayersTextures.Add(ComponentLayerData->HeightmapData.Texture);
|
|
LayersTextures.Append(ToRawPtrTArrayUnsafe(ComponentLayerData->WeightmapData.Textures));
|
|
}
|
|
}
|
|
|
|
// Retrigger a caching of the platform data as we wrote again in the textures
|
|
for (UTexture2D* Texture : LayersTextures)
|
|
{
|
|
Texture->UpdateResource();
|
|
}
|
|
|
|
LandscapeActor->RequestLayersContentUpdateForceAll();
|
|
|
|
if (!ShouldComponentBeRegistered)
|
|
{
|
|
UnregisterAllComponents();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (GetLevel()->bIsVisible)
|
|
{
|
|
ReregisterAllComponents();
|
|
}
|
|
|
|
ReimportDestinationLayerGuid = FGuid();
|
|
LandscapeInfo->RecreateCollisionComponents();
|
|
LandscapeInfo->UpdateAllAddCollisions();
|
|
}
|
|
|
|
ReimportHeightmapFilePath = InHeightmapFileName;
|
|
|
|
LandscapeInfo->UpdateLayerInfoMap();
|
|
|
|
}
|
|
|
|
bool ALandscapeProxy::ExportToRawMesh(int32 InExportLOD, FMeshDescription& OutRawMesh) const
|
|
{
|
|
FBoxSphereBounds GarbageBounds;
|
|
return ExportToRawMesh(InExportLOD, OutRawMesh, GarbageBounds, true);
|
|
}
|
|
|
|
bool ALandscapeProxy::ExportToRawMesh(int32 InExportLOD, FMeshDescription& OutRawMesh, const FBoxSphereBounds& InBounds, bool bIgnoreBounds /*= false*/) const
|
|
{
|
|
TInlineComponentArray<ULandscapeComponent*> RegisteredLandscapeComponents;
|
|
GetComponents<ULandscapeComponent>(RegisteredLandscapeComponents);
|
|
|
|
const FIntRect LandscapeSectionRect = GetBoundingRect();
|
|
const FVector2D LandscapeUVScale = FVector2D(1.0f, 1.0f) / FVector2D(LandscapeSectionRect.Size());
|
|
|
|
FStaticMeshAttributes Attributes(OutRawMesh);
|
|
TVertexAttributesRef<FVector3f> VertexPositions = Attributes.GetVertexPositions();
|
|
TEdgeAttributesRef<bool> EdgeHardnesses = Attributes.GetEdgeHardnesses();
|
|
TPolygonGroupAttributesRef<FName> PolygonGroupImportedMaterialSlotNames = Attributes.GetPolygonGroupMaterialSlotNames();
|
|
TVertexInstanceAttributesRef<FVector3f> VertexInstanceNormals = Attributes.GetVertexInstanceNormals();
|
|
TVertexInstanceAttributesRef<FVector3f> VertexInstanceTangents = Attributes.GetVertexInstanceTangents();
|
|
TVertexInstanceAttributesRef<float> VertexInstanceBinormalSigns = Attributes.GetVertexInstanceBinormalSigns();
|
|
TVertexInstanceAttributesRef<FVector4> VertexInstanceColors = Attributes.GetVertexInstanceColors();
|
|
TVertexInstanceAttributesRef<FVector2D> VertexInstanceUVs = Attributes.GetVertexInstanceUVs();
|
|
|
|
if (VertexInstanceUVs.GetNumChannels() < 2)
|
|
{
|
|
VertexInstanceUVs.SetNumChannels(2);
|
|
}
|
|
|
|
// User specified LOD to export
|
|
int32 LandscapeLODToExport = ExportLOD;
|
|
if (InExportLOD != INDEX_NONE)
|
|
{
|
|
LandscapeLODToExport = FMath::Clamp<int32>(InExportLOD, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
|
|
}
|
|
|
|
// Export data for each component
|
|
for (auto It = RegisteredLandscapeComponents.CreateConstIterator(); It; ++It)
|
|
{
|
|
ULandscapeComponent* Component = (*It);
|
|
|
|
// Early out if the Landscape bounds and given bounds do not overlap at all
|
|
if (!bIgnoreBounds && !FBoxSphereBounds::SpheresIntersect(Component->Bounds, InBounds))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FLandscapeComponentDataInterface CDI(Component, LandscapeLODToExport);
|
|
const int32 ComponentSizeQuadsLOD = ((Component->ComponentSizeQuads + 1) >> LandscapeLODToExport) - 1;
|
|
const int32 SubsectionSizeQuadsLOD = ((Component->SubsectionSizeQuads + 1) >> LandscapeLODToExport) - 1;
|
|
const FIntPoint ComponentOffsetQuads = Component->GetSectionBase() - LandscapeSectionOffset - LandscapeSectionRect.Min;
|
|
const 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<uint8> VisDataMap;
|
|
TArray<FWeightmapLayerAllocationInfo>& 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<FIndexAndZ> VertIndexAndZ;
|
|
VertIndexAndZ.Reserve(ComponentSizeQuadsLOD*ComponentSizeQuadsLOD*UE_ARRAY_COUNT(QuadPattern));
|
|
int32 CurrentIndex = 0;
|
|
TMap<int32, FVector> IndexToPosition;
|
|
IndexToPosition.Reserve(ComponentSizeQuadsLOD*ComponentSizeQuadsLOD*UE_ARRAY_COUNT(QuadPattern));
|
|
for (int32 y = 0; y < ComponentSizeQuadsLOD; y++)
|
|
{
|
|
for (int32 x = 0; x < ComponentSizeQuadsLOD; x++)
|
|
{
|
|
for (int32 i = 0; i < UE_ARRAY_COUNT(QuadPattern); i++)
|
|
{
|
|
int32 VertexX = x + QuadPattern[i].X;
|
|
int32 VertexY = y + QuadPattern[i].Y;
|
|
FVector Position = CDI.GetWorldVertex(VertexX, VertexY);
|
|
|
|
// If at least one vertex is within the given bounds we should process the quad
|
|
new(VertIndexAndZ)FIndexAndZ(CurrentIndex, Position);
|
|
IndexToPosition.Add(CurrentIndex, Position);
|
|
CurrentIndex++;
|
|
}
|
|
}
|
|
}
|
|
// Sort the vertices by z value
|
|
VertIndexAndZ.Sort(FCompareIndexAndZ());
|
|
|
|
auto FindPreviousIndex = [&VertIndexAndZ, &IndexToPosition](int32 Index)->int32
|
|
{
|
|
const FVector& PositionA = IndexToPosition[Index];
|
|
FIndexAndZ CompressPosition(0, PositionA);
|
|
// Search for lowest index duplicates
|
|
int32 BestIndex = MAX_int32;
|
|
for (int32 i = 0; i < IndexToPosition.Num(); i++)
|
|
{
|
|
if (CompressPosition.Z > (VertIndexAndZ[i].Z + SMALL_NUMBER))
|
|
{
|
|
//We will not find anything there is no point searching more
|
|
break;
|
|
}
|
|
const FVector& PositionB = IndexToPosition[VertIndexAndZ[i].Index];
|
|
if (PointsEqual(PositionA, PositionB, SMALL_NUMBER))
|
|
{
|
|
if (VertIndexAndZ[i].Index < BestIndex)
|
|
{
|
|
BestIndex = VertIndexAndZ[i].Index;
|
|
}
|
|
}
|
|
}
|
|
return BestIndex < MAX_int32 ? BestIndex : Index;
|
|
};
|
|
|
|
// Export to MeshDescription
|
|
TMap<int32, FVertexID> IndexToVertexID;
|
|
IndexToVertexID.Reserve(CurrentIndex);
|
|
CurrentIndex = 0;
|
|
for (int32 y = 0; y < ComponentSizeQuadsLOD; y++)
|
|
{
|
|
for (int32 x = 0; x < ComponentSizeQuadsLOD; x++)
|
|
{
|
|
FVector Positions[UE_ARRAY_COUNT(QuadPattern)];
|
|
bool bProcess = bIgnoreBounds;
|
|
|
|
// Fill positions
|
|
for (int32 i = 0; i < UE_ARRAY_COUNT(QuadPattern); i++)
|
|
{
|
|
int32 VertexX = x + QuadPattern[i].X;
|
|
int32 VertexY = y + QuadPattern[i].Y;
|
|
Positions[i] = CDI.GetWorldVertex(VertexX, VertexY);
|
|
|
|
// If at least one vertex is within the given bounds we should process the quad
|
|
if (!bProcess && InBounds.ComputeSquaredDistanceFromBoxToPoint(Positions[i]) < SquaredSphereRadius)
|
|
{
|
|
bProcess = true;
|
|
}
|
|
}
|
|
|
|
if (bProcess)
|
|
{
|
|
//Fill the vertexID we need
|
|
TArray<FVertexID> VertexIDs;
|
|
VertexIDs.Reserve(UE_ARRAY_COUNT(QuadPattern));
|
|
TArray<FVertexInstanceID> VertexInstanceIDs;
|
|
VertexInstanceIDs.Reserve(UE_ARRAY_COUNT(QuadPattern));
|
|
// Fill positions
|
|
for (int32 i = 0; i < UE_ARRAY_COUNT(QuadPattern); i++)
|
|
{
|
|
int32 DuplicateLowestIndex = FindPreviousIndex(CurrentIndex);
|
|
FVertexID VertexID;
|
|
if (DuplicateLowestIndex < CurrentIndex)
|
|
{
|
|
VertexID = IndexToVertexID[DuplicateLowestIndex];
|
|
}
|
|
else
|
|
{
|
|
VertexID = OutRawMesh.CreateVertex();
|
|
VertexPositions[VertexID] = 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<FVertexInstanceID> PerimeterVertexInstances;
|
|
PerimeterVertexInstances.SetNum(3);
|
|
for (int32 Corner = 0; Corner < 3; ++Corner)
|
|
{
|
|
PerimeterVertexInstances[Corner] = VertexInstanceIDs[BaseIndex + Corner];
|
|
}
|
|
// Insert a polygon into the mesh
|
|
TArray<FEdgeID> NewEdgeIDs;
|
|
const FPolygonID NewPolygonID = OutRawMesh.CreatePolygon(PolygonGroupID, PerimeterVertexInstances, &NewEdgeIDs);
|
|
for (const FEdgeID& NewEdgeID : NewEdgeIDs)
|
|
{
|
|
EdgeHardnesses[NewEdgeID] = false;
|
|
}
|
|
};
|
|
AddTriangle(0);
|
|
AddTriangle(3);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CurrentIndex += UE_ARRAY_COUNT(QuadPattern);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Compact the MeshDescription, if there was visibility mask or some bounding box clip, it need to be compacted so the sparse array are from 0 to n with no invalid data in between.
|
|
FElementIDRemappings ElementIDRemappings;
|
|
OutRawMesh.Compact(ElementIDRemappings);
|
|
return OutRawMesh.Polygons().Num() > 0;
|
|
}
|
|
|
|
|
|
FIntRect ALandscapeProxy::GetBoundingRect() const
|
|
{
|
|
if (LandscapeComponents.Num() > 0)
|
|
{
|
|
FIntRect Rect(MAX_int32, MAX_int32, MIN_int32, MIN_int32);
|
|
for (int32 CompIdx = 0; CompIdx < LandscapeComponents.Num(); CompIdx++)
|
|
{
|
|
Rect.Include(LandscapeComponents[CompIdx]->GetSectionBase());
|
|
}
|
|
Rect.Max += FIntPoint(ComponentSizeQuads, ComponentSizeQuads);
|
|
Rect -= LandscapeSectionOffset;
|
|
return Rect;
|
|
}
|
|
|
|
return FIntRect();
|
|
}
|
|
|
|
bool ALandscape::HasAllComponent()
|
|
{
|
|
ULandscapeInfo* Info = GetLandscapeInfo();
|
|
if (Info && Info->XYtoComponentMap.Num() == LandscapeComponents.Num())
|
|
{
|
|
// all components are owned by this Landscape actor (no Landscape Proxies)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ULandscapeInfo::GetLandscapeExtent(ALandscapeProxy* LandscapeProxy, FIntRect& ProxyExtent) const
|
|
{
|
|
ProxyExtent.Min.X = INT32_MAX;
|
|
ProxyExtent.Min.Y = INT32_MAX;
|
|
ProxyExtent.Max.X = INT32_MIN;
|
|
ProxyExtent.Max.Y = INT32_MIN;
|
|
|
|
for (ULandscapeComponent* LandscapeComponent : LandscapeProxy->LandscapeComponents)
|
|
{
|
|
LandscapeComponent->GetComponentExtent(ProxyExtent.Min.X, ProxyExtent.Min.Y, ProxyExtent.Max.X, ProxyExtent.Max.Y);
|
|
}
|
|
|
|
return ProxyExtent.Min.X != INT32_MAX;
|
|
}
|
|
|
|
bool ULandscapeInfo::GetLandscapeExtent(FIntRect& LandscapeExtent) const
|
|
{
|
|
return GetLandscapeExtent(LandscapeExtent.Min.X, LandscapeExtent.Min.Y, LandscapeExtent.Max.X, LandscapeExtent.Max.Y);
|
|
}
|
|
|
|
bool ULandscapeInfo::GetLandscapeExtent(int32& MinX, int32& MinY, int32& MaxX, int32& MaxY) const
|
|
{
|
|
MinX = MAX_int32;
|
|
MinY = MAX_int32;
|
|
MaxX = MIN_int32;
|
|
MaxY = MIN_int32;
|
|
|
|
// Find range of entire landscape
|
|
for (auto& XYComponentPair : XYtoComponentMap)
|
|
{
|
|
const ULandscapeComponent* Comp = XYComponentPair.Value;
|
|
Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY);
|
|
}
|
|
return (MinX != MAX_int32);
|
|
}
|
|
|
|
LANDSCAPE_API void ULandscapeInfo::ForAllLandscapeComponents(TFunctionRef<void(ULandscapeComponent*)> Fn) const
|
|
{
|
|
ForAllLandscapeProxies([&](ALandscapeProxy* Proxy)
|
|
{
|
|
for (ULandscapeComponent* Component : Proxy->LandscapeComponents)
|
|
{
|
|
Fn(Component);
|
|
}
|
|
});
|
|
}
|
|
|
|
bool ULandscapeInfo::GetSelectedExtent(int32& MinX, int32& MinY, int32& MaxX, int32& MaxY) const
|
|
{
|
|
MinX = MinY = MAX_int32;
|
|
MaxX = MaxY = MIN_int32;
|
|
for (auto& SelectedPointPair : SelectedRegion)
|
|
{
|
|
const FIntPoint Key = SelectedPointPair.Key;
|
|
if (MinX > Key.X) MinX = Key.X;
|
|
if (MaxX < Key.X) MaxX = Key.X;
|
|
if (MinY > Key.Y) MinY = Key.Y;
|
|
if (MaxY < Key.Y) MaxY = Key.Y;
|
|
}
|
|
if (MinX != MAX_int32)
|
|
{
|
|
return true;
|
|
}
|
|
// if SelectedRegion is empty, try SelectedComponents
|
|
for (const ULandscapeComponent* Comp : SelectedComponents)
|
|
{
|
|
Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY);
|
|
}
|
|
return MinX != MAX_int32;
|
|
}
|
|
|
|
FVector ULandscapeInfo::GetLandscapeCenterPos(float& LengthZ, int32 MinX /*= MAX_INT*/, int32 MinY /*= MAX_INT*/, int32 MaxX /*= MIN_INT*/, int32 MaxY /*= MIN_INT*/)
|
|
{
|
|
// MinZ, MaxZ is Local coordinate
|
|
float MaxZ = -HALF_WORLD_MAX, MinZ = HALF_WORLD_MAX;
|
|
const float ScaleZ = DrawScale.Z;
|
|
|
|
if (MinX == MAX_int32)
|
|
{
|
|
// Find range of entire landscape
|
|
for (auto It = XYtoComponentMap.CreateIterator(); It; ++It)
|
|
{
|
|
ULandscapeComponent* Comp = It.Value();
|
|
Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY);
|
|
}
|
|
|
|
const int32 Dist = (ComponentSizeQuads + 1) >> 1; // Should be same in ALandscapeGizmoActiveActor::SetTargetLandscape
|
|
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<ILandscapeEditorModule>("LandscapeEditor");
|
|
FLandscapeEditDataInterface LandscapeEdit(this);
|
|
|
|
TArray<uint16> HeightData;
|
|
int32 ExportWidth = ExportRegion.Width() + 1;
|
|
int32 ExportHeight = ExportRegion.Height() + 1;
|
|
HeightData.AddZeroed(ExportWidth * ExportHeight);
|
|
LandscapeEdit.GetHeightDataFast(ExportRegion.Min.X, ExportRegion.Min.Y, ExportRegion.Max.X, ExportRegion.Max.Y, HeightData.GetData(), 0);
|
|
|
|
const ILandscapeHeightmapFileFormat* HeightmapFormat = LandscapeEditorModule.GetHeightmapFormatByExtension(*FPaths::GetExtension(Filename, true));
|
|
if (HeightmapFormat)
|
|
{
|
|
HeightmapFormat->Export(*Filename, NAME_None, HeightData, {(uint32)ExportWidth, (uint32)ExportHeight}, DrawScale * FVector(1, 1, LANDSCAPE_ZSCALE));
|
|
}
|
|
}
|
|
|
|
void ULandscapeInfo::ExportLayer(ULandscapeLayerInfoObject* LayerInfo, const FString& Filename)
|
|
{
|
|
FIntRect ExportRegion;
|
|
if (!GetLandscapeExtent(ExportRegion))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ExportLayer(LayerInfo, Filename, ExportRegion);
|
|
}
|
|
|
|
void ULandscapeInfo::ExportLayer(ULandscapeLayerInfoObject* LayerInfo, const FString& Filename, const FIntRect& ExportRegion)
|
|
{
|
|
FScopedSlowTask Progress(1, LOCTEXT("ExportingLandscapeWeightmapTask", "Exporting Landscape Layer Weightmap..."));
|
|
Progress.MakeDialog();
|
|
|
|
check(LayerInfo);
|
|
|
|
ILandscapeEditorModule& LandscapeEditorModule = FModuleManager::GetModuleChecked<ILandscapeEditorModule>("LandscapeEditor");
|
|
|
|
TArray<uint8> WeightData;
|
|
int32 ExportWidth = ExportRegion.Width() + 1;
|
|
int32 ExportHeight = ExportRegion.Height() + 1;
|
|
WeightData.AddZeroed(ExportWidth * ExportHeight);
|
|
|
|
FLandscapeEditDataInterface LandscapeEdit(this);
|
|
LandscapeEdit.GetWeightDataFast(LayerInfo, ExportRegion.Min.X, ExportRegion.Min.Y, ExportRegion.Max.X, ExportRegion.Max.Y, WeightData.GetData(), 0);
|
|
|
|
const ILandscapeWeightmapFileFormat* WeightmapFormat = LandscapeEditorModule.GetWeightmapFormatByExtension(*FPaths::GetExtension(Filename, true));
|
|
if (WeightmapFormat)
|
|
{
|
|
WeightmapFormat->Export(*Filename, LayerInfo->LayerName, WeightData, { (uint32)ExportWidth, (uint32)ExportHeight }, DrawScale * FVector(1, 1, LANDSCAPE_ZSCALE));
|
|
}
|
|
}
|
|
|
|
void ULandscapeInfo::DeleteLayer(ULandscapeLayerInfoObject* LayerInfo, const FName& LayerName)
|
|
{
|
|
GWarn->BeginSlowTask(LOCTEXT("BeginDeletingLayerTask", "Deleting Layer"), true);
|
|
|
|
// Remove data from all components
|
|
FLandscapeEditDataInterface LandscapeEdit(this);
|
|
LandscapeEdit.DeleteLayer(LayerInfo);
|
|
|
|
// Remove from layer settings array
|
|
{
|
|
int32 LayerIndex = Layers.IndexOfByPredicate([LayerInfo, LayerName](const FLandscapeInfoLayerSettings& LayerSettings) { return LayerSettings.LayerInfoObj == LayerInfo && LayerSettings.LayerName == LayerName; });
|
|
if (LayerIndex != INDEX_NONE)
|
|
{
|
|
Layers.RemoveAt(LayerIndex);
|
|
}
|
|
}
|
|
|
|
ForAllLandscapeProxies([LayerInfo](ALandscapeProxy* Proxy)
|
|
{
|
|
Proxy->Modify();
|
|
int32 Index = Proxy->EditorLayerSettings.IndexOfByKey(LayerInfo);
|
|
if (Index != INDEX_NONE)
|
|
{
|
|
Proxy->EditorLayerSettings.RemoveAt(Index);
|
|
}
|
|
});
|
|
|
|
//UpdateLayerInfoMap();
|
|
|
|
GWarn->EndSlowTask();
|
|
}
|
|
|
|
void ULandscapeInfo::ReplaceLayer(ULandscapeLayerInfoObject* FromLayerInfo, ULandscapeLayerInfoObject* ToLayerInfo)
|
|
{
|
|
if (ensure(FromLayerInfo != ToLayerInfo))
|
|
{
|
|
GWarn->BeginSlowTask(LOCTEXT("BeginReplacingLayerTask", "Replacing Layer"), true);
|
|
|
|
// Remove data from all components
|
|
FLandscapeEditDataInterface LandscapeEdit(this);
|
|
LandscapeEdit.ReplaceLayer(FromLayerInfo, ToLayerInfo);
|
|
|
|
// Convert array
|
|
for (int32 j = 0; j < Layers.Num(); j++)
|
|
{
|
|
if (Layers[j].LayerInfoObj == FromLayerInfo)
|
|
{
|
|
Layers[j].LayerInfoObj = ToLayerInfo;
|
|
}
|
|
}
|
|
|
|
ForAllLandscapeProxies([FromLayerInfo, ToLayerInfo](ALandscapeProxy* Proxy)
|
|
{
|
|
Proxy->Modify();
|
|
FLandscapeEditorLayerSettings* ToEditorLayerSettings = Proxy->EditorLayerSettings.FindByKey(ToLayerInfo);
|
|
if (ToEditorLayerSettings != nullptr)
|
|
{
|
|
// If the new layer already exists, simple remove the old layer
|
|
int32 Index = Proxy->EditorLayerSettings.IndexOfByKey(FromLayerInfo);
|
|
if (Index != INDEX_NONE)
|
|
{
|
|
Proxy->EditorLayerSettings.RemoveAt(Index);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FLandscapeEditorLayerSettings* FromEditorLayerSettings = Proxy->EditorLayerSettings.FindByKey(FromLayerInfo);
|
|
if (FromEditorLayerSettings != nullptr)
|
|
{
|
|
// If only the old layer exists (most common case), change it to point to the new layer info
|
|
FromEditorLayerSettings->LayerInfoObj = ToLayerInfo;
|
|
}
|
|
else
|
|
{
|
|
// If neither exists in the EditorLayerSettings cache, add it
|
|
Proxy->EditorLayerSettings.Add(FLandscapeEditorLayerSettings(ToLayerInfo));
|
|
}
|
|
}
|
|
});
|
|
|
|
//UpdateLayerInfoMap();
|
|
|
|
GWarn->EndSlowTask();
|
|
}
|
|
}
|
|
|
|
void ULandscapeInfo::GetUsedPaintLayers(const FGuid& InLayerGuid, TArray<ULandscapeLayerInfoObject*>& OutUsedLayerInfos) const
|
|
{
|
|
OutUsedLayerInfos.Empty();
|
|
ForAllLandscapeProxies([&](ALandscapeProxy* Proxy)
|
|
{
|
|
for (ULandscapeComponent* Component : Proxy->LandscapeComponents)
|
|
{
|
|
const TArray<FWeightmapLayerAllocationInfo>& 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<ALandscape>(GetWorld()))
|
|
{
|
|
if (Landscape && Landscape != this && !Landscape->HasAnyFlags(RF_BeginDestroyed) && Landscape->LandscapeGuid == LandscapeGuid)
|
|
{
|
|
// Copy/Paste case, need to generate new GUID
|
|
LandscapeGuid = FGuid::NewGuid();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Some edit layers could be affected by BP brushes, which might need to be updated when the landscape is transformed :
|
|
RequestLayersContentUpdate(ELandscapeLayerUpdateMode::Update_All);
|
|
|
|
Super::PostEditImport();
|
|
}
|
|
|
|
void ALandscape::PostDuplicate(bool bDuplicateForPIE)
|
|
{
|
|
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<float>(Hardness, 0.0f, 1.0f);
|
|
}
|
|
else if (PropertyName == NAME_PhysMaterial || PropertyName == NAME_MinimumCollisionRelevanceWeight)
|
|
{
|
|
for (TObjectIterator<ALandscapeProxy> It; It; ++It)
|
|
{
|
|
ALandscapeProxy* Proxy = *It;
|
|
if (Proxy->GetWorld() && !Proxy->GetWorld()->IsPlayInEditor())
|
|
{
|
|
ULandscapeInfo* Info = Proxy->GetLandscapeInfo();
|
|
if (Info)
|
|
{
|
|
for (int32 i = 0; i < Info->Layers.Num(); ++i)
|
|
{
|
|
if (Info->Layers[i].LayerInfoObj == this)
|
|
{
|
|
Proxy->ChangedPhysMaterial();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (PropertyName == NAME_LayerUsageDebugColor || PropertyName == NAME_R || PropertyName == NAME_G || PropertyName == NAME_B || PropertyName == NAME_A)
|
|
{
|
|
LayerUsageDebugColor.A = 1.0f;
|
|
for (TObjectIterator<ALandscapeProxy> It; It; ++It)
|
|
{
|
|
ALandscapeProxy* Proxy = *It;
|
|
if (Proxy->GetWorld() && !Proxy->GetWorld()->IsPlayInEditor())
|
|
{
|
|
Proxy->MarkComponentsRenderStateDirty();
|
|
}
|
|
}
|
|
}
|
|
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ULandscapeLayerInfoObject, SplineFalloffModulationTexture) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(ULandscapeLayerInfoObject, SplineFalloffModulationColorMask) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(ULandscapeLayerInfoObject, SplineFalloffModulationBias) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(ULandscapeLayerInfoObject, SplineFalloffModulationScale) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(ULandscapeLayerInfoObject, SplineFalloffModulationTiling))
|
|
{
|
|
for (TObjectIterator<ULandscapeInfo> It; It; ++It)
|
|
{
|
|
if(ALandscape* Landscape = It->LandscapeActor.Get())
|
|
{
|
|
Landscape->OnLayerInfoSplineFalloffModulationChanged(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeLayerInfoObject::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
if (GIsEditor)
|
|
{
|
|
if (!HasAnyFlags(RF_Standalone))
|
|
{
|
|
SetFlags(RF_Standalone);
|
|
}
|
|
Hardness = FMath::Clamp<float>(Hardness, 0.0f, 1.0f);
|
|
}
|
|
}
|
|
|
|
void ALandscapeProxy::RemoveXYOffsets()
|
|
{
|
|
bool bFoundXYOffset = false;
|
|
|
|
for (int32 i = 0; i < LandscapeComponents.Num(); ++i)
|
|
{
|
|
ULandscapeComponent* Comp = LandscapeComponents[i];
|
|
if (Comp && Comp->XYOffsetmapTexture)
|
|
{
|
|
Comp->XYOffsetmapTexture->SetFlags(RF_Transactional);
|
|
Comp->XYOffsetmapTexture->Modify();
|
|
Comp->XYOffsetmapTexture->MarkPackageDirty();
|
|
Comp->XYOffsetmapTexture->ClearFlags(RF_Standalone);
|
|
Comp->Modify();
|
|
Comp->MarkPackageDirty();
|
|
Comp->XYOffsetmapTexture = nullptr;
|
|
Comp->MarkRenderStateDirty();
|
|
bFoundXYOffset = true;
|
|
}
|
|
}
|
|
|
|
if (bFoundXYOffset)
|
|
{
|
|
RecreateCollisionComponents();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void ALandscapeProxy::RecreateCollisionComponents()
|
|
{
|
|
// We can assume these are all junk; they recreate as needed
|
|
FlushGrassComponents();
|
|
|
|
// Clear old CollisionComponent containers
|
|
CollisionComponents.Empty();
|
|
|
|
// Destroy any owned collision components
|
|
TInlineComponentArray<ULandscapeHeightfieldCollisionComponent*> CollisionComps;
|
|
GetComponents(CollisionComps);
|
|
for (ULandscapeHeightfieldCollisionComponent* Component : CollisionComps)
|
|
{
|
|
Component->DestroyComponent();
|
|
}
|
|
|
|
TArray<USceneComponent*> AttachedCollisionComponents = RootComponent->GetAttachChildren().FilterByPredicate(
|
|
[](USceneComponent* Component)
|
|
{
|
|
return Cast<ULandscapeHeightfieldCollisionComponent>(Component);
|
|
});
|
|
|
|
// Destroy any attached but un-owned collision components
|
|
for (USceneComponent* Component : AttachedCollisionComponents)
|
|
{
|
|
Component->DestroyComponent();
|
|
}
|
|
|
|
// Recreate collision
|
|
CollisionMipLevel = FMath::Clamp<int32>(CollisionMipLevel, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
|
|
SimpleCollisionMipLevel = FMath::Clamp<int32>(SimpleCollisionMipLevel, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
|
|
for (ULandscapeComponent* Comp : LandscapeComponents)
|
|
{
|
|
if (Comp)
|
|
{
|
|
Comp->CollisionMipLevel = CollisionMipLevel;
|
|
Comp->SimpleCollisionMipLevel = SimpleCollisionMipLevel;
|
|
Comp->DestroyCollisionData();
|
|
Comp->UpdateCollisionData();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeInfo::RecreateCollisionComponents()
|
|
{
|
|
ForAllLandscapeProxies([](ALandscapeProxy* Proxy)
|
|
{
|
|
Proxy->RecreateCollisionComponents();
|
|
});
|
|
}
|
|
|
|
void ULandscapeInfo::RemoveXYOffsets()
|
|
{
|
|
ForAllLandscapeProxies([](ALandscapeProxy* Proxy)
|
|
{
|
|
Proxy->RemoveXYOffsets();
|
|
});
|
|
}
|
|
|
|
void ULandscapeInfo::PostponeTextureBaking()
|
|
{
|
|
static const int32 PostponeValue = 60; //frames
|
|
|
|
ForAllLandscapeProxies([](ALandscapeProxy* Proxy)
|
|
{
|
|
Proxy->UpdateBakedTexturesCountdown = PostponeValue;
|
|
});
|
|
}
|
|
|
|
bool ULandscapeInfo::CanHaveLayersContent() const
|
|
{
|
|
if (ALandscape* Landscape = LandscapeActor.Get())
|
|
{
|
|
return Landscape->CanHaveLayersContent();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ULandscapeInfo::ClearDirtyData()
|
|
{
|
|
if (ALandscape* Landscape = LandscapeActor.Get())
|
|
{
|
|
ForAllLandscapeComponents([=](ULandscapeComponent* InLandscapeComponent)
|
|
{
|
|
Landscape->ClearDirtyData(InLandscapeComponent);
|
|
});
|
|
}
|
|
}
|
|
|
|
void ULandscapeInfo::UpdateAllComponentMaterialInstances()
|
|
{
|
|
ForAllLandscapeProxies([](ALandscapeProxy* Proxy)
|
|
{
|
|
Proxy->UpdateAllComponentMaterialInstances();
|
|
});
|
|
}
|
|
|
|
uint32 ULandscapeInfo::GetGridSize(uint32 InGridSizeInComponents) const
|
|
{
|
|
return InGridSizeInComponents * ComponentSizeQuads;
|
|
}
|
|
|
|
ALandscapeProxy* ULandscapeInfo::MoveComponentsToLevel(const TArray<ULandscapeComponent*>& InComponents, ULevel* TargetLevel, FName NewProxyName)
|
|
{
|
|
ALandscape* Landscape = LandscapeActor.Get();
|
|
check(Landscape != nullptr);
|
|
|
|
// Make sure references are in a different package (should be fixed up before calling this method)
|
|
// Check the Physical Material is same package with Landscape
|
|
if (Landscape->DefaultPhysMaterial && Landscape->DefaultPhysMaterial->GetOutermost() == Landscape->GetOutermost())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// Check the LayerInfoObjects are not in same package as Landscape
|
|
for (int32 i = 0; i < Layers.Num(); ++i)
|
|
{
|
|
ULandscapeLayerInfoObject* LayerInfo = Layers[i].LayerInfoObj;
|
|
if (LayerInfo && LayerInfo->GetOutermost() == Landscape->GetOutermost())
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
// Check the Landscape Materials are not in same package as moved components
|
|
for (ULandscapeComponent* Component : InComponents)
|
|
{
|
|
UMaterialInterface* LandscapeMaterial = Component->GetLandscapeMaterial();
|
|
if (LandscapeMaterial && LandscapeMaterial->GetOutermost() == Component->GetOutermost())
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
ALandscapeProxy* LandscapeProxy = GetLandscapeProxyForLevel(TargetLevel);
|
|
bool bSetPositionAndOffset = false;
|
|
if (!LandscapeProxy)
|
|
{
|
|
FActorSpawnParameters SpawnParams;
|
|
SpawnParams.Name = NewProxyName;
|
|
SpawnParams.OverrideLevel = TargetLevel;
|
|
LandscapeProxy = TargetLevel->GetWorld()->SpawnActor<ALandscapeStreamingProxy>(SpawnParams);
|
|
|
|
// copy shared properties to this new proxy
|
|
LandscapeProxy->GetSharedProperties(Landscape);
|
|
LandscapeProxy->CreateLandscapeInfo();
|
|
LandscapeProxy->SetActorLabel(LandscapeProxy->GetName());
|
|
bSetPositionAndOffset = true;
|
|
}
|
|
|
|
return MoveComponentsToProxy(InComponents, LandscapeProxy, bSetPositionAndOffset, TargetLevel);
|
|
}
|
|
|
|
ALandscapeProxy* ULandscapeInfo::MoveComponentsToProxy(const TArray<ULandscapeComponent*>& InComponents, ALandscapeProxy* LandscapeProxy, bool bSetPositionAndOffset, ULevel* TargetLevel)
|
|
{
|
|
ALandscape* Landscape = LandscapeActor.Get();
|
|
check(Landscape != nullptr);
|
|
|
|
struct FCompareULandscapeComponentBySectionBase
|
|
{
|
|
FORCEINLINE bool operator()(const ULandscapeComponent& A, const ULandscapeComponent& B) const
|
|
{
|
|
return (A.GetSectionBase().X == B.GetSectionBase().X) ? (A.GetSectionBase().Y < B.GetSectionBase().Y) : (A.GetSectionBase().X < B.GetSectionBase().X);
|
|
}
|
|
};
|
|
TArray<ULandscapeComponent*> ComponentsToMove(InComponents);
|
|
ComponentsToMove.Sort(FCompareULandscapeComponentBySectionBase());
|
|
|
|
const int32 ComponentSizeVerts = Landscape->NumSubsections * (Landscape->SubsectionSizeQuads + 1);
|
|
const int32 NeedHeightmapSize = 1 << FMath::CeilLogTwo(ComponentSizeVerts);
|
|
|
|
TSet<ALandscapeProxy*> SelectProxies;
|
|
TSet<ULandscapeComponent*> TargetSelectedComponents;
|
|
TArray<ULandscapeHeightfieldCollisionComponent*> TargetSelectedCollisionComponents;
|
|
for (ULandscapeComponent* Component : ComponentsToMove)
|
|
{
|
|
SelectProxies.Add(Component->GetLandscapeProxy());
|
|
if (Component->GetLandscapeProxy() != LandscapeProxy && (!TargetLevel || Component->GetLandscapeProxy()->GetOuter() != TargetLevel))
|
|
{
|
|
TargetSelectedComponents.Add(Component);
|
|
}
|
|
|
|
ULandscapeHeightfieldCollisionComponent* CollisionComp = Component->CollisionComponent.Get();
|
|
SelectProxies.Add(CollisionComp->GetLandscapeProxy());
|
|
if (CollisionComp->GetLandscapeProxy() != LandscapeProxy && (!TargetLevel || CollisionComp->GetLandscapeProxy()->GetOuter() != TargetLevel))
|
|
{
|
|
TargetSelectedCollisionComponents.Add(CollisionComp);
|
|
}
|
|
}
|
|
|
|
// Check which ones are need for height map change
|
|
TSet<UTexture2D*> OldHeightmapTextures;
|
|
for (ULandscapeComponent* Component : TargetSelectedComponents)
|
|
{
|
|
Component->Modify();
|
|
OldHeightmapTextures.Add(Component->GetHeightmap());
|
|
}
|
|
|
|
// Need to split all the component which share Heightmap with selected components
|
|
TMap<ULandscapeComponent*, bool> HeightmapUpdateComponents;
|
|
HeightmapUpdateComponents.Reserve(TargetSelectedComponents.Num() * 4); // worst case
|
|
for (ULandscapeComponent* Component : TargetSelectedComponents)
|
|
{
|
|
// Search neighbor only
|
|
const int32 SearchX = Component->GetHeightmap()->Source.GetSizeX() / NeedHeightmapSize - 1;
|
|
const int32 SearchY = Component->GetHeightmap()->Source.GetSizeY() / NeedHeightmapSize - 1;
|
|
const FIntPoint ComponentBase = Component->GetSectionBase() / Component->ComponentSizeQuads;
|
|
|
|
for (int32 Y = -SearchY; Y <= SearchY; ++Y)
|
|
{
|
|
for (int32 X = -SearchX; X <= SearchX; ++X)
|
|
{
|
|
ULandscapeComponent* const Neighbor = XYtoComponentMap.FindRef(ComponentBase + FIntPoint(X, Y));
|
|
if (Neighbor && Neighbor->GetHeightmap() == Component->GetHeightmap() && !HeightmapUpdateComponents.Contains(Neighbor))
|
|
{
|
|
Neighbor->Modify();
|
|
bool bNeedsMoveToCurrentLevel = TargetSelectedComponents.Contains(Neighbor);
|
|
HeightmapUpdateComponents.Add(Neighbor, bNeedsMoveToCurrentLevel);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Proxy position/offset needs to be set
|
|
if(bSetPositionAndOffset)
|
|
{
|
|
// set proxy location
|
|
// by default first component location
|
|
ULandscapeComponent* FirstComponent = *TargetSelectedComponents.CreateConstIterator();
|
|
LandscapeProxy->GetRootComponent()->SetWorldLocationAndRotation(FirstComponent->GetComponentLocation(), FirstComponent->GetComponentRotation());
|
|
LandscapeProxy->LandscapeSectionOffset = FirstComponent->GetSectionBase();
|
|
}
|
|
|
|
// Hide(unregister) the new landscape if owning level currently in hidden state
|
|
if (LandscapeProxy->GetLevel()->bIsVisible == false)
|
|
{
|
|
LandscapeProxy->UnregisterAllComponents();
|
|
}
|
|
|
|
// Changing Heightmap format for selected components
|
|
for (const auto& HeightmapUpdateComponentPair : HeightmapUpdateComponents)
|
|
{
|
|
ALandscape::SplitHeightmap(HeightmapUpdateComponentPair.Key, HeightmapUpdateComponentPair.Value ? LandscapeProxy : nullptr);
|
|
}
|
|
|
|
// Delete if 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<FComponentRecreateRenderStateContext>* InOutRecreateRenderStateContext, bool InReregisterComponent)
|
|
{
|
|
ULandscapeInfo* Info = Comp->GetLandscapeInfo();
|
|
|
|
// Make sure the heightmap UVs are powers of two.
|
|
int32 ComponentSizeVerts = Comp->NumSubsections * (Comp->SubsectionSizeQuads + 1);
|
|
int32 HeightmapSizeU = (1 << FMath::CeilLogTwo(ComponentSizeVerts));
|
|
int32 HeightmapSizeV = (1 << FMath::CeilLogTwo(ComponentSizeVerts));
|
|
|
|
ALandscapeProxy* SrcProxy = Comp->GetLandscapeProxy();
|
|
ALandscapeProxy* DstProxy = TargetProxy ? TargetProxy : SrcProxy;
|
|
SrcProxy->Modify();
|
|
DstProxy->Modify();
|
|
|
|
UTexture2D* OldHeightmapTexture = Comp->GetHeightmap(false);
|
|
UTexture2D* NewHeightmapTexture = NULL;
|
|
FVector4 OldHeightmapScaleBias = Comp->HeightmapScaleBias;
|
|
FVector4 NewHeightmapScaleBias = FVector4(1.0f / (float)HeightmapSizeU, 1.0f / (float)HeightmapSizeV, 0.0f, 0.0f);
|
|
|
|
{
|
|
// Read old data and split
|
|
FLandscapeEditDataInterface LandscapeEdit(Info);
|
|
TArray<uint8> HeightData;
|
|
HeightData.AddZeroed((1 + Comp->ComponentSizeQuads) * (1 + Comp->ComponentSizeQuads) * sizeof(uint16));
|
|
// Because of edge problem, normal would be just copy from old component data
|
|
TArray<uint8> NormalData;
|
|
NormalData.AddZeroed((1 + Comp->ComponentSizeQuads) * (1 + Comp->ComponentSizeQuads) * sizeof(uint16));
|
|
LandscapeEdit.GetHeightDataFast(Comp->GetSectionBase().X, Comp->GetSectionBase().Y, Comp->GetSectionBase().X + Comp->ComponentSizeQuads, Comp->GetSectionBase().Y + Comp->ComponentSizeQuads, (uint16*)HeightData.GetData(), 0, (uint16*)NormalData.GetData());
|
|
|
|
// Create the new heightmap texture
|
|
NewHeightmapTexture = DstProxy->CreateLandscapeTexture(HeightmapSizeU, HeightmapSizeV, TEXTUREGROUP_Terrain_Heightmap, TSF_BGRA8);
|
|
ULandscapeComponent::CreateEmptyTextureMips(NewHeightmapTexture, true);
|
|
NewHeightmapTexture->PostEditChange();
|
|
|
|
Comp->HeightmapScaleBias = NewHeightmapScaleBias;
|
|
Comp->SetHeightmap(NewHeightmapTexture);
|
|
|
|
check(Comp->GetHeightmap(false) == Comp->GetHeightmap(true));
|
|
LandscapeEdit.SetHeightData(Comp->GetSectionBase().X, Comp->GetSectionBase().Y, Comp->GetSectionBase().X + Comp->ComponentSizeQuads, Comp->GetSectionBase().Y + Comp->ComponentSizeQuads, (uint16*)HeightData.GetData(), 0, false, (uint16*)NormalData.GetData());
|
|
}
|
|
|
|
// End material update
|
|
if (InOutUpdateContext != nullptr && InOutRecreateRenderStateContext != nullptr)
|
|
{
|
|
Comp->UpdateMaterialInstances(*InOutUpdateContext, *InOutRecreateRenderStateContext);
|
|
}
|
|
else
|
|
{
|
|
Comp->UpdateMaterialInstances();
|
|
}
|
|
|
|
// We disable automatic material update context, to manage it manually if we have a custom update context specified
|
|
GDisableAutomaticTextureMaterialUpdateDependencies = (InOutUpdateContext != nullptr);
|
|
|
|
if (InOutUpdateContext != nullptr)
|
|
{
|
|
// Build a list of all unique materials the landscape uses
|
|
TArray<UMaterialInterface*> LandscapeMaterials;
|
|
|
|
int8 MaxLOD = FMath::CeilLogTwo(Comp->SubsectionSizeQuads + 1) - 1;
|
|
|
|
for (int8 LODIndex = 0; LODIndex < MaxLOD; ++LODIndex)
|
|
{
|
|
UMaterialInterface* Material = Comp->GetLandscapeMaterial(LODIndex);
|
|
LandscapeMaterials.AddUnique(Material);
|
|
}
|
|
|
|
TSet<UMaterial*> BaseMaterialsThatUseThisTexture;
|
|
|
|
for (UMaterialInterface* MaterialInterface : LandscapeMaterials)
|
|
{
|
|
if (DoesMaterialUseTexture(MaterialInterface, NewHeightmapTexture))
|
|
{
|
|
UMaterial* Material = MaterialInterface->GetMaterial();
|
|
bool MaterialAlreadyCompute = false;
|
|
BaseMaterialsThatUseThisTexture.Add(Material, &MaterialAlreadyCompute);
|
|
|
|
if (!MaterialAlreadyCompute)
|
|
{
|
|
if (Material->IsTextureForceRecompileCacheRessource(NewHeightmapTexture))
|
|
{
|
|
InOutUpdateContext->AddMaterial(Material);
|
|
Material->UpdateMaterialShaderCacheAndTextureReferences();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
GDisableAutomaticTextureMaterialUpdateDependencies = false;
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
check(Comp->GetLandscapeProxy()->HasLayersContent() == DstProxy->CanHaveLayersContent());
|
|
if (Comp->GetLandscapeProxy()->HasLayersContent() && DstProxy->CanHaveLayersContent())
|
|
{
|
|
FLandscapeEditLayerReadback* NewCPUReadback = new FLandscapeEditLayerReadback();
|
|
DstProxy->HeightmapsCPUReadback.Add(NewHeightmapTexture, NewCPUReadback);
|
|
|
|
// Free OldHeightmapTexture's CPUReadBackResource if not used by any component
|
|
bool FreeCPUReadBack = true;
|
|
for (ULandscapeComponent* Component : SrcProxy->LandscapeComponents)
|
|
{
|
|
if (Component != Comp && Component->GetHeightmap(false) == OldHeightmapTexture)
|
|
{
|
|
FreeCPUReadBack = false;
|
|
break;
|
|
}
|
|
}
|
|
if (FreeCPUReadBack)
|
|
{
|
|
FLandscapeEditLayerReadback** OldCPUReadback = SrcProxy->HeightmapsCPUReadback.Find(OldHeightmapTexture);
|
|
if (OldCPUReadback)
|
|
{
|
|
if (FLandscapeEditLayerReadback* ResourceToDelete = *OldCPUReadback)
|
|
{
|
|
delete ResourceToDelete;
|
|
SrcProxy->HeightmapsCPUReadback.Remove(OldHeightmapTexture);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Move layer content to new layer heightmap
|
|
FLandscapeEditDataInterface LandscapeEdit(Info);
|
|
ALandscape* Landscape = Info->LandscapeActor.Get();
|
|
Comp->ForEachLayer([&](const FGuid& LayerGuid, FLandscapeLayerComponentData& LayerData)
|
|
{
|
|
UTexture2D* OldLayerHeightmap = LayerData.HeightmapData.Texture;
|
|
if (OldLayerHeightmap != nullptr)
|
|
{
|
|
FScopedSetLandscapeEditingLayer Scope(Landscape, LayerGuid);
|
|
// Read old data and split
|
|
TArray<uint8> LayerHeightData;
|
|
LayerHeightData.AddZeroed((1 + Comp->ComponentSizeQuads) * (1 + Comp->ComponentSizeQuads) * sizeof(uint16));
|
|
// Because of edge problem, normal would be just copy from old component data
|
|
TArray<uint8> LayerNormalData;
|
|
LayerNormalData.AddZeroed((1 + Comp->ComponentSizeQuads) * (1 + Comp->ComponentSizeQuads) * sizeof(uint16));
|
|
|
|
// Read using old heightmap scale/bias
|
|
Comp->HeightmapScaleBias = OldHeightmapScaleBias;
|
|
LandscapeEdit.GetHeightDataFast(Comp->GetSectionBase().X, Comp->GetSectionBase().Y, Comp->GetSectionBase().X + Comp->ComponentSizeQuads, Comp->GetSectionBase().Y + Comp->ComponentSizeQuads, (uint16*)LayerHeightData.GetData(), 0, (uint16*)LayerNormalData.GetData());
|
|
// Restore new heightmap scale/bias
|
|
Comp->HeightmapScaleBias = NewHeightmapScaleBias;
|
|
{
|
|
UTexture2D* LayerHeightmapTexture = DstProxy->CreateLandscapeTexture(HeightmapSizeU, HeightmapSizeV, TEXTUREGROUP_Terrain_Heightmap, TSF_BGRA8);
|
|
ULandscapeComponent::CreateEmptyTextureMips(LayerHeightmapTexture, true);
|
|
LayerHeightmapTexture->PostEditChange();
|
|
// Set Layer heightmap texture
|
|
LayerData.HeightmapData.Texture = LayerHeightmapTexture;
|
|
LandscapeEdit.SetHeightData(Comp->GetSectionBase().X, Comp->GetSectionBase().Y, Comp->GetSectionBase().X + Comp->ComponentSizeQuads, Comp->GetSectionBase().Y + Comp->ComponentSizeQuads, (uint16*)LayerHeightData.GetData(), 0, false, (uint16*)LayerNormalData.GetData());
|
|
}
|
|
}
|
|
});
|
|
|
|
Landscape->RequestLayersContentUpdateForceAll();
|
|
}
|
|
#endif
|
|
|
|
// Reregister
|
|
if (InReregisterComponent)
|
|
{
|
|
FComponentReregisterContext ReregisterContext(Comp);
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
inline float AdjustStaticLightingResolution(float StaticLightingResolution, int32 NumSubsections, int32 SubsectionSizeQuads, int32 ComponentSizeQuads)
|
|
{
|
|
// Change Lighting resolution to proper one...
|
|
if (StaticLightingResolution > 1.0f)
|
|
{
|
|
StaticLightingResolution = (int32)StaticLightingResolution;
|
|
}
|
|
else if (StaticLightingResolution < 1.0f)
|
|
{
|
|
// Restrict to 1/16
|
|
if (StaticLightingResolution < 0.0625)
|
|
{
|
|
StaticLightingResolution = 0.0625;
|
|
}
|
|
|
|
// Adjust to 1/2^n
|
|
int32 i = 2;
|
|
int32 LightmapSize = (NumSubsections * (SubsectionSizeQuads + 1)) >> 1;
|
|
while (StaticLightingResolution < (1.0f / i) && LightmapSize > 4)
|
|
{
|
|
i <<= 1;
|
|
LightmapSize >>= 1;
|
|
}
|
|
StaticLightingResolution = 1.0f / i;
|
|
|
|
int32 PixelPaddingX = GPixelFormats[PF_DXT1].BlockSizeX;
|
|
|
|
int32 DestSize = (int32)((2 * PixelPaddingX + ComponentSizeQuads + 1) * StaticLightingResolution);
|
|
StaticLightingResolution = (float)DestSize / (2 * PixelPaddingX + ComponentSizeQuads + 1);
|
|
}
|
|
|
|
return StaticLightingResolution;
|
|
}
|
|
};
|
|
|
|
bool ALandscapeProxy::CanEditChange(const FProperty* InProperty) const
|
|
{
|
|
if (!Super::CanEditChange(InProperty))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (IsTemplate())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Don't allow edition of properties that are shared with the parent landscape properties
|
|
// See ALandscapeProxy::FixupSharedData(ALandscape* Landscape)
|
|
if (GetLandscapeActor() != this)
|
|
{
|
|
FName PropertyName = InProperty ? InProperty->GetFName() : NAME_None;
|
|
|
|
if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, MaxLODLevel) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, ComponentScreenSizeToUseSubSections) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LODDistributionSetting) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LOD0DistributionSetting) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LOD0ScreenSize) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, TargetDisplayOrder) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, TargetDisplayOrderList))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ALandscapeProxy::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
|
{
|
|
const FName PropertyName = PropertyChangedEvent.MemberProperty ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None;
|
|
const FName SubPropertyName = PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None;
|
|
|
|
bool bChangedPhysMaterial = false;
|
|
|
|
if (PropertyName == FName(TEXT("RelativeScale3D")))
|
|
{
|
|
// RelativeScale3D isn't even a property of ALandscapeProxy, it's a property of the root component
|
|
if (RootComponent)
|
|
{
|
|
const FVector OriginalScale = RootComponent->GetRelativeScale3D();
|
|
FVector ModifiedScale = OriginalScale;
|
|
|
|
// Lock X and Y scaling to the same value
|
|
if (SubPropertyName == FName("Y"))
|
|
{
|
|
ModifiedScale.X = FMath::Abs(OriginalScale.Y)*FMath::Sign(ModifiedScale.X);
|
|
}
|
|
else if (SubPropertyName == FName("X"))
|
|
{
|
|
ModifiedScale.Y = FMath::Abs(OriginalScale.X)*FMath::Sign(ModifiedScale.Y);
|
|
}
|
|
|
|
ULandscapeInfo* Info = GetLandscapeInfo();
|
|
|
|
// Correct for attempts to scale to 0 on any axis
|
|
if (ModifiedScale.X == 0)
|
|
{
|
|
if (Info && Info->DrawScale.X < 0)
|
|
{
|
|
ModifiedScale.Y = ModifiedScale.X = -1;
|
|
}
|
|
else
|
|
{
|
|
ModifiedScale.Y = ModifiedScale.X = 1;
|
|
}
|
|
}
|
|
if (ModifiedScale.Z == 0)
|
|
{
|
|
if (Info && Info->DrawScale.Z < 0)
|
|
{
|
|
ModifiedScale.Z = -1;
|
|
}
|
|
else
|
|
{
|
|
ModifiedScale.Z = 1;
|
|
}
|
|
}
|
|
|
|
RootComponent->SetRelativeScale3D(ModifiedScale);
|
|
|
|
// Update ULandscapeInfo cached DrawScale
|
|
if (Info)
|
|
{
|
|
Info->DrawScale = ModifiedScale;
|
|
}
|
|
|
|
// We need to regenerate collision objects, they depend on scale value
|
|
if (PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive)
|
|
{
|
|
for (int32 ComponentIndex = 0; ComponentIndex < CollisionComponents.Num(); ComponentIndex++)
|
|
{
|
|
ULandscapeHeightfieldCollisionComponent* Comp = CollisionComponents[ComponentIndex];
|
|
if (Comp)
|
|
{
|
|
Comp->RecreateCollision();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GIsEditor && PropertyName == FName(TEXT("StreamingDistanceMultiplier")))
|
|
{
|
|
// Recalculate in a few seconds.
|
|
GetWorld()->TriggerStreamingDataRebuild();
|
|
}
|
|
else if (GIsEditor && PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, DefaultPhysMaterial))
|
|
{
|
|
bChangedPhysMaterial = true;
|
|
}
|
|
else if (GIsEditor &&
|
|
(PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, CollisionMipLevel) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, SimpleCollisionMipLevel) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, CollisionThickness) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bBakeMaterialPositionOffsetIntoCollision) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bGenerateOverlapEvents)))
|
|
{
|
|
if (bBakeMaterialPositionOffsetIntoCollision)
|
|
{
|
|
MarkComponentsRenderStateDirty();
|
|
}
|
|
if (PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive)
|
|
{
|
|
RecreateCollisionComponents();
|
|
}
|
|
}
|
|
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, ComponentScreenSizeToUseSubSections))
|
|
{
|
|
ChangeComponentScreenSizeToUseSubSections(ComponentScreenSizeToUseSubSections);
|
|
}
|
|
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LODDistributionSetting)
|
|
|| PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LOD0DistributionSetting)
|
|
|| PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LOD0ScreenSize))
|
|
{
|
|
MarkComponentsRenderStateDirty();
|
|
}
|
|
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bUseMaterialPositionOffsetInStaticLighting))
|
|
{
|
|
InvalidateLightingCache();
|
|
}
|
|
else if ((PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, CastShadow))
|
|
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bCastDynamicShadow))
|
|
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bCastStaticShadow))
|
|
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bCastFarShadow))
|
|
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bCastHiddenShadow))
|
|
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bCastShadowAsTwoSided))
|
|
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bAffectDistanceFieldLighting))
|
|
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bRenderCustomDepth))
|
|
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, CustomDepthStencilWriteMask))
|
|
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, CustomDepthStencilValue))
|
|
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LightingChannels))
|
|
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, LDMaxDrawDistance))
|
|
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bUsedForNavigation))
|
|
|| (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bFillCollisionUnderLandscapeForNavmesh)))
|
|
{
|
|
// Replicate shared properties to all components.
|
|
for (int32 ComponentIndex = 0; ComponentIndex < LandscapeComponents.Num(); ComponentIndex++)
|
|
{
|
|
ULandscapeComponent* Comp = LandscapeComponents[ComponentIndex];
|
|
if (Comp)
|
|
{
|
|
Comp->UpdatedSharedPropertiesFromActor();
|
|
}
|
|
}
|
|
}
|
|
else if (GIsEditor &&
|
|
(PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bMeshHoles) || PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, MeshHolesMaxLod)))
|
|
{
|
|
CheckGenerateLandscapePlatformData(false, nullptr);
|
|
MarkComponentsRenderStateDirty();
|
|
}
|
|
else if (PropertyName == FName(TEXT("bUseDynamicMaterialInstance")))
|
|
{
|
|
MarkComponentsRenderStateDirty();
|
|
}
|
|
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, RuntimeVirtualTextures)
|
|
|| PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, VirtualTextureRenderPassType)
|
|
|| PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, VirtualTextureNumLods)
|
|
|| PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, VirtualTextureLodBias))
|
|
{
|
|
MarkComponentsRenderStateDirty();
|
|
}
|
|
|
|
// Remove null layer infos
|
|
EditorLayerSettings.RemoveAll([](const FLandscapeEditorLayerSettings& Entry) { return Entry.LayerInfoObj == nullptr; });
|
|
|
|
// Remove any null landscape components
|
|
LandscapeComponents.RemoveAll([](const ULandscapeComponent* Component) { return Component == nullptr; });
|
|
|
|
ULandscapeInfo* Info = GetLandscapeInfo();
|
|
bool bRemovedAnyLayers = false;
|
|
for (ULandscapeComponent* Component : LandscapeComponents)
|
|
{
|
|
TArray<FWeightmapLayerAllocationInfo>& 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<ULandscapeSubsystem>()->IsGridBased();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ALandscapeStreamingProxy::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
|
{
|
|
const FName PropertyName = PropertyChangedEvent.MemberProperty ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None;
|
|
|
|
if (PropertyName == FName(TEXT("LandscapeActor")))
|
|
{
|
|
if (LandscapeActor && IsValidLandscapeActor(LandscapeActor.Get()))
|
|
{
|
|
LandscapeGuid = LandscapeActor->GetLandscapeGuid();
|
|
if (GIsEditor && GetWorld() && !GetWorld()->IsPlayInEditor())
|
|
{
|
|
// TODO - only need to refresh the old and new landscape info
|
|
ULandscapeInfo::RecreateLandscapeInfo(GetWorld(), false);
|
|
FixupWeightmaps();
|
|
InitializeProxyLayersWeightmapUsage();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LandscapeActor = nullptr;
|
|
}
|
|
}
|
|
else if (PropertyName == FName(TEXT("LandscapeMaterial")) || PropertyName == FName(TEXT("LandscapeHoleMaterial")) || PropertyName == FName(TEXT("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<FWorldPartitionHandle>& 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<ALandscapeProxy>(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<ILandscapeSplineInterface> SplineOwner : SplineActors)
|
|
{
|
|
// Only check for ALandscapeSplineActor type because ALandscapeProxy also implement the ILandscapeSplineInterface for non WP worlds
|
|
if(ALandscapeSplineActor* SplineActor = Cast<ALandscapeSplineActor>(SplineOwner.GetObject()))
|
|
{
|
|
check(!SplineActor->IsPendingKill());
|
|
UndeletedSplineCount++;
|
|
}
|
|
}
|
|
|
|
// Then check for Unloaded Splines
|
|
for (const FWorldPartitionHandle& Handle : SplineHandles)
|
|
{
|
|
ALandscapeSplineActor* SplineActor = Cast<ALandscapeSplineActor>(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<int32>(MaxLODLevel, -1, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
|
|
bPropagateToProxies = true;
|
|
}
|
|
else if (PropertyName == FName(TEXT("ComponentScreenSizeToUseSubSections")))
|
|
{
|
|
ComponentScreenSizeToUseSubSections = FMath::Clamp<float>(ComponentScreenSizeToUseSubSections, 0.01f, 1.0f);
|
|
bPropagateToProxies = true;
|
|
}
|
|
else if (PropertyName == FName(TEXT("LODDistributionSetting")))
|
|
{
|
|
LODDistributionSetting = FMath::Clamp<float>(LODDistributionSetting, 1.0f, 10.0f);
|
|
bPropagateToProxies = true;
|
|
}
|
|
else if (PropertyName == FName(TEXT("LOD0DistributionSetting")))
|
|
{
|
|
LOD0DistributionSetting = FMath::Clamp<float>(LOD0DistributionSetting, 1.0f, 10.0f);
|
|
bPropagateToProxies = true;
|
|
}
|
|
else if (PropertyName == FName(TEXT("LOD0ScreenSize")))
|
|
{
|
|
LOD0ScreenSize = FMath::Clamp<float>(LOD0ScreenSize, 0.1f, 10.0f);
|
|
bPropagateToProxies = true;
|
|
}
|
|
else if (PropertyName == FName(TEXT("CollisionMipLevel")))
|
|
{
|
|
CollisionMipLevel = FMath::Clamp<int32>(CollisionMipLevel, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
|
|
bPropagateToProxies = true;
|
|
}
|
|
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, DefaultPhysMaterial))
|
|
{
|
|
bPropagateToProxies = true;
|
|
}
|
|
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, SimpleCollisionMipLevel))
|
|
{
|
|
SimpleCollisionMipLevel = FMath::Clamp<int32>(SimpleCollisionMipLevel, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
|
|
bPropagateToProxies = true;
|
|
}
|
|
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, bBakeMaterialPositionOffsetIntoCollision))
|
|
{
|
|
bPropagateToProxies = true;
|
|
}
|
|
else if (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<int32>(StaticLightingLOD, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
|
|
bChangedLighting = true;
|
|
}
|
|
else if (GIsEditor && PropertyName == GET_MEMBER_NAME_CHECKED(ALandscapeProxy, ExportLOD))
|
|
{
|
|
ExportLOD = FMath::Clamp<int32>(ExportLOD, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
|
|
}
|
|
|
|
// Must do this *after* clamping values
|
|
Super::PostEditChangeProperty(PropertyChangedEvent);
|
|
|
|
bPropagateToProxies = bPropagateToProxies || bNeedsRecalcBoundingBox || bChangedLighting;
|
|
|
|
if (Info != nullptr)
|
|
{
|
|
if (bPropagateToProxies)
|
|
{
|
|
// Propagate Event to Proxies...
|
|
for (ALandscapeProxy* Proxy : Info->Proxies)
|
|
{
|
|
Proxy->GetSharedProperties(this);
|
|
Proxy->PostEditChangeProperty(PropertyChangedEvent);
|
|
}
|
|
}
|
|
|
|
// Update normals if DrawScale3D is changed
|
|
if (MemberPropertyName == FName(TEXT("RelativeScale3D")))
|
|
{
|
|
FLandscapeEditDataInterface LandscapeEdit(Info);
|
|
LandscapeEdit.RecalculateNormals();
|
|
}
|
|
|
|
if (bNeedsRecalcBoundingBox || ChangedMaterial || bChangedLighting)
|
|
{
|
|
// We cannot iterate the XYtoComponentMap directly because reregistering components modifies the array.
|
|
TArray<ULandscapeComponent*> AllComponents;
|
|
Info->XYtoComponentMap.GenerateValueArray(AllComponents);
|
|
for (ULandscapeComponent* Comp : AllComponents)
|
|
{
|
|
if (ensure(Comp))
|
|
{
|
|
Comp->Modify();
|
|
|
|
if (bNeedsRecalcBoundingBox)
|
|
{
|
|
Comp->UpdateCachedBounds();
|
|
Comp->UpdateBounds();
|
|
}
|
|
|
|
if (bChangedLighting)
|
|
{
|
|
Comp->InvalidateLightingCache();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ChangedMaterial)
|
|
{
|
|
UpdateAllComponentMaterialInstances();
|
|
|
|
UWorld* World = GetWorld();
|
|
|
|
if (World != nullptr && 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<ALandscapeGizmoActiveActor>(GetWorld()))
|
|
{
|
|
Gizmo->MarkComponentsRenderStateDirty();
|
|
}
|
|
}
|
|
|
|
// Must be done after the AActor::PostEditChange as we depend on the relinking of the landscapeInfo->LandscapeActor
|
|
if (ChangedMaterial)
|
|
{
|
|
LandscapeMaterialChangedDelegate.Broadcast();
|
|
}
|
|
}
|
|
|
|
PreEditLandscapeMaterial = nullptr;
|
|
PreEditLandscapeHoleMaterial = nullptr;
|
|
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<int32>(ForcedLOD, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
|
|
}
|
|
else
|
|
{
|
|
ForcedLOD = -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int32 MaxLOD = FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1;
|
|
LODBias = FMath::Clamp<int32>(InLODValue, -MaxLOD, MaxLOD);
|
|
}
|
|
|
|
InvalidateLightingCache();
|
|
MarkRenderStateDirty();
|
|
|
|
// 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<int32>(LightingLODBias, -1, MaxLOD);
|
|
InvalidateLightingCache();
|
|
}
|
|
else if (GIsEditor &&
|
|
(PropertyName == GET_MEMBER_NAME_CHECKED(ULandscapeComponent, CollisionMipLevel) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(ULandscapeComponent, SimpleCollisionMipLevel)))
|
|
{
|
|
CollisionMipLevel = FMath::Clamp<int32>(CollisionMipLevel, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
|
|
SimpleCollisionMipLevel = FMath::Clamp<int32>(SimpleCollisionMipLevel, 0, FMath::CeilLogTwo(SubsectionSizeQuads + 1) - 1);
|
|
if (PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive)
|
|
{
|
|
DestroyCollisionData();
|
|
UpdateCollisionData(); // Rebuild for new CollisionMipLevel
|
|
}
|
|
}
|
|
|
|
// Must do this *after* clamping values
|
|
Super::PostEditChangeProperty(PropertyChangedEvent);
|
|
}
|
|
|
|
TSet<class ULandscapeComponent*> ULandscapeInfo::GetSelectedComponents() const
|
|
{
|
|
return SelectedComponents;
|
|
}
|
|
|
|
TSet<class ULandscapeComponent*> ULandscapeInfo::GetSelectedRegionComponents() const
|
|
{
|
|
return SelectedRegionComponents;
|
|
}
|
|
|
|
void ULandscapeInfo::UpdateSelectedComponents(TSet<ULandscapeComponent*>& NewComponents, bool bIsComponentwise /*=true*/)
|
|
{
|
|
int32 InSelectType = bIsComponentwise ? FLandscapeEditToolRenderData::ST_COMPONENT : FLandscapeEditToolRenderData::ST_REGION;
|
|
|
|
if (bIsComponentwise)
|
|
{
|
|
for (TSet<ULandscapeComponent*>::TIterator It(NewComponents); It; ++It)
|
|
{
|
|
ULandscapeComponent* Comp = *It;
|
|
if ((Comp->EditToolRenderData.SelectedType & InSelectType) == 0)
|
|
{
|
|
Comp->Modify(false);
|
|
int32 SelectedType = Comp->EditToolRenderData.SelectedType;
|
|
SelectedType |= InSelectType;
|
|
Comp->EditToolRenderData.UpdateSelectionMaterial(SelectedType, Comp);
|
|
Comp->UpdateEditToolRenderData();
|
|
}
|
|
}
|
|
|
|
// Remove the material from any old components that are no longer in the region
|
|
TSet<ULandscapeComponent*> RemovedComponents = SelectedComponents.Difference(NewComponents);
|
|
for (TSet<ULandscapeComponent*>::TIterator It(RemovedComponents); It; ++It)
|
|
{
|
|
ULandscapeComponent* Comp = *It;
|
|
Comp->Modify(false);
|
|
int32 SelectedType = Comp->EditToolRenderData.SelectedType;
|
|
SelectedType &= ~InSelectType;
|
|
Comp->EditToolRenderData.UpdateSelectionMaterial(SelectedType, Comp);
|
|
Comp->UpdateEditToolRenderData();
|
|
}
|
|
SelectedComponents = NewComponents;
|
|
}
|
|
else
|
|
{
|
|
// Only add components...
|
|
if (NewComponents.Num())
|
|
{
|
|
for (TSet<ULandscapeComponent*>::TIterator It(NewComponents); It; ++It)
|
|
{
|
|
ULandscapeComponent* Comp = *It;
|
|
if ((Comp->EditToolRenderData.SelectedType & InSelectType) == 0)
|
|
{
|
|
Comp->Modify(false);
|
|
int32 SelectedType = Comp->EditToolRenderData.SelectedType;
|
|
SelectedType |= InSelectType;
|
|
Comp->EditToolRenderData.UpdateSelectionMaterial(SelectedType, Comp);
|
|
Comp->UpdateEditToolRenderData();
|
|
}
|
|
|
|
SelectedRegionComponents.Add(*It);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Remove the material from any old components that are no longer in the region
|
|
for (TSet<ULandscapeComponent*>::TIterator It(SelectedRegionComponents); It; ++It)
|
|
{
|
|
ULandscapeComponent* Comp = *It;
|
|
Comp->Modify(false);
|
|
int32 SelectedType = Comp->EditToolRenderData.SelectedType;
|
|
SelectedType &= ~InSelectType;
|
|
Comp->EditToolRenderData.UpdateSelectionMaterial(SelectedType, Comp);
|
|
Comp->UpdateEditToolRenderData();
|
|
}
|
|
SelectedRegionComponents = NewComponents;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeInfo::ClearSelectedRegion(bool bIsComponentwise /*= true*/)
|
|
{
|
|
TSet<ULandscapeComponent*> NewComponents;
|
|
UpdateSelectedComponents(NewComponents, bIsComponentwise);
|
|
if (!bIsComponentwise)
|
|
{
|
|
SelectedRegion.Empty();
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::ReallocateWeightmaps(FLandscapeEditDataInterface* DataInterface, bool InCanUseEditingWeightmap, bool InSaveToTransactionBuffer, bool InForceReallocate, ALandscapeProxy* InTargetProxy, TArray<UTexture*>* OutNewCreatedTextures)
|
|
{
|
|
int32 NeededNewChannels = 0;
|
|
ALandscapeProxy* TargetProxy = InTargetProxy ? InTargetProxy : GetLandscapeProxy();
|
|
|
|
FGuid EditingLayerGUID = GetEditingLayerGUID();
|
|
check(!TargetProxy->HasLayersContent() || !InCanUseEditingWeightmap || EditingLayerGUID.IsValid());
|
|
FGuid TargetLayerGuid = InCanUseEditingWeightmap ? EditingLayerGUID : FGuid();
|
|
|
|
TArray<FWeightmapLayerAllocationInfo>& ComponentWeightmapLayerAllocations = GetWeightmapLayerAllocations(InCanUseEditingWeightmap);
|
|
TArray<UTexture2D*>& ComponentWeightmapTextures = GetWeightmapTextures(InCanUseEditingWeightmap);
|
|
TArray<ULandscapeWeightmapUsage*>& ComponentWeightmapTexturesUsage = GetWeightmapTexturesUsage(InCanUseEditingWeightmap);
|
|
|
|
// When force reallocating, skip tests to see if allocations are necessary based on Component's WeightmapLayeAllocInfo
|
|
if (!InForceReallocate)
|
|
{
|
|
for (int32 LayerIdx = 0; LayerIdx < ComponentWeightmapLayerAllocations.Num(); LayerIdx++)
|
|
{
|
|
if (!ComponentWeightmapLayerAllocations[LayerIdx].IsAllocated())
|
|
{
|
|
NeededNewChannels++;
|
|
}
|
|
}
|
|
|
|
// All channels allocated!
|
|
if (NeededNewChannels == 0)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool bMarkPackageDirty = DataInterface == nullptr ? true : DataInterface->GetShouldDirtyPackage();
|
|
if (InSaveToTransactionBuffer)
|
|
{
|
|
Modify(bMarkPackageDirty);
|
|
TargetProxy->Modify(bMarkPackageDirty);
|
|
}
|
|
|
|
if (!InForceReallocate)
|
|
{
|
|
// UE_LOG(LogLandscape, Log, TEXT("----------------------"));
|
|
// UE_LOG(LogLandscape, Log, TEXT("Component %s needs %d layers (%d new)"), *GetName(), WeightmapLayerAllocations.Num(), NeededNewChannels);
|
|
|
|
// See if our existing textures have sufficient space
|
|
int32 ExistingTexAvailableChannels = 0;
|
|
for (int32 TexIdx = 0; TexIdx < ComponentWeightmapTextures.Num(); TexIdx++)
|
|
{
|
|
ULandscapeWeightmapUsage* Usage = ComponentWeightmapTexturesUsage[TexIdx];
|
|
check(Usage);
|
|
check(Usage->LayerGuid == TargetLayerGuid);
|
|
ExistingTexAvailableChannels += Usage->FreeChannelCount();
|
|
|
|
if (ExistingTexAvailableChannels >= NeededNewChannels)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ExistingTexAvailableChannels >= NeededNewChannels)
|
|
{
|
|
// UE_LOG(LogLandscape, Log, TEXT("Existing texture has available channels"));
|
|
|
|
// Allocate using our existing textures' spare channels.
|
|
int32 CurrentAlloc = 0;
|
|
for (int32 TexIdx = 0; TexIdx < ComponentWeightmapTextures.Num(); TexIdx++)
|
|
{
|
|
ULandscapeWeightmapUsage* Usage = ComponentWeightmapTexturesUsage[TexIdx];
|
|
|
|
for (int32 ChanIdx = 0; ChanIdx < 4; ChanIdx++)
|
|
{
|
|
if (Usage->ChannelUsage[ChanIdx] == nullptr)
|
|
{
|
|
// Find next allocation to treat
|
|
for (; CurrentAlloc < ComponentWeightmapLayerAllocations.Num(); ++CurrentAlloc)
|
|
{
|
|
FWeightmapLayerAllocationInfo& AllocInfo = ComponentWeightmapLayerAllocations[CurrentAlloc];
|
|
|
|
if (!AllocInfo.IsAllocated())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
FWeightmapLayerAllocationInfo& AllocInfo = ComponentWeightmapLayerAllocations[CurrentAlloc];
|
|
check(!AllocInfo.IsAllocated());
|
|
|
|
// Zero out the data for this texture channel
|
|
if (DataInterface)
|
|
{
|
|
DataInterface->ZeroTextureChannel(ComponentWeightmapTextures[TexIdx], ChanIdx);
|
|
}
|
|
|
|
AllocInfo.WeightmapTextureIndex = TexIdx;
|
|
AllocInfo.WeightmapTextureChannel = ChanIdx;
|
|
|
|
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<UTexture2D*> NewWeightmapTextures;
|
|
TArray<ULandscapeWeightmapUsage*> NewComponentWeightmapTexturesUsage;
|
|
|
|
while (TotalNeededChannels > 0)
|
|
{
|
|
// UE_LOG(LogLandscape, Log, TEXT("Still need %d channels"), TotalNeededChannels);
|
|
|
|
UTexture2D* CurrentWeightmapTexture = nullptr;
|
|
ULandscapeWeightmapUsage* CurrentWeightmapUsage = nullptr;
|
|
|
|
if (TotalNeededChannels < 4)
|
|
{
|
|
// UE_LOG(LogLandscape, Log, TEXT("Looking for nearest"));
|
|
|
|
// see if we can find a suitable existing weightmap texture with sufficient channels
|
|
int32 BestDistanceSquared = MAX_int32;
|
|
for (auto& ItPair : TargetProxy->WeightmapUsageMap)
|
|
{
|
|
ULandscapeWeightmapUsage* TryWeightmapUsage = ItPair.Value;
|
|
//
|
|
if (TryWeightmapUsage->FreeChannelCount() >= TotalNeededChannels && TryWeightmapUsage->LayerGuid == TargetLayerGuid)
|
|
{
|
|
if (TryWeightmapUsage->IsEmpty())
|
|
{
|
|
CurrentWeightmapTexture = ItPair.Key;
|
|
CurrentWeightmapUsage = TryWeightmapUsage;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// See if this candidate is closer than any others we've found
|
|
for (int32 ChanIdx = 0; ChanIdx < ULandscapeWeightmapUsage::NumChannels; ChanIdx++)
|
|
{
|
|
if (TryWeightmapUsage->ChannelUsage[ChanIdx] != nullptr)
|
|
{
|
|
int32 TryDistanceSquared = (TryWeightmapUsage->ChannelUsage[ChanIdx]->GetSectionBase() - GetSectionBase()).SizeSquared();
|
|
if (TryDistanceSquared < BestDistanceSquared)
|
|
{
|
|
CurrentWeightmapTexture = ItPair.Key;
|
|
CurrentWeightmapUsage = TryWeightmapUsage;
|
|
BestDistanceSquared = TryDistanceSquared;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool NeedsUpdateResource = false;
|
|
// No suitable weightmap texture
|
|
if (CurrentWeightmapTexture == nullptr)
|
|
{
|
|
MarkPackageDirty();
|
|
|
|
// Weightmap is sized the same as the component
|
|
int32 WeightmapSize = (SubsectionSizeQuads + 1) * NumSubsections;
|
|
|
|
// We need a new weightmap texture
|
|
CurrentWeightmapTexture = TargetProxy->CreateLandscapeTexture(WeightmapSize, WeightmapSize, TEXTUREGROUP_Terrain_Weightmap, TSF_BGRA8);
|
|
|
|
// Alloc dummy mips
|
|
CreateEmptyTextureMips(CurrentWeightmapTexture, true);
|
|
|
|
CurrentWeightmapTexture->PostEditChange();
|
|
|
|
if (OutNewCreatedTextures != nullptr)
|
|
{
|
|
OutNewCreatedTextures->Add(CurrentWeightmapTexture);
|
|
}
|
|
|
|
// Store it in the usage map
|
|
CurrentWeightmapUsage = TargetProxy->WeightmapUsageMap.Add(CurrentWeightmapTexture, TargetProxy->CreateWeightmapUsage());
|
|
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<FColor*> WeightmapTextureMipData;
|
|
WeightmapTextureMipData.AddUninitialized(NumMips);
|
|
for (int32 MipIdx = 0; MipIdx < NumMips; MipIdx++)
|
|
{
|
|
WeightmapTextureMipData[MipIdx] = (FColor*)WeightmapDataInfo->GetMipData(MipIdx);
|
|
}
|
|
|
|
ULandscapeComponent::UpdateWeightmapMips(NumSubsections, SubsectionSizeQuads, WeightmapTexture, WeightmapTextureMipData, 0, 0, MAX_int32, MAX_int32, WeightmapDataInfo);
|
|
}
|
|
}
|
|
|
|
// Replace the weightmap textures
|
|
SetWeightmapTextures(MoveTemp(NewWeightmapTextures), InCanUseEditingWeightmap);
|
|
SetWeightmapTexturesUsage(MoveTemp(NewComponentWeightmapTexturesUsage), InCanUseEditingWeightmap);
|
|
}
|
|
|
|
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<FWeightmapLayerAllocationInfo>& ComponentWeightmapLayerAllocations = GetWeightmapLayerAllocations();
|
|
TArray<UTexture2D*>& ComponentWeightmapTextures = GetWeightmapTextures();
|
|
TArray<ULandscapeWeightmapUsage*>& ComponentWeightmapTexturesUsage = GetWeightmapTexturesUsage();
|
|
|
|
// Adjust WeightmapTextureIndex index for other layers
|
|
TSet<int32> UnUsedTextureIndices;
|
|
{
|
|
TSet<int32> UsedTextureIndices;
|
|
for (int32 LayerIdx = 0; LayerIdx < ComponentWeightmapLayerAllocations.Num(); LayerIdx++)
|
|
{
|
|
UsedTextureIndices.Add(ComponentWeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex);
|
|
}
|
|
|
|
for (int32 WeightIdx = 0; WeightIdx < ComponentWeightmapTextures.Num(); ++WeightIdx)
|
|
{
|
|
if (!UsedTextureIndices.Contains(WeightIdx))
|
|
{
|
|
UnUsedTextureIndices.Add(WeightIdx);
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 RemovedTextures = 0;
|
|
for (int32 UnusedIndex : UnUsedTextureIndices)
|
|
{
|
|
int32 WeightmapTextureIndexToRemove = UnusedIndex - RemovedTextures;
|
|
ComponentWeightmapTextures[WeightmapTextureIndexToRemove]->SetFlags(RF_Transactional);
|
|
ComponentWeightmapTextures[WeightmapTextureIndexToRemove]->Modify();
|
|
ComponentWeightmapTextures[WeightmapTextureIndexToRemove]->MarkPackageDirty();
|
|
ComponentWeightmapTextures[WeightmapTextureIndexToRemove]->ClearFlags(RF_Standalone);
|
|
ComponentWeightmapTextures.RemoveAt(WeightmapTextureIndexToRemove);
|
|
|
|
ComponentWeightmapTexturesUsage.RemoveAt(WeightmapTextureIndexToRemove);
|
|
|
|
// Adjust WeightmapTextureIndex index for other layers
|
|
for (int32 LayerIdx = 0; LayerIdx < ComponentWeightmapLayerAllocations.Num(); LayerIdx++)
|
|
{
|
|
FWeightmapLayerAllocationInfo& Allocation = ComponentWeightmapLayerAllocations[LayerIdx];
|
|
|
|
if (Allocation.WeightmapTextureIndex > WeightmapTextureIndexToRemove)
|
|
{
|
|
Allocation.WeightmapTextureIndex--;
|
|
}
|
|
|
|
checkSlow(Allocation.WeightmapTextureIndex < WeightmapTextures.Num());
|
|
}
|
|
RemovedTextures++;
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::InitHeightmapData(TArray<FColor>& Heights, bool bUpdateCollision)
|
|
{
|
|
int32 ComponentSizeVerts = NumSubsections * (SubsectionSizeQuads + 1);
|
|
|
|
if (Heights.Num() != FMath::Square(ComponentSizeVerts))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Handling old Height map....
|
|
if (HeightmapTexture && HeightmapTexture->GetOutermost() != GetTransientPackage()
|
|
&& HeightmapTexture->GetOutermost() == GetOutermost()
|
|
&& HeightmapTexture->Source.GetSizeX() >= ComponentSizeVerts) // if Height map is not valid...
|
|
{
|
|
HeightmapTexture->SetFlags(RF_Transactional);
|
|
HeightmapTexture->Modify();
|
|
HeightmapTexture->MarkPackageDirty();
|
|
HeightmapTexture->ClearFlags(RF_Standalone); // Delete if no reference...
|
|
}
|
|
|
|
// New Height map
|
|
TArray<FColor*> HeightmapTextureMipData;
|
|
// make sure the heightmap UVs are powers of two.
|
|
int32 HeightmapSizeU = (1 << FMath::CeilLogTwo(ComponentSizeVerts));
|
|
int32 HeightmapSizeV = (1 << FMath::CeilLogTwo(ComponentSizeVerts));
|
|
|
|
// Height map construction
|
|
SetHeightmap(GetLandscapeProxy()->CreateLandscapeTexture(HeightmapSizeU, HeightmapSizeV, TEXTUREGROUP_Terrain_Heightmap, TSF_BGRA8));
|
|
|
|
int32 MipSubsectionSizeQuads = SubsectionSizeQuads;
|
|
int32 MipSizeU = HeightmapSizeU;
|
|
int32 MipSizeV = HeightmapSizeV;
|
|
|
|
HeightmapScaleBias = FVector4(1.0f / (float)HeightmapSizeU, 1.0f / (float)HeightmapSizeV, 0.0f, 0.0f);
|
|
|
|
int32 Mip = 0;
|
|
while (MipSizeU > 1 && MipSizeV > 1 && MipSubsectionSizeQuads >= 1)
|
|
{
|
|
FColor* HeightmapTextureData = (FColor*)GetHeightmap()->Source.LockMip(Mip);
|
|
if (Mip == 0)
|
|
{
|
|
FMemory::Memcpy(HeightmapTextureData, Heights.GetData(), MipSizeU*MipSizeV*sizeof(FColor));
|
|
}
|
|
else
|
|
{
|
|
FMemory::Memzero(HeightmapTextureData, MipSizeU*MipSizeV*sizeof(FColor));
|
|
}
|
|
HeightmapTextureMipData.Add(HeightmapTextureData);
|
|
|
|
MipSizeU >>= 1;
|
|
MipSizeV >>= 1;
|
|
Mip++;
|
|
|
|
MipSubsectionSizeQuads = ((MipSubsectionSizeQuads + 1) >> 1) - 1;
|
|
}
|
|
ULandscapeComponent::GenerateHeightmapMips(HeightmapTextureMipData);
|
|
|
|
if (bUpdateCollision)
|
|
{
|
|
UpdateCollisionHeightData(
|
|
HeightmapTextureMipData[CollisionMipLevel],
|
|
SimpleCollisionMipLevel > CollisionMipLevel ? HeightmapTextureMipData[SimpleCollisionMipLevel] : nullptr);
|
|
}
|
|
|
|
for (int32 i = 0; i < HeightmapTextureMipData.Num(); i++)
|
|
{
|
|
GetHeightmap()->Source.UnlockMip(i);
|
|
}
|
|
GetHeightmap()->PostEditChange();
|
|
}
|
|
|
|
void ULandscapeComponent::InitWeightmapData(TArray<ULandscapeLayerInfoObject*>& LayerInfos, TArray<TArray<uint8> >& WeightmapData)
|
|
{
|
|
if (LayerInfos.Num() != WeightmapData.Num() || LayerInfos.Num() <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int32 ComponentSizeVerts = NumSubsections * (SubsectionSizeQuads + 1);
|
|
|
|
// Validation..
|
|
for (int32 Idx = 0; Idx < WeightmapData.Num(); ++Idx)
|
|
{
|
|
if (WeightmapData[Idx].Num() != FMath::Square(ComponentSizeVerts))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (int32 Idx = 0; Idx < WeightmapTextures.Num(); ++Idx)
|
|
{
|
|
if (WeightmapTextures[Idx] && WeightmapTextures[Idx]->GetOutermost() != GetTransientPackage()
|
|
&& WeightmapTextures[Idx]->GetOutermost() == GetOutermost()
|
|
&& WeightmapTextures[Idx]->Source.GetSizeX() == ComponentSizeVerts)
|
|
{
|
|
WeightmapTextures[Idx]->SetFlags(RF_Transactional);
|
|
WeightmapTextures[Idx]->Modify();
|
|
WeightmapTextures[Idx]->MarkPackageDirty();
|
|
WeightmapTextures[Idx]->ClearFlags(RF_Standalone); // Delete if no reference...
|
|
}
|
|
}
|
|
WeightmapTextures.Empty();
|
|
|
|
WeightmapLayerAllocations.Empty(LayerInfos.Num());
|
|
for (int32 Idx = 0; Idx < LayerInfos.Num(); ++Idx)
|
|
{
|
|
new (WeightmapLayerAllocations)FWeightmapLayerAllocationInfo(LayerInfos[Idx]);
|
|
}
|
|
|
|
ReallocateWeightmaps();
|
|
|
|
check(WeightmapLayerAllocations.Num() > 0 && WeightmapTextures.Num() > 0);
|
|
|
|
int32 WeightmapSize = ComponentSizeVerts;
|
|
WeightmapScaleBias = FVector4(1.0f / (float)WeightmapSize, 1.0f / (float)WeightmapSize, 0.5f / (float)WeightmapSize, 0.5f / (float)WeightmapSize);
|
|
WeightmapSubsectionOffset = (float)(SubsectionSizeQuads + 1) / (float)WeightmapSize;
|
|
|
|
TArray<void*> WeightmapDataPtrs;
|
|
WeightmapDataPtrs.AddUninitialized(WeightmapTextures.Num());
|
|
for (int32 WeightmapIdx = 0; WeightmapIdx < WeightmapTextures.Num(); ++WeightmapIdx)
|
|
{
|
|
WeightmapDataPtrs[WeightmapIdx] = WeightmapTextures[WeightmapIdx]->Source.LockMip(0);
|
|
}
|
|
|
|
for (int32 LayerIdx = 0; LayerIdx < WeightmapLayerAllocations.Num(); ++LayerIdx)
|
|
{
|
|
void* DestDataPtr = WeightmapDataPtrs[WeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex];
|
|
uint8* DestTextureData = (uint8*)DestDataPtr + ChannelOffsets[WeightmapLayerAllocations[LayerIdx].WeightmapTextureChannel];
|
|
uint8* SrcTextureData = (uint8*)&WeightmapData[LayerIdx][0];
|
|
|
|
for (int32 i = 0; i < WeightmapData[LayerIdx].Num(); i++)
|
|
{
|
|
DestTextureData[i * 4] = SrcTextureData[i];
|
|
}
|
|
}
|
|
|
|
for (int32 Idx = 0; Idx < WeightmapTextures.Num(); Idx++)
|
|
{
|
|
UTexture2D* WeightmapTexture = WeightmapTextures[Idx];
|
|
WeightmapTexture->Source.UnlockMip(0);
|
|
}
|
|
|
|
for (int32 Idx = 0; Idx < WeightmapTextures.Num(); Idx++)
|
|
{
|
|
UTexture2D* WeightmapTexture = WeightmapTextures[Idx];
|
|
{
|
|
const bool bShouldDirtyPackage = true;
|
|
FLandscapeTextureDataInfo WeightmapDataInfo(WeightmapTexture, bShouldDirtyPackage);
|
|
|
|
int32 NumMips = WeightmapTexture->Source.GetNumMips();
|
|
TArray<FColor*> WeightmapTextureMipData;
|
|
WeightmapTextureMipData.AddUninitialized(NumMips);
|
|
for (int32 MipIdx = 0; MipIdx < NumMips; MipIdx++)
|
|
{
|
|
WeightmapTextureMipData[MipIdx] = (FColor*)WeightmapDataInfo.GetMipData(MipIdx);
|
|
}
|
|
|
|
ULandscapeComponent::UpdateWeightmapMips(NumSubsections, SubsectionSizeQuads, WeightmapTexture, WeightmapTextureMipData, 0, 0, MAX_int32, MAX_int32, &WeightmapDataInfo);
|
|
}
|
|
|
|
WeightmapTexture->PostEditChange();
|
|
}
|
|
|
|
FlushRenderingCommands();
|
|
|
|
MaterialInstances.Empty(1);
|
|
MaterialInstances.Add(nullptr);
|
|
|
|
LODIndexToMaterialIndex.Empty(1);
|
|
LODIndexToMaterialIndex.Add(0);
|
|
|
|
// TODO: need to update layer system?
|
|
}
|
|
|
|
#define MAX_LANDSCAPE_EXPORT_COMPONENTS_NUM 16
|
|
#define MAX_LANDSCAPE_PROP_TEXT_LENGTH 1024*1024*16
|
|
|
|
bool ALandscapeProxy::ShouldExport()
|
|
{
|
|
if (!bIsMovingToLevel && LandscapeComponents.Num() > MAX_LANDSCAPE_EXPORT_COMPONENTS_NUM)
|
|
{
|
|
// Prompt to save startup packages
|
|
if (EAppReturnType::Yes == FMessageDialog::Open(EAppMsgType::YesNo, FText::Format(
|
|
NSLOCTEXT("UnrealEd", "LandscapeExport_Warning", "Landscape has large number({0}) of components, so it will use large amount memory to copy it to the clipboard. Do you want to proceed?"), FText::AsNumber(LandscapeComponents.Num()))))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ALandscapeProxy::ShouldImport(FString* ActorPropString, bool IsMovingToLevel)
|
|
{
|
|
bIsMovingToLevel = IsMovingToLevel;
|
|
if (!bIsMovingToLevel && ActorPropString && ActorPropString->Len() > MAX_LANDSCAPE_PROP_TEXT_LENGTH)
|
|
{
|
|
// Prompt to save startup packages
|
|
if (EAppReturnType::Yes == FMessageDialog::Open(EAppMsgType::YesNo, FText::Format(
|
|
NSLOCTEXT("UnrealEd", "LandscapeImport_Warning", "Landscape is about to import large amount memory ({0}MB) from the clipboard, which will take some time. Do you want to proceed?"), FText::AsNumber(ActorPropString->Len() >> 20))))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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<FColor> 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<uint8> 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<FColor> 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<ULandscapeLayerInfoObject*> LayerInfos;
|
|
LayerInfos.Empty(LayerNum);
|
|
TArray<TArray<uint8>> WeightmapData;
|
|
for (int32 LayerIndex = 0; LayerIndex < LayerNum; ++LayerIndex)
|
|
{
|
|
TArray<uint8> 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<ULandscapeLayerInfoObject>(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<FName>& OutLayerNames, UMaterial* InMaterial)
|
|
{
|
|
TArray<FMaterialParameterInfo> ParameterInfos;
|
|
TArray<FGuid> ParameterIds;
|
|
|
|
TArray<UMaterialExpression*> ES31Expressions;
|
|
InMaterial->GetAllReferencedExpressions(ES31Expressions, nullptr, ERHIFeatureLevel::ES3_1);
|
|
|
|
TArray<UMaterialExpression*> MobileExpressions = MoveTemp(ES31Expressions);
|
|
|
|
for (UMaterialExpression* Expression : MobileExpressions)
|
|
{
|
|
UMaterialExpressionLandscapeLayerWeight* LayerWeightExpression = Cast<UMaterialExpressionLandscapeLayerWeight>(Expression);
|
|
UMaterialExpressionLandscapeLayerSwitch* LayerSwitchExpression = Cast<UMaterialExpressionLandscapeLayerSwitch>(Expression);
|
|
UMaterialExpressionLandscapeLayerSample* LayerSampleExpression = Cast<UMaterialExpressionLandscapeLayerSample>(Expression);
|
|
UMaterialExpressionLandscapeLayerBlend* LayerBlendExpression = Cast<UMaterialExpressionLandscapeLayerBlend>(Expression);
|
|
UMaterialExpressionLandscapeVisibilityMask* VisibilityMaskExpression = Cast<UMaterialExpressionLandscapeVisibilityMask>(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<FName> 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<ULandscapeMaterialInstanceConstant>(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<uint8> const& InVisibilityData, TArray<FIntBox2D>& OutHoleBounds)
|
|
{
|
|
check(InVisibilityData.Num() == InSize * InSize);
|
|
|
|
TArray<uint32> HoleSegmentLabels;
|
|
HoleSegmentLabels.AddZeroed(InSize*InSize);
|
|
|
|
TArray<uint32, TInlineAllocator<32>> LabelEquivalenceMap;
|
|
LabelEquivalenceMap.Add(0);
|
|
uint32 NextLabel = 1;
|
|
|
|
// First pass fills HoleSegmentLabels with labels.
|
|
for (int32 y = 0; y < InSize; ++y)
|
|
{
|
|
for (int32 x = 0; x < InSize; ++x)
|
|
{
|
|
const uint8 VisThreshold = 170;
|
|
const bool bIsHole = InVisibilityData[y * InSize + x] >= VisThreshold;
|
|
if (bIsHole)
|
|
{
|
|
uint8 WestLabel = (x > 0) ? HoleSegmentLabels[y * InSize + x - 1] : 0;
|
|
uint8 NorthLabel = (y > 0) ? HoleSegmentLabels[(y - 1) * InSize + x] : 0;
|
|
|
|
if (WestLabel != 0 && NorthLabel != 0 && WestLabel != NorthLabel)
|
|
{
|
|
uint32 MinLabel = FMath::Min(WestLabel, NorthLabel);
|
|
uint32 MaxLabel = FMath::Max(WestLabel, NorthLabel);
|
|
LabelEquivalenceMap[MaxLabel] = MinLabel;
|
|
HoleSegmentLabels[y * InSize + x] = MinLabel;
|
|
}
|
|
else if (WestLabel != 0)
|
|
{
|
|
HoleSegmentLabels[y * InSize + x] = WestLabel;
|
|
}
|
|
else if (NorthLabel != 0)
|
|
{
|
|
HoleSegmentLabels[y * InSize + x] = NorthLabel;
|
|
}
|
|
else
|
|
{
|
|
LabelEquivalenceMap.Add(NextLabel);
|
|
HoleSegmentLabels[y * InSize + x] = NextLabel++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Resolve label equivalences.
|
|
for (int32 Index = 0; Index < LabelEquivalenceMap.Num(); ++ Index)
|
|
{
|
|
int32 CommonIndex = Index;
|
|
while (LabelEquivalenceMap[CommonIndex] != CommonIndex)
|
|
{
|
|
CommonIndex = LabelEquivalenceMap[CommonIndex];
|
|
}
|
|
LabelEquivalenceMap[Index] = CommonIndex;
|
|
}
|
|
|
|
// Flatten labels to be contiguous.
|
|
int32 NumLabels = 0;
|
|
for (int32 Index = 0; Index < LabelEquivalenceMap.Num(); ++Index)
|
|
{
|
|
if (LabelEquivalenceMap[Index] == Index)
|
|
{
|
|
LabelEquivalenceMap[Index] = NumLabels++;
|
|
}
|
|
else
|
|
{
|
|
LabelEquivalenceMap[Index] = LabelEquivalenceMap[LabelEquivalenceMap[Index]];
|
|
}
|
|
}
|
|
|
|
// Second pass finds bounds for each label.
|
|
// Could also write contiguous labels to HoleSegmentLabels here if we want to keep that info.
|
|
OutHoleBounds.AddDefaulted(NumLabels);
|
|
for (int32 y = 0; y < InSize - 1; ++y)
|
|
{
|
|
for (int32 x = 0; x < InSize - 1; ++x)
|
|
{
|
|
const int32 Index = InSize * y + x;
|
|
const int32 Label = LabelEquivalenceMap[HoleSegmentLabels[Index]];
|
|
OutHoleBounds[Label].Add(FIntPoint(x, y));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Move vertex index up to the next location which obeys the condition:
|
|
// PosAt(VertexIndex, LodIndex) > PosAt(VertexIndex - 1, LodIndex + 1)
|
|
// Maths derived from pattern when analyzing a spreadsheet containing a dump of lod vertex positions.
|
|
inline void AlignVertexDown(int32 InLodIndex, int32& InOutVertexIndex)
|
|
{
|
|
const int32 Offset = InOutVertexIndex & ((2 << InLodIndex) - 1);
|
|
if (Offset < (1 << InLodIndex))
|
|
{
|
|
InOutVertexIndex -= Offset;
|
|
}
|
|
}
|
|
|
|
// Move vertex index up to the next location which obeys the condition:
|
|
// PosAt(VertexIndex, LodIndex) < PosAt(VertexIndex + 1, LodIndex + 1)
|
|
// Maths derived from pattern when analyzing a spreadsheet containing a dump of lod vertex positions.
|
|
inline void AlignVertexUp(int32 InLodIndex, int32& InOutVertexIndex)
|
|
{
|
|
const int32 Offset = (InOutVertexIndex + 1) & ((2 << InLodIndex) - 1);
|
|
if (Offset > (1 << InLodIndex))
|
|
{
|
|
InOutVertexIndex += (1 << InLodIndex) - Offset;
|
|
}
|
|
}
|
|
|
|
// Expand bounding rectangles from LodIndex-1 to LodIndex
|
|
void ExpandBoundsForLod(int32 InSize, int32 InLodIndex, TArray<FIntBox2D> const& InHoleBounds, TArray<FIntBox2D>& OutHoleBounds)
|
|
{
|
|
OutHoleBounds.AddZeroed(InHoleBounds.Num());
|
|
for (int32 i = 0; i < InHoleBounds.Num(); ++i)
|
|
{
|
|
// Expand
|
|
const int32 ExpandDistance = (2 << InLodIndex) - 1;
|
|
OutHoleBounds[i].Min.X = InHoleBounds[i].Min.X - ExpandDistance;
|
|
OutHoleBounds[i].Min.Y = InHoleBounds[i].Min.Y - ExpandDistance;
|
|
OutHoleBounds[i].Max.X = InHoleBounds[i].Max.X + ExpandDistance;
|
|
OutHoleBounds[i].Max.Y = InHoleBounds[i].Max.Y + ExpandDistance;
|
|
|
|
// Snap to continuous LOD borders so that consecutive vertices with different LODs don't overlap
|
|
if (InLodIndex > 0)
|
|
{
|
|
AlignVertexDown(InLodIndex, OutHoleBounds[i].Min.X);
|
|
AlignVertexDown(InLodIndex, OutHoleBounds[i].Min.Y);
|
|
AlignVertexUp(InLodIndex, OutHoleBounds[i].Max.X);
|
|
AlignVertexUp(InLodIndex, OutHoleBounds[i].Max.Y);
|
|
}
|
|
|
|
// Clamp to edges
|
|
OutHoleBounds[i].Min.X = FMath::Max(OutHoleBounds[i].Min.X, 0);
|
|
OutHoleBounds[i].Max.X = FMath::Min(OutHoleBounds[i].Max.X, InSize - 1);
|
|
OutHoleBounds[i].Min.Y = FMath::Max(OutHoleBounds[i].Min.Y, 0);
|
|
OutHoleBounds[i].Max.Y = FMath::Min(OutHoleBounds[i].Max.Y, InSize - 1);
|
|
}
|
|
}
|
|
|
|
// Combine intersecting bounding rectangles into to form their bounding rectangles.
|
|
void CombineIntersectingBounds(TArray<FIntBox2D>& InOutHoleBounds)
|
|
{
|
|
int i = 1;
|
|
while (i < InOutHoleBounds.Num())
|
|
{
|
|
int j = i + 1;
|
|
for (; j < InOutHoleBounds.Num(); ++j)
|
|
{
|
|
if (InOutHoleBounds[i].Intersects(InOutHoleBounds[j]))
|
|
{
|
|
InOutHoleBounds[i].Add(InOutHoleBounds[j]);
|
|
InOutHoleBounds.RemoveAtSwap(j);
|
|
break;
|
|
}
|
|
}
|
|
if (j == InOutHoleBounds.Num())
|
|
{
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build an array with an entry per vertex which contains the Lod at which that vertex falls inside a hole bounding rectangle.
|
|
// This is the Lod at which we should clamp the vertex in the vertex shader.
|
|
void BuildHoleVertexLods(int32 InSize, int32 InNumLods, TArray<FIntBox2D> const& InHoleBounds, TArray<uint8>& OutHoleVertexLods)
|
|
{
|
|
// Generate hole bounds for each Lod level from Lod0 InHoleBounds
|
|
TArray< TArray<FIntBox2D> > HoleBoundsPerLevel;
|
|
HoleBoundsPerLevel.AddDefaulted(InNumLods);
|
|
HoleBoundsPerLevel[0] = InHoleBounds;
|
|
|
|
for (int32 LodIndex = 1; LodIndex < InNumLods; ++LodIndex)
|
|
{
|
|
ExpandBoundsForLod(InSize, LodIndex, HoleBoundsPerLevel[LodIndex - 1], HoleBoundsPerLevel[LodIndex]);
|
|
}
|
|
|
|
for (int32 LodIndex = 0; LodIndex < InNumLods; ++LodIndex)
|
|
{
|
|
CombineIntersectingBounds(HoleBoundsPerLevel[LodIndex]);
|
|
}
|
|
|
|
// Initialize output to the max Lod
|
|
OutHoleVertexLods.Init(InNumLods, InSize * InSize);
|
|
|
|
// Fill by writing each Lod level in turn
|
|
for (int32 LodIndex = InNumLods - 1; LodIndex >= 0; --LodIndex)
|
|
{
|
|
TArray<FIntBox2D> const& HoleBoundsAtLevel = HoleBoundsPerLevel[LodIndex];
|
|
for (int32 BoxIndex = 1; BoxIndex < HoleBoundsAtLevel.Num(); ++BoxIndex)
|
|
{
|
|
const FIntPoint Min = HoleBoundsAtLevel[BoxIndex].Min;
|
|
const FIntPoint Max = HoleBoundsAtLevel[BoxIndex].Max;
|
|
|
|
for (int32 y = Min.Y; y <= Max.Y; ++y)
|
|
{
|
|
for (int32 x = Min.X; x <= Max.X; ++x)
|
|
{
|
|
OutHoleVertexLods[y * InSize + x] = LodIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Structure containing the hole render data required by the runtime rendering.
|
|
template <typename INDEX_TYPE>
|
|
struct FLandscapeHoleRenderData
|
|
{
|
|
TArray<INDEX_TYPE> HoleIndices;
|
|
int32 MinIndex;
|
|
int32 MaxIndex;
|
|
};
|
|
|
|
// Serialize the hole render data.
|
|
template <typename INDEX_TYPE>
|
|
void SerializeHoleRenderData(FMemoryArchive& Ar, FLandscapeHoleRenderData<INDEX_TYPE>& InHoleRenderData)
|
|
{
|
|
bool b16BitIndices = sizeof(INDEX_TYPE) == 2;
|
|
Ar << b16BitIndices;
|
|
|
|
Ar << InHoleRenderData.MinIndex;
|
|
Ar << InHoleRenderData.MaxIndex;
|
|
|
|
int32 HoleIndexCount = InHoleRenderData.HoleIndices.Num();
|
|
Ar << HoleIndexCount;
|
|
Ar.Serialize(InHoleRenderData.HoleIndices.GetData(), HoleIndexCount * sizeof(INDEX_TYPE));
|
|
}
|
|
|
|
// Take the processed hole map and generate the hole render data.
|
|
template <typename INDEX_TYPE>
|
|
void BuildHoleRenderData(int32 InNumSubsections, int32 InSubsectionSizeVerts, TArray<uint8> const& InVisibilityData, TArray<uint32>& InVertexToIndexMap, FLandscapeHoleRenderData<INDEX_TYPE>& OutHoleRenderData)
|
|
{
|
|
const int32 SizeVerts = InNumSubsections * InSubsectionSizeVerts;
|
|
const int32 SubsectionSizeQuads = InSubsectionSizeVerts - 1;
|
|
const uint8 VisThreshold = 170;
|
|
|
|
INDEX_TYPE MaxIndex = 0;
|
|
INDEX_TYPE MinIndex = TNumericLimits<INDEX_TYPE>::Max();
|
|
|
|
for (int32 SubY = 0; SubY < InNumSubsections; SubY++)
|
|
{
|
|
for (int32 SubX = 0; SubX < InNumSubsections; SubX++)
|
|
{
|
|
for (int32 y = 0; y < SubsectionSizeQuads; y++)
|
|
{
|
|
for (int32 x = 0; x < SubsectionSizeQuads; x++)
|
|
{
|
|
const int32 x0 = x;
|
|
const int32 y0 = y;
|
|
const int32 x1 = x + 1;
|
|
const int32 y1 = y + 1;
|
|
|
|
const int32 VertexIndex = (SubY * InSubsectionSizeVerts + y0) * SizeVerts + SubX * InSubsectionSizeVerts + x0;
|
|
const bool bIsHole = InVisibilityData[VertexIndex] < VisThreshold;
|
|
if (bIsHole)
|
|
{
|
|
INDEX_TYPE i00 = InVertexToIndexMap[FLandscapeVertexRef::GetVertexIndex(FLandscapeVertexRef(x0, y0, SubX, SubY), InNumSubsections, InSubsectionSizeVerts)];
|
|
INDEX_TYPE i10 = InVertexToIndexMap[FLandscapeVertexRef::GetVertexIndex(FLandscapeVertexRef(x1, y0, SubX, SubY), InNumSubsections, InSubsectionSizeVerts)];
|
|
INDEX_TYPE i11 = InVertexToIndexMap[FLandscapeVertexRef::GetVertexIndex(FLandscapeVertexRef(x1, y1, SubX, SubY), InNumSubsections, InSubsectionSizeVerts)];
|
|
INDEX_TYPE i01 = InVertexToIndexMap[FLandscapeVertexRef::GetVertexIndex(FLandscapeVertexRef(x0, y1, SubX, SubY), InNumSubsections, InSubsectionSizeVerts)];
|
|
|
|
OutHoleRenderData.HoleIndices.Add(i00);
|
|
OutHoleRenderData.HoleIndices.Add(i11);
|
|
OutHoleRenderData.HoleIndices.Add(i10);
|
|
|
|
OutHoleRenderData.HoleIndices.Add(i00);
|
|
OutHoleRenderData.HoleIndices.Add(i01);
|
|
OutHoleRenderData.HoleIndices.Add(i11);
|
|
|
|
// Update the min/max index ranges
|
|
MaxIndex = FMath::Max<INDEX_TYPE>(MaxIndex, i00);
|
|
MinIndex = FMath::Min<INDEX_TYPE>(MinIndex, i00);
|
|
MaxIndex = FMath::Max<INDEX_TYPE>(MaxIndex, i10);
|
|
MinIndex = FMath::Min<INDEX_TYPE>(MinIndex, i10);
|
|
MaxIndex = FMath::Max<INDEX_TYPE>(MaxIndex, i11);
|
|
MinIndex = FMath::Min<INDEX_TYPE>(MinIndex, i11);
|
|
MaxIndex = FMath::Max<INDEX_TYPE>(MaxIndex, i01);
|
|
MinIndex = FMath::Min<INDEX_TYPE>(MinIndex, i01);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
OutHoleRenderData.MinIndex = MinIndex;
|
|
OutHoleRenderData.MaxIndex = MaxIndex;
|
|
}
|
|
|
|
// Generates vertex and index buffer data from the component's height map and visibility textures.
|
|
// For use on mobile platforms that don't use vertex texture fetch for height or alpha testing for visibility.
|
|
void ULandscapeComponent::GeneratePlatformVertexData(const ITargetPlatform* TargetPlatform)
|
|
{
|
|
if (IsTemplate())
|
|
{
|
|
return;
|
|
}
|
|
check(GetHeightmap());
|
|
check(GetHeightmap()->Source.GetFormat() == TSF_BGRA8);
|
|
|
|
TArray<uint8> NewPlatformData;
|
|
FMemoryWriter PlatformAr(NewPlatformData);
|
|
|
|
const int32 SubsectionSizeVerts = SubsectionSizeQuads + 1;
|
|
const int32 MaxLOD = FMath::CeilLogTwo(SubsectionSizeVerts) - 1;
|
|
const int32 NumMips = FMath::Min(LANDSCAPE_MAX_ES_LOD, GetHeightmap()->Source.GetNumMips());
|
|
|
|
const float HeightmapSubsectionOffsetU = (float)(SubsectionSizeVerts) / (float)GetHeightmap()->Source.GetSizeX();
|
|
const float HeightmapSubsectionOffsetV = (float)(SubsectionSizeVerts) / (float)GetHeightmap()->Source.GetSizeY();
|
|
|
|
// Get the required height mip data
|
|
TArray<TArray64<uint8>> HeightmapMipRawData;
|
|
TArray64<FColor*> HeightmapMipData;
|
|
for (int32 MipIdx = 0; MipIdx < NumMips; MipIdx++)
|
|
{
|
|
int32 MipSubsectionSizeVerts = (SubsectionSizeVerts) >> MipIdx;
|
|
if (MipSubsectionSizeVerts > 1)
|
|
{
|
|
new(HeightmapMipRawData) TArray64<uint8>();
|
|
GetHeightmap()->Source.GetMipData(HeightmapMipRawData.Last(), MipIdx);
|
|
HeightmapMipData.Add((FColor*)HeightmapMipRawData.Last().GetData());
|
|
}
|
|
}
|
|
|
|
// Get any hole data
|
|
int32 NumHoleLods = 0;
|
|
TArray< uint8 > VisibilityData;
|
|
if (ComponentHasVisibilityPainted() && GetLandscapeProxy()->bMeshHoles)
|
|
{
|
|
TArray<FWeightmapLayerAllocationInfo>& ComponentWeightmapLayerAllocations = GetWeightmapLayerAllocations();
|
|
for (int32 AllocIdx = 0; AllocIdx < ComponentWeightmapLayerAllocations.Num(); AllocIdx++)
|
|
{
|
|
FWeightmapLayerAllocationInfo& AllocInfo = ComponentWeightmapLayerAllocations[AllocIdx];
|
|
if (AllocInfo.LayerInfo == ALandscapeProxy::VisibilityLayer)
|
|
{
|
|
NumHoleLods = FMath::Clamp<int32>(GetLandscapeProxy()->MeshHolesMaxLod, 1, NumMips);
|
|
|
|
FLandscapeComponentDataInterface CDI(this, 0);
|
|
CDI.GetWeightmapTextureData(AllocInfo.LayerInfo, VisibilityData);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Layout index buffer to determine best vertex order.
|
|
// This vertex layout code is duplicated in FLandscapeSharedBuffers::CreateIndexBuffers() to create matching index buffers at runtime.
|
|
const int32 NumVertices = FMath::Square(SubsectionSizeVerts * NumSubsections);
|
|
|
|
TArray<uint32> VertexToIndexMap;
|
|
VertexToIndexMap.AddUninitialized(NumVertices);
|
|
FMemory::Memset(VertexToIndexMap.GetData(), 0xFF, NumVertices * sizeof(uint32));
|
|
|
|
TArray<FLandscapeVertexRef> VertexOrder;
|
|
VertexOrder.Empty(NumVertices);
|
|
|
|
// Can't stream if the number of hole LODs is greater than the number of streaming LODs
|
|
const int32 MaxLODClamp = FMath::Min((uint32)GetLandscapeProxy()->MaxLODLevel, (uint32)MAX_MESH_LOD_COUNT - 1u);
|
|
const bool bStreamLandscapeMeshLODs = TargetPlatform
|
|
&& TargetPlatform->SupportsFeature(ETargetPlatformFeatures::LandscapeMeshLODStreaming)
|
|
&& NumHoleLods <= FMath::Min(MaxLOD, MaxLODClamp);
|
|
const int32 NumStreamingLODs = bStreamLandscapeMeshLODs ? FMath::Min(MaxLOD, MaxLODClamp) : 0;
|
|
TArray<int32> StreamingLODVertStartOffsets;
|
|
StreamingLODVertStartOffsets.AddUninitialized(NumStreamingLODs);
|
|
|
|
for (int32 Mip = MaxLOD; Mip >= 0; Mip--)
|
|
{
|
|
int32 LodSubsectionSizeQuads = (SubsectionSizeVerts >> Mip) - 1;
|
|
float MipRatio = (float)SubsectionSizeQuads / (float)LodSubsectionSizeQuads; // Morph current MIP to base MIP
|
|
|
|
if (Mip < NumStreamingLODs)
|
|
{
|
|
StreamingLODVertStartOffsets[Mip] = VertexOrder.Num();
|
|
}
|
|
|
|
for (int32 SubY = 0; SubY < NumSubsections; SubY++)
|
|
{
|
|
for (int32 SubX = 0; SubX < NumSubsections; SubX++)
|
|
{
|
|
for (int32 Y = 0; Y < LodSubsectionSizeQuads; Y++)
|
|
{
|
|
for (int32 X = 0; X < LodSubsectionSizeQuads; X++)
|
|
{
|
|
for (int32 CornerId = 0; CornerId < 4; CornerId++)
|
|
{
|
|
const int32 CornerX = FMath::RoundToInt((float)(X + (CornerId & 1)) * MipRatio);
|
|
const int32 CornerY = FMath::RoundToInt((float)(Y + (CornerId >> 1)) * MipRatio);
|
|
const FLandscapeVertexRef VertexRef(CornerX, CornerY, SubX, SubY);
|
|
|
|
const int32 VertexIndex = FLandscapeVertexRef::GetVertexIndex(VertexRef, NumSubsections, SubsectionSizeVerts);
|
|
if (VertexToIndexMap[VertexIndex] == 0xFFFFFFFF)
|
|
{
|
|
VertexToIndexMap[VertexIndex] = VertexOrder.Num();
|
|
VertexOrder.Add(VertexRef);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (VertexOrder.Num() != NumVertices)
|
|
{
|
|
UE_LOG(LogLandscape, Warning, TEXT("VertexOrder count of %d did not match expected size of %d"), VertexOrder.Num(), NumVertices);
|
|
}
|
|
|
|
// Build and serialize hole render data which includes a unique index buffer with the holes missing.
|
|
// This fills HoleVertexLods which is required for filling the vertex data.
|
|
TArray<uint8> HoleVertexLods;
|
|
PlatformAr << NumHoleLods;
|
|
if (NumHoleLods > 0)
|
|
{
|
|
TArray<FIntBox2D> HoleBounds;
|
|
GetHoleBounds(SubsectionSizeVerts * NumSubsections, VisibilityData, HoleBounds);
|
|
BuildHoleVertexLods(SubsectionSizeVerts * NumSubsections, NumHoleLods, HoleBounds, HoleVertexLods);
|
|
|
|
if (NumVertices <= UINT16_MAX)
|
|
{
|
|
FLandscapeHoleRenderData<uint16> HoleRenderData;
|
|
BuildHoleRenderData(NumSubsections, SubsectionSizeVerts, VisibilityData, VertexToIndexMap, HoleRenderData);
|
|
SerializeHoleRenderData(PlatformAr, HoleRenderData);
|
|
}
|
|
else
|
|
{
|
|
FLandscapeHoleRenderData<uint32> HoleRenderData;
|
|
BuildHoleRenderData(NumSubsections, SubsectionSizeVerts, VisibilityData, VertexToIndexMap, HoleRenderData);
|
|
SerializeHoleRenderData(PlatformAr, HoleRenderData);
|
|
}
|
|
}
|
|
|
|
// Fill in the vertices in the specified order.
|
|
const int32 SizeVerts = SubsectionSizeVerts * NumSubsections;
|
|
int32 NumInlineMobileVertices = NumStreamingLODs > 0 ? StreamingLODVertStartOffsets.Last() : FMath::Square(SizeVerts);
|
|
TArray<FLandscapeMobileVertex> InlineMobileVertices;
|
|
InlineMobileVertices.AddZeroed(NumInlineMobileVertices);
|
|
FLandscapeMobileVertex* DstVert = InlineMobileVertices.GetData();
|
|
|
|
int32 StreamingLODIdx = NumStreamingLODs - 1;
|
|
TArray<TArray<uint8>> StreamingLODData;
|
|
StreamingLODData.Empty(NumStreamingLODs);
|
|
StreamingLODData.AddDefaulted(NumStreamingLODs);
|
|
|
|
for (int32 Idx = 0; Idx < NumVertices; Idx++)
|
|
{
|
|
if (StreamingLODIdx >= 0
|
|
&& StreamingLODIdx >= NumHoleLods - 1
|
|
&& Idx >= StreamingLODVertStartOffsets[StreamingLODIdx])
|
|
{
|
|
const int32 EndIdx = StreamingLODIdx - 1 < 0 || StreamingLODIdx == NumHoleLods - 1 ?
|
|
FMath::Square(SizeVerts) :
|
|
StreamingLODVertStartOffsets[StreamingLODIdx - 1];
|
|
const int32 NumVerts = EndIdx - StreamingLODVertStartOffsets[StreamingLODIdx];
|
|
TArray<uint8>& StreamingLOD = StreamingLODData[StreamingLODIdx];
|
|
StreamingLOD.Empty(NumVerts * sizeof(FLandscapeMobileVertex));
|
|
StreamingLOD.AddZeroed(NumVerts * sizeof(FLandscapeMobileVertex));
|
|
DstVert = (FLandscapeMobileVertex*)StreamingLOD.GetData();
|
|
--StreamingLODIdx;
|
|
}
|
|
|
|
// Store XY position info
|
|
const int32 X = VertexOrder[Idx].X;
|
|
const int32 Y = VertexOrder[Idx].Y;
|
|
|
|
check(X < 256 && Y < 256);
|
|
DstVert->Position[0] = X;
|
|
DstVert->Position[1] = Y;
|
|
|
|
const int32 SubX = VertexOrder[Idx].SubX;
|
|
const int32 SubY = VertexOrder[Idx].SubY;
|
|
|
|
check(SubX < 2 && SubY < 2);
|
|
DstVert->Position[2] = (SubX << 1) | SubY;
|
|
|
|
// Store hole info
|
|
const int32 VertexIndex = (SubY * SubsectionSizeVerts + Y) * SizeVerts + SubX * SubsectionSizeVerts + X;
|
|
const int32 HoleVertexLod = (NumHoleLods > 0) ? HoleVertexLods[VertexIndex] : 0;
|
|
const int32 HoleMaxLod = (NumHoleLods > 0) ? NumHoleLods : 0;
|
|
|
|
check(HoleMaxLod < 8 && HoleVertexLod < 8);
|
|
DstVert->Position[2] |= (HoleMaxLod << 5) | (HoleVertexLod << 2);
|
|
|
|
// Calculate min/max height for packing
|
|
TArray<int32> MipHeights;
|
|
MipHeights.AddZeroed(HeightmapMipData.Num());
|
|
|
|
uint16 MaxHeight = 0, MinHeight = 65535;
|
|
|
|
float HeightmapScaleBiasZ = HeightmapScaleBias.Z + HeightmapSubsectionOffsetU * (float)SubX;
|
|
float HeightmapScaleBiasW = HeightmapScaleBias.W + HeightmapSubsectionOffsetV * (float)SubY;
|
|
int32 BaseMipOfsX = FMath::RoundToInt(HeightmapScaleBiasZ * (float)GetHeightmap()->Source.GetSizeX());
|
|
int32 BaseMipOfsY = FMath::RoundToInt(HeightmapScaleBiasW * (float)GetHeightmap()->Source.GetSizeY());
|
|
|
|
for (int32 Mip = 0; Mip < HeightmapMipData.Num(); ++Mip)
|
|
{
|
|
int32 MipSizeX = GetHeightmap()->Source.GetSizeX() >> Mip;
|
|
|
|
int32 CurrentMipOfsX = BaseMipOfsX >> Mip;
|
|
int32 CurrentMipOfsY = BaseMipOfsY >> Mip;
|
|
|
|
int32 MipX = X >> Mip;
|
|
int32 MipY = Y >> Mip;
|
|
|
|
FColor* CurrentMipSrcRow = HeightmapMipData[Mip] + (CurrentMipOfsY + MipY) * MipSizeX + CurrentMipOfsX;
|
|
uint16 Height = CurrentMipSrcRow[MipX].R << 8 | CurrentMipSrcRow[MipX].G;
|
|
|
|
MipHeights[Mip] = Height;
|
|
MaxHeight = FMath::Max(MaxHeight, Height);
|
|
MinHeight = FMath::Min(MinHeight, Height);
|
|
}
|
|
|
|
DstVert->LODHeights[0] = MinHeight >> 8;
|
|
DstVert->LODHeights[1] = MinHeight & 0xff;
|
|
|
|
// Quantize height delta so we can store in 8 bits in the spare Position channel
|
|
uint16 HeightDelta = FMath::Max(MaxHeight - MinHeight, 1);
|
|
HeightDelta = (HeightDelta + 255) & (~255);
|
|
DstVert->Position[3] = HeightDelta >> 8;
|
|
|
|
// Now quantize the mip heights to 255 steps between MinHeight and MinHeight+HeightDelta
|
|
for (int32 Mip = 0; Mip < HeightmapMipData.Num(); ++Mip)
|
|
{
|
|
check(Mip < 6);
|
|
DstVert->LODHeights[2 + Mip] = FMath::RoundToInt(((float)(MipHeights[Mip] - MinHeight) / (float)HeightDelta) * 255.f);
|
|
}
|
|
|
|
DstVert++;
|
|
}
|
|
|
|
// Serialize vertex buffer
|
|
PlatformAr << NumInlineMobileVertices;
|
|
PlatformAr.Serialize(InlineMobileVertices.GetData(), NumInlineMobileVertices*sizeof(FLandscapeMobileVertex));
|
|
|
|
// Copy to PlatformData as Compressed
|
|
PlatformData.InitializeFromUncompressedData(NewPlatformData, StreamingLODData);
|
|
}
|
|
|
|
UTexture2D* ALandscapeProxy::CreateLandscapeTexture(int32 InSizeX, int32 InSizeY, TextureGroup InLODGroup, ETextureSourceFormat InFormat, UObject* OptionalOverrideOuter, bool bCompress) const
|
|
{
|
|
UObject* TexOuter = OptionalOverrideOuter ? OptionalOverrideOuter : const_cast<ALandscapeProxy*>(this);
|
|
UTexture2D* NewTexture = NewObject<UTexture2D>(TexOuter);
|
|
NewTexture->Source.Init2DWithMipChain(InSizeX, InSizeY, InFormat);
|
|
NewTexture->SRGB = false;
|
|
NewTexture->CompressionNone = !bCompress;
|
|
NewTexture->MipGenSettings = TMGS_LeaveExistingMips;
|
|
NewTexture->AddressX = TA_Clamp;
|
|
NewTexture->AddressY = TA_Clamp;
|
|
NewTexture->LODGroup = InLODGroup;
|
|
|
|
return NewTexture;
|
|
}
|
|
|
|
UTexture2D* ALandscapeProxy::CreateLandscapeToolTexture(int32 InSizeX, int32 InSizeY, TextureGroup InLODGroup, ETextureSourceFormat InFormat) const
|
|
{
|
|
UObject* TexOuter = const_cast<ALandscapeProxy*>(this);
|
|
UTexture2D* NewTexture = NewObject<UTexture2D>(TexOuter);
|
|
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<ULandscapeWeightmapUsage>(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<FLinearColor> ALandscapeProxy::SampleRTData(UTextureRenderTarget2D* InRenderTarget, FLinearColor InRect)
|
|
{
|
|
|
|
if (!InRenderTarget)
|
|
{
|
|
FMessageLog("Blueprint").Warning(LOCTEXT("SampleRTData_InvalidRenderTarget", "SampleRTData: Render Target must be non-null."));
|
|
return { FLinearColor(0,0,0,0) };
|
|
}
|
|
else if (!InRenderTarget->GetResource())
|
|
{
|
|
FMessageLog("Blueprint").Warning(LOCTEXT("SampleRTData_ReleasedRenderTarget", "SampleRTData: Render Target has been released."));
|
|
return { FLinearColor(0,0,0,0) };
|
|
}
|
|
else
|
|
{
|
|
ETextureRenderTargetFormat format = (InRenderTarget->RenderTargetFormat);
|
|
|
|
if ((format == (RTF_RGBA16f)) || (format == (RTF_RGBA32f)) || (format == (RTF_RGBA8)))
|
|
{
|
|
|
|
FTextureRenderTargetResource* RTResource = InRenderTarget->GameThread_GetRenderTargetResource();
|
|
|
|
InRect.R = FMath::Clamp(int(InRect.R), 0, InRenderTarget->SizeX - 1);
|
|
InRect.G = FMath::Clamp(int(InRect.G), 0, InRenderTarget->SizeY - 1);
|
|
InRect.B = FMath::Clamp(int(InRect.B), int(InRect.R + 1), InRenderTarget->SizeX);
|
|
InRect.A = FMath::Clamp(int(InRect.A), int(InRect.G + 1), InRenderTarget->SizeY);
|
|
FIntRect Rect = FIntRect(InRect.R, InRect.G, InRect.B, InRect.A);
|
|
|
|
FReadSurfaceDataFlags ReadPixelFlags(RCM_MinMax);
|
|
|
|
TArray<FColor> OutLDR;
|
|
TArray<FLinearColor> OutHDR;
|
|
|
|
TArray<FLinearColor> OutVals;
|
|
|
|
bool ishdr = ((format == (RTF_R16f)) || (format == (RTF_RG16f)) || (format == (RTF_RGBA16f)) || (format == (RTF_R32f)) || (format == (RTF_RG32f)) || (format == (RTF_RGBA32f)));
|
|
|
|
if (!ishdr)
|
|
{
|
|
RTResource->ReadPixels(OutLDR, ReadPixelFlags, Rect);
|
|
for (auto i : OutLDR)
|
|
{
|
|
OutVals.Add(FLinearColor(float(i.R), float(i.G), float(i.B), float(i.A)) / 255.0f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RTResource->ReadLinearColorPixels(OutHDR, ReadPixelFlags, Rect);
|
|
return OutHDR;
|
|
}
|
|
|
|
return OutVals;
|
|
}
|
|
}
|
|
FMessageLog("Blueprint").Warning(LOCTEXT("SampleRTData_InvalidTexture", "SampleRTData: Currently only 4 channel formats are supported: RTF_RGBA8, RTF_RGBA16f, and RTF_RGBA32f."));
|
|
|
|
return { FLinearColor(0,0,0,0) };
|
|
}
|
|
|
|
bool ALandscapeProxy::LandscapeImportHeightmapFromRenderTarget(UTextureRenderTarget2D* InRenderTarget, bool InImportHeightFromRGChannel)
|
|
{
|
|
uint64 StartCycle = FPlatformTime::Cycles64();
|
|
|
|
ALandscape* Landscape = GetLandscapeActor();
|
|
if (Landscape == nullptr)
|
|
{
|
|
FMessageLog("Blueprint").Error(LOCTEXT("LandscapeImportHeightmapFromRenderTarget_NullLandscape", "LandscapeImportHeightmapFromRenderTarget: Landscape must be non-null."));
|
|
return false;
|
|
}
|
|
|
|
if (Landscape->HasLayersContent())
|
|
{
|
|
//todo: Support an edit layer name input parameter to support import to edit layers.
|
|
FMessageLog("Blueprint").Error(LOCTEXT("LandscapeImportHeightmapFromRenderTarget_LandscapeLayersNotSupported", "LandscapeImportHeightmapFromRenderTarget: Cannot import to landscape with Edit Layers enabled."));
|
|
return false;
|
|
}
|
|
|
|
int32 MinX, MinY, MaxX, MaxY;
|
|
ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo();
|
|
|
|
if (!LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY))
|
|
{
|
|
FMessageLog("Blueprint").Error(LOCTEXT("LandscapeImportHeightmapFromRenderTarget_InvalidLandscapeExtends", "LandscapeImportHeightmapFromRenderTarget: The landscape min extends are invalid."));
|
|
return false;
|
|
}
|
|
|
|
if (InRenderTarget == nullptr || InRenderTarget->GetResource() == nullptr)
|
|
{
|
|
FMessageLog("Blueprint").Error(LOCTEXT("LandscapeImportHeightmapFromRenderTarget_InvalidRT", "LandscapeImportHeightmapFromRenderTarget: Render Target must be non null and not released."));
|
|
return false;
|
|
}
|
|
|
|
FTextureRenderTargetResource* RenderTargetResource = InRenderTarget->GameThread_GetRenderTargetResource();
|
|
FIntRect SampleRect = FIntRect(0, 0, FMath::Min(1 + MaxX - MinX, InRenderTarget->SizeX), FMath::Min(1 + MaxY - MinY, InRenderTarget->SizeY));
|
|
|
|
TArray<uint16> HeightData;
|
|
|
|
switch (InRenderTarget->RenderTargetFormat)
|
|
{
|
|
case RTF_RGBA16f:
|
|
case RTF_RGBA32f:
|
|
{
|
|
TArray<FLinearColor> OutputRTHeightmap;
|
|
OutputRTHeightmap.Reserve(SampleRect.Width() * SampleRect.Height());
|
|
|
|
RenderTargetResource->ReadLinearColorPixels(OutputRTHeightmap, FReadSurfaceDataFlags(RCM_MinMax, CubeFace_MAX), SampleRect);
|
|
HeightData.Reserve(OutputRTHeightmap.Num());
|
|
|
|
for (auto LinearColor : OutputRTHeightmap)
|
|
{
|
|
if (InImportHeightFromRGChannel)
|
|
{
|
|
FColor Color = LinearColor.ToFColor(false);
|
|
uint16 Height = ((Color.R << 8) | Color.G);
|
|
HeightData.Add(Height);
|
|
}
|
|
else
|
|
{
|
|
HeightData.Add((uint16)LinearColor.R);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case RTF_RGBA8:
|
|
{
|
|
TArray<FColor> OutputRTHeightmap;
|
|
OutputRTHeightmap.Reserve(SampleRect.Width() * SampleRect.Height());
|
|
|
|
RenderTargetResource->ReadPixels(OutputRTHeightmap, FReadSurfaceDataFlags(RCM_MinMax, CubeFace_MAX), SampleRect);
|
|
HeightData.Reserve(OutputRTHeightmap.Num());
|
|
|
|
for (FColor Color : OutputRTHeightmap)
|
|
{
|
|
uint16 Height = ((Color.R << 8) | Color.G);
|
|
HeightData.Add(Height);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
FMessageLog("Blueprint").Error(LOCTEXT("LandscapeImportHeightmapFromRenderTarget_InvalidRTFormat", "LandscapeImportHeightmapFromRenderTarget: The Render Target format is invalid. We only support RTF_RGBA16f, RTF_RGBA32f, RTF_RGBA8"));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
FScopedTransaction Transaction(LOCTEXT("Undo_ImportHeightmap", "Importing Landscape Heightmap"));
|
|
|
|
FHeightmapAccessor<false> HeightmapAccessor(LandscapeInfo);
|
|
HeightmapAccessor.SetData(MinX, MinY, SampleRect.Width() - 1, SampleRect.Height() - 1, HeightData.GetData());
|
|
|
|
double SecondsTaken = FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - StartCycle);
|
|
UE_LOG(LogLandscapeBP, Display, TEXT("Took %f seconds to import heightmap from render target."), SecondsTaken);
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
bool ALandscapeProxy::LandscapeExportHeightmapToRenderTarget(UTextureRenderTarget2D* InRenderTarget, bool bInExportHeightIntoRGChannel, bool InExportLandscapeProxies)
|
|
{
|
|
#if WITH_EDITOR
|
|
uint64 StartCycle = FPlatformTime::Cycles64();
|
|
|
|
UMaterial* HeightmapRenderMaterial = LoadObject<UMaterial>(nullptr, TEXT("/Engine/EditorLandscapeResources/Landscape_Heightmap_To_RenderTarget2D.Landscape_Heightmap_To_RenderTarget2D"));
|
|
if (HeightmapRenderMaterial == nullptr)
|
|
{
|
|
FMessageLog("Blueprint").Error(LOCTEXT("LandscapeExportHeightmapToRenderTarget_Landscape_Heightmap_To_RenderTarget2D.", "LandscapeExportHeightmapToRenderTarget: Material Landscape_Heightmap_To_RenderTarget2D not found in engine content."));
|
|
return false;
|
|
}
|
|
|
|
TArray<ULandscapeComponent*> LandscapeComponentsToExport;
|
|
// Export the component of the specified proxy
|
|
LandscapeComponentsToExport.Append(LandscapeComponents);
|
|
|
|
// If requested, export all proxies
|
|
if (InExportLandscapeProxies && (GetLandscapeActor() == this))
|
|
{
|
|
ULandscapeInfo* LandscapeInfo = GetLandscapeInfo();
|
|
for (ALandscapeProxy* Proxy : LandscapeInfo->Proxies)
|
|
{
|
|
LandscapeComponentsToExport.Append(Proxy->LandscapeComponents);
|
|
}
|
|
}
|
|
|
|
if (LandscapeComponentsToExport.Num() == 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
UWorld* World = GEditor->GetEditorWorldContext().World();
|
|
FTextureRenderTargetResource* RenderTargetResource = InRenderTarget->GameThread_GetRenderTargetResource();
|
|
|
|
// Create a canvas for the render target and clear it to black
|
|
FCanvas Canvas(RenderTargetResource, nullptr, 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<FCanvasUVTri> TriangleList;
|
|
};
|
|
|
|
TMap<UTexture*, FTrianglePerMID> TrianglesPerHeightmap;
|
|
|
|
for (const ULandscapeComponent* Component : LandscapeComponentsToExport)
|
|
{
|
|
FTrianglePerMID* TrianglesPerMID = TrianglesPerHeightmap.Find(Component->GetHeightmap());
|
|
|
|
if (TrianglesPerMID == nullptr)
|
|
{
|
|
FTrianglePerMID Data;
|
|
Data.HeightmapMID = UMaterialInstanceDynamic::Create(HeightmapRenderMaterial, this);
|
|
Data.HeightmapMID->SetTextureParameterValue(TEXT("Heightmap"), Component->GetHeightmap());
|
|
Data.HeightmapMID->SetScalarParameterValue(TEXT("ExportHeightIntoRGChannel"), bInExportHeightIntoRGChannel);
|
|
TrianglesPerMID = &TrianglesPerHeightmap.Add(Component->GetHeightmap(), Data);
|
|
}
|
|
|
|
FIntPoint ComponentSectionBase = Component->GetSectionBase();
|
|
FIntPoint ComponentHeightmapTextureSize(Component->GetHeightmap()->Source.GetSizeX(), Component->GetHeightmap()->Source.GetSizeY());
|
|
int32 SubsectionSizeVerts = Component->SubsectionSizeQuads + 1;
|
|
float HeightmapSubsectionOffsetU = (float)(SubsectionSizeVerts) / (float)ComponentHeightmapTextureSize.X;
|
|
float HeightmapSubsectionOffsetV = (float)(SubsectionSizeVerts) / (float)ComponentHeightmapTextureSize.Y;
|
|
|
|
for (int8 SubY = 0; SubY < NumSubsections; ++SubY)
|
|
{
|
|
for (int8 SubX = 0; SubX < NumSubsections; ++SubX)
|
|
{
|
|
FIntPoint SubSectionSectionBase = ComponentSectionBase - ExportBaseOffset;
|
|
SubSectionSectionBase.X += Component->SubsectionSizeQuads * SubX;
|
|
SubSectionSectionBase.Y += Component->SubsectionSizeQuads * SubY;
|
|
|
|
// Offset for this component's data in heightmap texture
|
|
float HeightmapOffsetU = Component->HeightmapScaleBias.Z + HeightmapSubsectionOffsetU * (float)SubX;
|
|
float HeightmapOffsetV = Component->HeightmapScaleBias.W + HeightmapSubsectionOffsetV * (float)SubY;
|
|
|
|
FCanvasUVTri Tri1;
|
|
Tri1.V0_Pos = FVector2D(SubSectionSectionBase.X, SubSectionSectionBase.Y);
|
|
Tri1.V1_Pos = FVector2D(SubSectionSectionBase.X + SubsectionSizeVerts, SubSectionSectionBase.Y);
|
|
Tri1.V2_Pos = FVector2D(SubSectionSectionBase.X + SubsectionSizeVerts, SubSectionSectionBase.Y + SubsectionSizeVerts);
|
|
|
|
Tri1.V0_UV = FVector2D(HeightmapOffsetU, HeightmapOffsetV);
|
|
Tri1.V1_UV = FVector2D(HeightmapOffsetU + HeightmapSubsectionOffsetU, HeightmapOffsetV);
|
|
Tri1.V2_UV = FVector2D(HeightmapOffsetU + HeightmapSubsectionOffsetU, HeightmapOffsetV + HeightmapSubsectionOffsetV);
|
|
TrianglesPerMID->TriangleList.Add(Tri1);
|
|
|
|
FCanvasUVTri Tri2;
|
|
Tri2.V0_Pos = FVector2D(SubSectionSectionBase.X + SubsectionSizeVerts, SubSectionSectionBase.Y + SubsectionSizeVerts);
|
|
Tri2.V1_Pos = FVector2D(SubSectionSectionBase.X, SubSectionSectionBase.Y + SubsectionSizeVerts);
|
|
Tri2.V2_Pos = FVector2D(SubSectionSectionBase.X, SubSectionSectionBase.Y);
|
|
|
|
Tri2.V0_UV = FVector2D(HeightmapOffsetU + HeightmapSubsectionOffsetU, HeightmapOffsetV + HeightmapSubsectionOffsetV);
|
|
Tri2.V1_UV = FVector2D(HeightmapOffsetU, HeightmapOffsetV + HeightmapSubsectionOffsetV);
|
|
Tri2.V2_UV = FVector2D(HeightmapOffsetU, HeightmapOffsetV);
|
|
|
|
TrianglesPerMID->TriangleList.Add(Tri2);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto& TriangleList : TrianglesPerHeightmap)
|
|
{
|
|
FCanvasTriangleItem TriItemList(MoveTemp(TriangleList.Value.TriangleList), nullptr);
|
|
TriItemList.MaterialRenderProxy = TriangleList.Value.HeightmapMID->GetRenderProxy();
|
|
TriItemList.BlendMode = SE_BLEND_Opaque;
|
|
TriItemList.SetColor(FLinearColor::White);
|
|
|
|
TriItemList.Draw(&Canvas);
|
|
}
|
|
|
|
TrianglesPerHeightmap.Reset();
|
|
|
|
// Tell the rendering thread to draw any remaining batched elements
|
|
Canvas.Flush_GameThread(true);
|
|
|
|
ENQUEUE_RENDER_COMMAND(DrawHeightmapRTCommand)(
|
|
[RenderTargetResource](FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
// 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<FLinearColor> RTData;
|
|
RTData = SampleRTData(InRenderTarget, SampleRect);
|
|
|
|
TArray<uint8> LayerData;
|
|
|
|
for (auto i : RTData)
|
|
{
|
|
LayerData.Add((uint8)(FMath::Clamp((float)i.R, 0.0f, 1.0f) * 255));
|
|
}
|
|
|
|
FLandscapeInfoLayerSettings CurWeightmapInfo;
|
|
|
|
int32 Index = LandscapeInfo->GetLayerInfoIndex(InLayerName, LandscapeInfo->GetLandscapeProxy());
|
|
|
|
if (ensure(Index != INDEX_NONE))
|
|
{
|
|
CurWeightmapInfo = LandscapeInfo->Layers[Index];
|
|
}
|
|
|
|
if (CurWeightmapInfo.LayerInfoObj == nullptr)
|
|
{
|
|
FMessageLog("Blueprint").Error(LOCTEXT("LandscapeImportRenderTarget_InvalidLayerInfoObject", "LandscapeImportWeightmapFromRenderTarget: Layers must first have Layer Info Objects assigned before importing."));
|
|
return false;
|
|
}
|
|
|
|
FScopedTransaction Transaction(LOCTEXT("Undo_ImportWeightmap", "Importing Landscape Layer"));
|
|
|
|
FAlphamapAccessor<false, false> AlphamapAccessor(LandscapeInfo, CurWeightmapInfo.LayerInfoObj);
|
|
AlphamapAccessor.SetData(MinX, MinY, MaxX, MaxY, LayerData.GetData(), ELandscapeLayerPaintingRestriction::None);
|
|
|
|
uint64 CycleEnd = FPlatformTime::Cycles64();
|
|
UE_LOG(LogLandscape, 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
|