654 lines
26 KiB
C++
654 lines
26 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
#include "LandscapeConfigHelper.h"
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(LandscapeConfigHelper)
|
|
|
|
#if WITH_EDITOR
|
|
|
|
#include "Engine/World.h"
|
|
#include "Misc/ScopeExit.h"
|
|
#include "LandscapeInfo.h"
|
|
#include "Landscape.h"
|
|
#include "LandscapeProxy.h"
|
|
#include "LandscapeStreamingProxy.h"
|
|
#include "LandscapeSplineActor.h"
|
|
#include "LandscapeSplineControlPoint.h"
|
|
#include "LandscapeSplinesComponent.h"
|
|
#include "LandscapeEdit.h"
|
|
#include "LandscapeDataAccess.h"
|
|
#include "LandscapeEditLayer.h"
|
|
#include "LandscapeSubsystem.h"
|
|
#include "ActorPartition/ActorPartitionSubsystem.h"
|
|
|
|
int32 FLandscapeConfig::NumSectionValues[2] = { 1, 2 };
|
|
int32 FLandscapeConfig::SubsectionSizeQuadsValues[6] = { 7, 15, 31, 63, 127, 255 };
|
|
|
|
FLandscapeConfig::FLandscapeConfig(ULandscapeInfo* InLandscapeInfo)
|
|
: FLandscapeConfig(InLandscapeInfo->ComponentNumSubsections, InLandscapeInfo->SubsectionSizeQuads, InLandscapeInfo->LandscapeActor->GetGridSize() / InLandscapeInfo->ComponentSizeQuads)
|
|
{
|
|
|
|
}
|
|
|
|
bool FLandscapeConfigChange::Validate() const
|
|
{
|
|
bool bValidNumSections = false;
|
|
for (int32 Index = 0; Index < UE_ARRAY_COUNT(NumSectionValues); ++Index)
|
|
{
|
|
if (ComponentNumSubsections == NumSectionValues[Index])
|
|
{
|
|
bValidNumSections = true;
|
|
break;
|
|
}
|
|
}
|
|
bool bValidSectionSizeQuads = false;
|
|
for (int32 Index = 0; Index < UE_ARRAY_COUNT(SubsectionSizeQuadsValues); ++Index)
|
|
{
|
|
if (SubsectionSizeQuads == SubsectionSizeQuadsValues[Index])
|
|
{
|
|
bValidSectionSizeQuads = true;
|
|
break;
|
|
}
|
|
}
|
|
return bValidNumSections && bValidSectionSizeQuads;
|
|
}
|
|
|
|
ALandscapeProxy* FLandscapeConfigHelper::FindOrAddLandscapeStreamingProxy(ULandscapeInfo* InLandscapeInfo, const FIntPoint& InSectionBase)
|
|
{
|
|
UWorld* World = InLandscapeInfo->LandscapeActor->GetWorld();
|
|
UActorPartitionSubsystem* ActorPartitionSubsystem = World->GetSubsystem<UActorPartitionSubsystem>();
|
|
const uint32 GridSize = InLandscapeInfo->LandscapeActor->GetGridSize();
|
|
|
|
UActorPartitionSubsystem::FCellCoord CellCoord = UActorPartitionSubsystem::FCellCoord::GetCellCoord(InSectionBase, InLandscapeInfo->LandscapeActor->GetLevel(), GridSize);
|
|
return FLandscapeConfigHelper::FindOrAddLandscapeStreamingProxy(ActorPartitionSubsystem, InLandscapeInfo, CellCoord);
|
|
}
|
|
|
|
ALandscapeProxy* FLandscapeConfigHelper::FindOrAddLandscapeStreamingProxy(UActorPartitionSubsystem* InActorPartitionSubsystem, ULandscapeInfo* InLandscapeInfo, const UActorPartitionSubsystem::FCellCoord& InCellCoord)
|
|
{
|
|
ALandscape* Landscape = InLandscapeInfo->LandscapeActor.Get();
|
|
check(Landscape);
|
|
|
|
auto LandscapeProxyCreated = [InCellCoord, Landscape](APartitionActor* PartitionActor)
|
|
{
|
|
const FIntPoint CellLocation(static_cast<int32>(InCellCoord.X) * Landscape->GetGridSize(), static_cast<int32>(InCellCoord.Y) * Landscape->GetGridSize());
|
|
|
|
ALandscapeProxy* LandscapeProxy = CastChecked<ALandscapeProxy>(PartitionActor);
|
|
// copy shared properties to this new proxy
|
|
LandscapeProxy->SynchronizeSharedProperties(Landscape);
|
|
const FVector ProxyLocation = Landscape->GetActorLocation() + FVector(CellLocation.X * Landscape->GetActorRelativeScale3D().X, CellLocation.Y * Landscape->GetActorRelativeScale3D().Y, 0.0f);
|
|
|
|
LandscapeProxy->CreateLandscapeInfo();
|
|
LandscapeProxy->SetActorLocationAndRotation(ProxyLocation, Landscape->GetActorRotation());
|
|
LandscapeProxy->LandscapeSectionOffset = FIntPoint(CellLocation.X, CellLocation.Y);
|
|
LandscapeProxy->SetIsSpatiallyLoaded(LandscapeProxy->GetLandscapeInfo()->AreNewLandscapeActorsSpatiallyLoaded());
|
|
};
|
|
|
|
const bool bCreate = true;
|
|
const bool bBoundsSearch = false;
|
|
|
|
ALandscapeProxy* LandscapeProxy = Cast<ALandscapeProxy>(InActorPartitionSubsystem->GetActor(ALandscapeStreamingProxy::StaticClass(), InCellCoord, bCreate, InLandscapeInfo->LandscapeGuid, Landscape->GetGridSize(), bBoundsSearch, LandscapeProxyCreated));
|
|
check(!LandscapeProxy || LandscapeProxy->GetGridSize() == Landscape->GetGridSize());
|
|
return LandscapeProxy;
|
|
}
|
|
|
|
bool FLandscapeConfigHelper::ChangeGridSize(ULandscapeInfo* InLandscapeInfo, uint32 InNewGridSizeInComponents, TSet<AActor*>& OutActorsToDelete)
|
|
{
|
|
check(InLandscapeInfo);
|
|
|
|
const uint32 GridSize = InLandscapeInfo->GetGridSize(InNewGridSizeInComponents);
|
|
|
|
InLandscapeInfo->LandscapeActor->Modify();
|
|
InLandscapeInfo->LandscapeActor->SetGridSize(GridSize);
|
|
|
|
// This needs to be done before moving components
|
|
InLandscapeInfo->LandscapeActor->InitializeLandscapeLayersWeightmapUsage();
|
|
|
|
// Make sure if actor didn't include grid size in name it now does. This will avoid recycling
|
|
// LandscapeStreamingProxy actors and create new ones with the proper name.
|
|
InLandscapeInfo->LandscapeActor->bIncludeGridSizeInNameForLandscapeActors = true;
|
|
|
|
FIntRect Extent;
|
|
InLandscapeInfo->GetLandscapeExtent(Extent.Min.X, Extent.Min.Y, Extent.Max.X, Extent.Max.Y);
|
|
const FBox Bounds(FVector(Extent.Min), FVector(Extent.Max));
|
|
|
|
UWorld* World = InLandscapeInfo->LandscapeActor->GetWorld();
|
|
UActorPartitionSubsystem* ActorPartitionSubsystem = World->GetSubsystem<UActorPartitionSubsystem>();
|
|
|
|
TArray<ULandscapeComponent*> LandscapeComponents;
|
|
LandscapeComponents.Reserve(InLandscapeInfo->XYtoComponentMap.Num());
|
|
InLandscapeInfo->ForAllLandscapeComponents([&LandscapeComponents](ULandscapeComponent* LandscapeComponent)
|
|
{
|
|
LandscapeComponents.Add(LandscapeComponent);
|
|
});
|
|
|
|
TSet<ALandscapeProxy*> ProxiesToDelete;
|
|
|
|
FActorPartitionGridHelper::ForEachIntersectingCell(ALandscapeStreamingProxy::StaticClass(), Extent, InLandscapeInfo->LandscapeActor->GetLevel(), [ActorPartitionSubsystem, InLandscapeInfo, InNewGridSizeInComponents, &LandscapeComponents, &ProxiesToDelete](const UActorPartitionSubsystem::FCellCoord& CellCoord, const FIntRect& CellBounds)
|
|
{
|
|
TMap<ULandscapeComponent*, UMaterialInterface*> ComponentMaterials;
|
|
TMap<ULandscapeComponent*, UMaterialInterface*> ComponentHoleMaterials;
|
|
TMap <ULandscapeComponent*, TMap<int32, UMaterialInterface*>> ComponentLODMaterials;
|
|
|
|
TArray<ULandscapeComponent*> ComponentsToMove;
|
|
const int32 MaxComponents = (int32)(InNewGridSizeInComponents * InNewGridSizeInComponents);
|
|
ComponentsToMove.Reserve(MaxComponents);
|
|
for (int32 i = 0; i < LandscapeComponents.Num();)
|
|
{
|
|
ULandscapeComponent* LandscapeComponent = LandscapeComponents[i];
|
|
if (CellBounds.Contains(LandscapeComponent->GetSectionBase()))
|
|
{
|
|
ComponentMaterials.FindOrAdd(LandscapeComponent, LandscapeComponent->GetLandscapeMaterial());
|
|
ComponentHoleMaterials.FindOrAdd(LandscapeComponent, LandscapeComponent->GetLandscapeHoleMaterial());
|
|
TMap<int32, UMaterialInterface*>& LODMaterials = ComponentLODMaterials.FindOrAdd(LandscapeComponent);
|
|
for (int8 LODIndex = 0; LODIndex <= 8; ++LODIndex)
|
|
{
|
|
LODMaterials.Add(LODIndex, LandscapeComponent->GetLandscapeMaterial(LODIndex));
|
|
}
|
|
|
|
ComponentsToMove.Add(LandscapeComponent);
|
|
LandscapeComponents.RemoveAtSwap(i);
|
|
ProxiesToDelete.Add(LandscapeComponent->GetTypedOuter<ALandscapeProxy>());
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
}
|
|
}
|
|
|
|
check(ComponentsToMove.Num() <= MaxComponents);
|
|
if (ComponentsToMove.Num())
|
|
{
|
|
ALandscapeProxy* LandscapeProxy = FLandscapeConfigHelper::FindOrAddLandscapeStreamingProxy(ActorPartitionSubsystem, InLandscapeInfo, CellCoord);
|
|
check(LandscapeProxy);
|
|
InLandscapeInfo->MoveComponentsToProxy(ComponentsToMove, LandscapeProxy);
|
|
|
|
// Make sure components retain their Materials if they don't match with their parent proxy
|
|
for (ULandscapeComponent* MovedComponent : ComponentsToMove)
|
|
{
|
|
UMaterialInterface* PreviousLandscapeMaterial = ComponentMaterials.FindChecked(MovedComponent);
|
|
UMaterialInterface* PreviousLandscapeHoleMaterial = ComponentHoleMaterials.FindChecked(MovedComponent);
|
|
TMap<int32, UMaterialInterface*> PreviousLandscapeLODMaterials = ComponentLODMaterials.FindChecked(MovedComponent);
|
|
|
|
MovedComponent->OverrideMaterial = nullptr;
|
|
if (PreviousLandscapeMaterial != nullptr && PreviousLandscapeMaterial != MovedComponent->GetLandscapeMaterial())
|
|
{
|
|
// If Proxy doesn't differ from Landscape override material there first
|
|
if(LandscapeProxy->GetLandscapeMaterial() == LandscapeProxy->GetLandscapeActor()->GetLandscapeMaterial())
|
|
{
|
|
LandscapeProxy->LandscapeMaterial = PreviousLandscapeMaterial;
|
|
}
|
|
else // If it already differs it means that the component differs from it, override on component
|
|
{
|
|
MovedComponent->OverrideMaterial = PreviousLandscapeMaterial;
|
|
}
|
|
}
|
|
|
|
MovedComponent->OverrideHoleMaterial = nullptr;
|
|
if (PreviousLandscapeHoleMaterial != nullptr && PreviousLandscapeHoleMaterial != MovedComponent->GetLandscapeHoleMaterial())
|
|
{
|
|
// If Proxy doesn't differ from Landscape override material there first
|
|
if (LandscapeProxy->GetLandscapeHoleMaterial() == LandscapeProxy->GetLandscapeActor()->GetLandscapeHoleMaterial())
|
|
{
|
|
LandscapeProxy->LandscapeHoleMaterial = PreviousLandscapeHoleMaterial;
|
|
}
|
|
else // If it already differs it means that the component differs from it, override on component
|
|
{
|
|
MovedComponent->OverrideHoleMaterial = PreviousLandscapeHoleMaterial;
|
|
}
|
|
}
|
|
|
|
TArray<FLandscapePerLODMaterialOverride> PerLODOverrideMaterialsForComponent;
|
|
TArray<FLandscapePerLODMaterialOverride> PerLODOverrideMaterialsForProxy = LandscapeProxy->GetPerLODOverrideMaterials();
|
|
for (int8 LODIndex = 0; LODIndex <= 8; ++LODIndex)
|
|
{
|
|
UMaterialInterface* PreviousLODMaterial = PreviousLandscapeLODMaterials.FindChecked(LODIndex);
|
|
// If Proxy doesn't differ from Landscape override material there first
|
|
if (PreviousLODMaterial != nullptr && PreviousLODMaterial != MovedComponent->GetLandscapeMaterial(LODIndex))
|
|
{
|
|
if (LandscapeProxy->GetLandscapeMaterial(LODIndex) == LandscapeProxy->GetLandscapeActor()->GetLandscapeMaterial(LODIndex))
|
|
{
|
|
PerLODOverrideMaterialsForProxy.Add({ LODIndex, TObjectPtr<UMaterialInterface>(PreviousLODMaterial) });
|
|
}
|
|
else // If it already differs it means that the component differs from it, override on component
|
|
{
|
|
PerLODOverrideMaterialsForComponent.Add({ LODIndex, TObjectPtr<UMaterialInterface>(PreviousLODMaterial) });
|
|
}
|
|
}
|
|
}
|
|
MovedComponent->SetPerLODOverrideMaterials(PerLODOverrideMaterialsForComponent);
|
|
LandscapeProxy->SetPerLODOverrideMaterials(PerLODOverrideMaterialsForProxy);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}, GridSize);
|
|
|
|
// Only delete Proxies that where not reused
|
|
for (ALandscapeProxy* ProxyToDelete : ProxiesToDelete)
|
|
{
|
|
if (ProxyToDelete->LandscapeComponents.Num() > 0 || ProxyToDelete->IsA<ALandscape>())
|
|
{
|
|
check(ProxyToDelete->GetGridSize() == GridSize);
|
|
continue;
|
|
}
|
|
|
|
OutActorsToDelete.Add(ProxyToDelete);
|
|
}
|
|
|
|
if (InLandscapeInfo->CanHaveLayersContent())
|
|
{
|
|
InLandscapeInfo->ForceLayersFullUpdate();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FLandscapeConfigHelper::PartitionLandscape(UWorld* InWorld, ULandscapeInfo* InLandscapeInfo, uint32 InGridSizeInComponents)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FLandscapeConfigHelper::PartitionLandscape);
|
|
|
|
TSet<AActor*> NewSplineActors;
|
|
|
|
// Handle Landscapes with missing LandscapeActor(s)
|
|
if (!InLandscapeInfo->LandscapeActor.Get())
|
|
{
|
|
// Use the first proxy as the landscape template
|
|
if (ALandscapeProxy* FirstProxy = InLandscapeInfo->StreamingProxies[0].Get())
|
|
{
|
|
FActorSpawnParameters SpawnParams;
|
|
FTransform LandscapeTransform = FirstProxy->LandscapeActorToWorld();
|
|
ALandscape* NewLandscape = InWorld->SpawnActor<ALandscape>(ALandscape::StaticClass(), LandscapeTransform, SpawnParams);
|
|
|
|
NewLandscape->CopySharedProperties(FirstProxy);
|
|
|
|
InLandscapeInfo->RegisterActor(NewLandscape);
|
|
}
|
|
}
|
|
|
|
auto MoveControlPointToNewSplineActor = [&NewSplineActors, InLandscapeInfo](ULandscapeSplineControlPoint* ControlPoint)
|
|
{
|
|
AActor* CurrentOwner = ControlPoint->GetTypedOuter<AActor>();
|
|
// Control point as already been moved through its connected segments
|
|
if (NewSplineActors.Contains(CurrentOwner))
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FTransform LocalToWorld = ControlPoint->GetOuterULandscapeSplinesComponent()->GetComponentTransform();
|
|
const FVector NewActorLocation = LocalToWorld.TransformPosition(ControlPoint->Location);
|
|
|
|
ALandscapeSplineActor* NewSplineActor = InLandscapeInfo->CreateSplineActor(NewActorLocation);
|
|
|
|
NewSplineActors.Add(NewSplineActor);
|
|
InLandscapeInfo->MoveSpline(ControlPoint, NewSplineActor);
|
|
};
|
|
|
|
// Iterate on copy since we are creating new spline actors
|
|
TArray<TScriptInterface<ILandscapeSplineInterface>> OldSplineActors(InLandscapeInfo->GetSplineActors());
|
|
for (TScriptInterface<ILandscapeSplineInterface> PreviousSplineActor : OldSplineActors)
|
|
{
|
|
if (ULandscapeSplinesComponent* SplineComponent = PreviousSplineActor->GetSplinesComponent())
|
|
{
|
|
SplineComponent->ForEachControlPoint(MoveControlPointToNewSplineActor);
|
|
}
|
|
}
|
|
|
|
TSet<AActor*> ActorsToDelete;
|
|
bool bChangedGridSize = FLandscapeConfigHelper::ChangeGridSize(InLandscapeInfo, InGridSizeInComponents, ActorsToDelete);
|
|
for (AActor* ActorToDelete : ActorsToDelete)
|
|
{
|
|
InWorld->DestroyActor(ActorToDelete);
|
|
}
|
|
|
|
return bChangedGridSize;
|
|
}
|
|
|
|
void FLandscapeConfigHelper::ExtractLandscapeData(ULandscapeInfo* InLandscapeInfo, const FIntRect& InRegion, const FGuid& InLayerGuid, TArray<uint16>& OutHeightData, TArray<FLandscapeImportLayerInfo>& OutImportMaterialLayerInfos)
|
|
{
|
|
FScopedSetLandscapeEditingLayer LayerScope(InLandscapeInfo->LandscapeActor.Get(), InLayerGuid);
|
|
FLandscapeEditDataInterface LandscapeEdit(InLandscapeInfo);
|
|
|
|
int32 VertsX = InRegion.Width() + 1;
|
|
int32 VertsY = InRegion.Height() + 1;
|
|
OutHeightData.Reset();
|
|
OutHeightData.AddZeroed(VertsX * VertsY);
|
|
|
|
{
|
|
FIntRect CopyRegion(InRegion);
|
|
LandscapeEdit.GetHeightData(CopyRegion.Min.X, CopyRegion.Min.Y, CopyRegion.Max.X, CopyRegion.Max.Y, OutHeightData.GetData(), 0);
|
|
}
|
|
|
|
for (const FLandscapeInfoLayerSettings& LayerSettings : InLandscapeInfo->Layers)
|
|
{
|
|
if (LayerSettings.LayerInfoObj != NULL)
|
|
{
|
|
auto ImportLayerInfo = new(OutImportMaterialLayerInfos) FLandscapeImportLayerInfo(LayerSettings);
|
|
ImportLayerInfo->LayerData.Reset();
|
|
ImportLayerInfo->LayerData.AddZeroed(VertsX * VertsY);
|
|
FIntRect CopyRegion(InRegion);
|
|
LandscapeEdit.GetWeightData(LayerSettings.LayerInfoObj, CopyRegion.Min.X, CopyRegion.Min.Y, CopyRegion.Max.X, CopyRegion.Max.Y, ImportLayerInfo->LayerData.GetData(), 0);
|
|
}
|
|
}
|
|
};
|
|
|
|
void FLandscapeConfigHelper::CopyRegionToComponent(ULandscapeInfo* InLandscapeInfo, const FIntRect& InRegion, bool bInResample, ULandscapeComponent* InComponent)
|
|
{
|
|
TArray<uint16> SrcHeightData;
|
|
TArray<FLandscapeImportLayerInfo> SrcWeightData;
|
|
|
|
ULandscapeInfo* NewLandscapeInfo = InComponent->GetLandscapeInfo();
|
|
|
|
FIntRect NewRegion = InComponent->GetComponentExtent();
|
|
|
|
// Create Heightmap Texture
|
|
check(InComponent->GetHeightmap(false) == nullptr);
|
|
int32 HeightmapSize = (InComponent->SubsectionSizeQuads + 1) * InComponent->NumSubsections;
|
|
|
|
TArray<uint16> NewHeightData;
|
|
TArray<uint8> NewWeightData;
|
|
|
|
if (!NewLandscapeInfo->LandscapeActor->bCanHaveLayersContent)
|
|
{
|
|
InComponent->HeightmapScaleBias = FVector4(1.0f / (float)HeightmapSize, 1.0f / (float)HeightmapSize, 0.0f, 0.0f);
|
|
InComponent->SetHeightmap(InComponent->GetLandscapeProxy()->CreateLandscapeTexture(HeightmapSize, HeightmapSize, TEXTUREGROUP_Terrain_Heightmap, TSF_BGRA8));
|
|
|
|
ExtractLandscapeData(InLandscapeInfo, InRegion, FGuid(), SrcHeightData, SrcWeightData);
|
|
|
|
CopyData(SrcHeightData, NewHeightData, InRegion, NewRegion, bInResample);
|
|
|
|
{
|
|
FLandscapeEditDataInterface LandscapeEdit(NewLandscapeInfo);
|
|
LandscapeEdit.SetHeightData(NewRegion.Min.X, NewRegion.Min.Y, NewRegion.Max.X, NewRegion.Max.Y, NewHeightData.GetData(), 0, false);
|
|
}
|
|
|
|
|
|
for (const FLandscapeImportLayerInfo& Layer : SrcWeightData)
|
|
{
|
|
FLandscapeEditDataInterface LandscapeEdit(NewLandscapeInfo);
|
|
CopyData(Layer.LayerData, NewWeightData, InRegion, NewRegion, bInResample);
|
|
LandscapeEdit.SetAlphaData(Layer.LayerInfo, NewRegion.Min.X, NewRegion.Min.Y, NewRegion.Max.X, NewRegion.Max.Y, NewWeightData.GetData(), 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TArray<FColor> HeightInitData;
|
|
HeightInitData.SetNum(FMath::Square(HeightmapSize));
|
|
const uint16 DefaultHeight = LandscapeDataAccess::GetTexHeight(0.f);
|
|
FColor DefaultPackedHeight = LandscapeDataAccess::PackHeight(DefaultHeight);
|
|
for (int32 Index = 0; Index < HeightInitData.Num(); ++Index)
|
|
{
|
|
HeightInitData[Index] = DefaultPackedHeight;
|
|
}
|
|
InComponent->InitHeightmapData(HeightInitData, false);
|
|
|
|
for (const ULandscapeEditLayerBase* LandscapeEditLayer : InLandscapeInfo->LandscapeActor->GetEditLayersConst())
|
|
{
|
|
check(LandscapeEditLayer != nullptr);
|
|
if (!LandscapeEditLayer->NeedsPersistentTextures())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TMap<UTexture2D*, UTexture2D*> CreatedTextures;
|
|
InComponent->AddDefaultLayerData(LandscapeEditLayer->GetGuid(), { InComponent }, CreatedTextures);
|
|
|
|
// No need to copy the persistent textures if they are not manually edited (e.g. fully procedural layers with persistent textures : e.g. the splines layer) :
|
|
if (!LandscapeEditLayer->SupportsEditingTools())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
SrcWeightData.Empty();
|
|
ExtractLandscapeData(InLandscapeInfo, InRegion, LandscapeEditLayer->GetGuid(), SrcHeightData, SrcWeightData);
|
|
CopyData(SrcHeightData, NewHeightData, InRegion, NewRegion, bInResample);
|
|
|
|
FScopedSetLandscapeEditingLayer LayerScope(NewLandscapeInfo->LandscapeActor.Get(), LandscapeEditLayer->GetGuid());
|
|
{
|
|
FLandscapeEditDataInterface LandscapeEdit(NewLandscapeInfo);
|
|
LandscapeEdit.SetHeightData(NewRegion.Min.X, NewRegion.Min.Y, NewRegion.Max.X, NewRegion.Max.Y, NewHeightData.GetData(), 0, false);
|
|
}
|
|
|
|
for (const FLandscapeImportLayerInfo& Layer : SrcWeightData)
|
|
{
|
|
CopyData(Layer.LayerData, NewWeightData, InRegion, NewRegion, bInResample);
|
|
FLandscapeEditDataInterface LandscapeEdit(NewLandscapeInfo);
|
|
LandscapeEdit.SetAlphaData(Layer.LayerInfo, NewRegion.Min.X, NewRegion.Min.Y, NewRegion.Max.X, NewRegion.Max.Y, NewWeightData.GetData(), 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FLandscapeConfigHelper::MoveSplinesToLandscape(ULandscapeInfo* InLandscapeInfo, ALandscapeProxy* InLandscape, float InScaleFactor)
|
|
{
|
|
if (!InLandscape->GetSplinesComponent())
|
|
{
|
|
InLandscape->CreateSplineComponent();
|
|
}
|
|
|
|
InLandscapeInfo->ForEachLandscapeProxy([InLandscapeInfo, InLandscape](ALandscapeProxy* LandscapeProxy)
|
|
{
|
|
if (InLandscape == LandscapeProxy || !LandscapeProxy->GetSplinesComponent())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
InLandscapeInfo->MoveSplinesToProxy(LandscapeProxy->GetSplinesComponent(), InLandscape);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
void FLandscapeConfigHelper::MoveFoliageToLandscape(ULandscapeInfo* InLandscapeInfo, ULandscapeInfo* InNewLandscapeInfo)
|
|
{
|
|
// Move instances
|
|
for (const TPair<FIntPoint, ULandscapeComponent*>& OldEntry : InLandscapeInfo->XYtoComponentMap)
|
|
{
|
|
ULandscapeHeightfieldCollisionComponent* OldCollisionComponent = OldEntry.Value->GetCollisionComponent();
|
|
|
|
if (OldCollisionComponent)
|
|
{
|
|
UWorld* World = OldCollisionComponent->GetWorld();
|
|
|
|
for (const TPair<FIntPoint, ULandscapeComponent*>& NewEntry : InNewLandscapeInfo->XYtoComponentMap)
|
|
{
|
|
ULandscapeHeightfieldCollisionComponent* NewCollisionComponent = NewEntry.Value->GetCollisionComponent();
|
|
|
|
if (NewCollisionComponent && FBoxSphereBounds::BoxesIntersect(NewCollisionComponent->Bounds, OldCollisionComponent->Bounds))
|
|
{
|
|
// only transfer instances overlapping the new box in x,y
|
|
FBox Box = NewCollisionComponent->Bounds.GetBox();
|
|
FBox OldBox = OldCollisionComponent->Bounds.GetBox();
|
|
|
|
// but allow just about any Z (expand old bounds by max extent)
|
|
double Extent = OldBox.GetExtent().GetMax();
|
|
Box.Min.Z = OldBox.Min.Z - Extent;
|
|
Box.Max.Z = OldBox.Max.Z + Extent;
|
|
|
|
AInstancedFoliageActor::MoveInstancesToNewComponent(World, OldCollisionComponent, Box, NewCollisionComponent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Snap them to the bounds
|
|
for (const TPair<FIntPoint, ULandscapeComponent*>& NewEntry : InNewLandscapeInfo->XYtoComponentMap)
|
|
{
|
|
ULandscapeHeightfieldCollisionComponent* NewCollisionComponent = NewEntry.Value->GetCollisionComponent();
|
|
|
|
if (NewCollisionComponent)
|
|
{
|
|
FBox Box = NewCollisionComponent->Bounds.GetBox();
|
|
Box.Min.Z = -WORLD_MAX;
|
|
Box.Max.Z = WORLD_MAX;
|
|
|
|
NewCollisionComponent->SnapFoliageInstances(Box);
|
|
}
|
|
}
|
|
}
|
|
|
|
ULandscapeInfo* FLandscapeConfigHelper::ChangeConfiguration(ULandscapeInfo* InLandscapeInfo, const FLandscapeConfigChange& InNewConfig, TSet<AActor*>& OutActorsToDelete, TSet<AActor*>& OutModifiedActors)
|
|
{
|
|
// @todo_ow: output a list of ModifiedActors
|
|
|
|
if (!InNewConfig.Validate())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
check(InLandscapeInfo);
|
|
ALandscape* OldLandscape = InLandscapeInfo->LandscapeActor.Get();
|
|
check(OldLandscape);
|
|
|
|
UWorld* World = OldLandscape->GetWorld();
|
|
UActorPartitionSubsystem* ActorPartitionSubsystem = World->GetSubsystem<UActorPartitionSubsystem>();
|
|
FLandscapeConfig OldConfig(InLandscapeInfo);
|
|
|
|
bool bGridSizeOnly = false;
|
|
if (OldConfig.ComponentNumSubsections == InNewConfig.ComponentNumSubsections &&
|
|
OldConfig.SubsectionSizeQuads == InNewConfig.SubsectionSizeQuads)
|
|
{
|
|
if (OldConfig.GridSizeInComponents == InNewConfig.GridSizeInComponents)
|
|
{
|
|
// Nothing to do
|
|
return nullptr;
|
|
}
|
|
|
|
// Simple redistribute
|
|
if (!FLandscapeConfigHelper::ChangeGridSize(InLandscapeInfo, InNewConfig.GridSizeInComponents, OutActorsToDelete))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
return InLandscapeInfo;
|
|
}
|
|
|
|
int32 LSMinX, LSMinY, LSMaxX, LSMaxY;
|
|
if (!InLandscapeInfo->GetLandscapeExtent(LSMinX, LSMinY, LSMaxX, LSMaxY))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
const int32 NewComponentSizeQuads = InNewConfig.GetComponentSizeQuads();
|
|
const int32 OldComponentSizeQuads = OldConfig.GetComponentSizeQuads();
|
|
|
|
const bool bResample = InNewConfig.ResizeMode == ELandscapeResizeMode::Resample;
|
|
const float SizeFactor = (float)OldComponentSizeQuads / NewComponentSizeQuads;
|
|
const float NewScaleFactor = bResample ? SizeFactor : 1.0f;
|
|
const FVector OldScale = OldLandscape->GetActorScale();
|
|
const FVector NewScale = OldScale * NewScaleFactor;
|
|
|
|
const FIntPoint OldSize(LSMaxX - LSMinX, LSMaxY - LSMinY);
|
|
FIntPoint NewLSMin((LSMinX / OldComponentSizeQuads) * NewComponentSizeQuads, (LSMinY / OldComponentSizeQuads) * NewComponentSizeQuads);
|
|
|
|
FVector ActorOffset(0, 0, 0);
|
|
if (InNewConfig.bZeroBased)
|
|
{
|
|
ActorOffset = FVector(NewLSMin) * OldScale * SizeFactor;
|
|
NewLSMin = FIntPoint(0, 0);
|
|
}
|
|
|
|
FIntPoint NewLSMax(NewLSMin.X + (OldSize.X / OldComponentSizeQuads) * NewComponentSizeQuads, NewLSMin.Y + (OldSize.Y / OldComponentSizeQuads) * NewComponentSizeQuads);
|
|
|
|
if (InNewConfig.ResizeMode == ELandscapeResizeMode::Expand)
|
|
{
|
|
NewLSMax.X = NewLSMin.X + FMath::DivideAndRoundUp(OldSize.X, NewComponentSizeQuads) * NewComponentSizeQuads;
|
|
NewLSMax.Y = NewLSMin.Y + FMath::DivideAndRoundUp(OldSize.Y, NewComponentSizeQuads) * NewComponentSizeQuads;
|
|
}
|
|
else if (InNewConfig.ResizeMode == ELandscapeResizeMode::Clip)
|
|
{
|
|
NewLSMax.X = NewLSMin.X + FMath::Max(1, OldSize.X / NewComponentSizeQuads) * NewComponentSizeQuads;
|
|
NewLSMax.Y = NewLSMin.Y + FMath::Max(1, OldSize.Y / NewComponentSizeQuads) * NewComponentSizeQuads;
|
|
}
|
|
|
|
FActorSpawnParameters SpawnParams;
|
|
ALandscape* NewLandscape = World->SpawnActor<ALandscape>(OldLandscape->GetActorLocation() + ActorOffset, OldLandscape->GetActorRotation(), SpawnParams);
|
|
|
|
NewLandscape->CopySharedProperties(OldLandscape);
|
|
NewLandscape->SetLandscapeGuid(FGuid::NewGuid());
|
|
NewLandscape->SetGridSize(InNewConfig.GetGridSizeQuads());
|
|
NewLandscape->ComponentSizeQuads = InNewConfig.GetComponentSizeQuads();
|
|
NewLandscape->NumSubsections = InNewConfig.ComponentNumSubsections;
|
|
NewLandscape->SubsectionSizeQuads = InNewConfig.SubsectionSizeQuads;
|
|
NewLandscape->SetActorRelativeScale3D(NewScale);
|
|
|
|
// Copy Layer Data
|
|
if (OldLandscape->HasLayersContent())
|
|
{
|
|
NewLandscape->bCanHaveLayersContent = true;
|
|
for (const FLandscapeLayer& OldLayer : OldLandscape->GetLayersConst())
|
|
{
|
|
const FLandscapeLayer* NewLayer = NewLandscape->DuplicateLayerAndMoveBrushes(OldLayer);
|
|
check((NewLayer == nullptr) || (NewLayer->EditLayer != nullptr)); // it's possible DuplicateLayerAndMoveBrushes fails (e.g. max number of layers reached), but if not, we should always have an EditLayer
|
|
}
|
|
}
|
|
|
|
ULandscapeInfo* NewLandscapeInfo = NewLandscape->CreateLandscapeInfo();
|
|
|
|
for (int32 X = NewLSMin.X; X < NewLSMax.X; X+=NewComponentSizeQuads)
|
|
{
|
|
for (int32 Y = NewLSMin.Y; Y < NewLSMax.Y; Y+=NewComponentSizeQuads)
|
|
{
|
|
FIntPoint ComponentKey(X/NewComponentSizeQuads, Y/NewComponentSizeQuads);
|
|
FIntPoint NewSectionBase(X, Y);
|
|
|
|
// Means we don't have more data to copy...
|
|
if (!bResample && ((NewSectionBase.X-NewLSMin.X) > OldSize.X || (NewSectionBase.Y-NewLSMin.Y) > OldSize.Y))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
UActorPartitionSubsystem::FCellCoord CellCoord = UActorPartitionSubsystem::FCellCoord::GetCellCoord(NewSectionBase, NewLandscape->GetLevel(), NewLandscape->GetGridSize());
|
|
ALandscapeProxy* LandscapeProxy = FLandscapeConfigHelper::FindOrAddLandscapeStreamingProxy(ActorPartitionSubsystem, NewLandscapeInfo, CellCoord);
|
|
check(LandscapeProxy);
|
|
|
|
ULandscapeComponent* NewComponent = NewObject<ULandscapeComponent>(LandscapeProxy, NAME_None, RF_Transactional);
|
|
NewComponent->Init(NewSectionBase.X, NewSectionBase.Y, NewComponentSizeQuads, InNewConfig.ComponentNumSubsections, InNewConfig.SubsectionSizeQuads);
|
|
|
|
// Add to map before resample so that SetHeightData/SetWeightData can find the component. XYtoComponentMap is usually updated in RegisterComponent
|
|
NewLandscapeInfo->XYtoComponentMap.Add(ComponentKey, NewComponent);
|
|
|
|
FIntRect SrcRegion(0, 0, 0, 0);
|
|
if (bResample)
|
|
{
|
|
FIntPoint OldSectionBase(ComponentKey.X* OldComponentSizeQuads, ComponentKey.Y* OldComponentSizeQuads);
|
|
SrcRegion.Min = OldSectionBase;
|
|
SrcRegion.Max = OldSectionBase + OldComponentSizeQuads;
|
|
if (InNewConfig.bZeroBased)
|
|
{
|
|
SrcRegion += FIntPoint(LSMinX, LSMinY);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FIntPoint RegionOffset(NewLSMin.X - LSMinX, NewLSMin.Y - LSMinY);
|
|
SrcRegion.Min = NewSectionBase - RegionOffset;
|
|
SrcRegion.Max.X = FMath::Min(LSMaxX, SrcRegion.Min.X + NewComponentSizeQuads);
|
|
SrcRegion.Max.Y = FMath::Min(LSMaxY, SrcRegion.Min.Y + NewComponentSizeQuads);
|
|
}
|
|
|
|
CopyRegionToComponent(InLandscapeInfo, SrcRegion, bResample, NewComponent);
|
|
|
|
NewComponent->UpdateMaterialInstances();
|
|
NewComponent->UpdateCachedBounds();
|
|
NewComponent->UpdateBounds();
|
|
|
|
NewComponent->RegisterComponent();
|
|
}
|
|
}
|
|
|
|
if (NewLandscapeInfo->CanHaveLayersContent())
|
|
{
|
|
NewLandscapeInfo->ForceLayersFullUpdate();
|
|
}
|
|
else
|
|
{
|
|
FLandscapeEditDataInterface LandscapeEdit(NewLandscapeInfo);
|
|
LandscapeEdit.RecalculateNormals();
|
|
}
|
|
|
|
// Do as last step so that components have a collision component
|
|
MoveFoliageToLandscape(InLandscapeInfo, NewLandscapeInfo);
|
|
|
|
return NewLandscapeInfo;
|
|
}
|
|
|
|
#endif
|