// 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(); 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(InCellCoord.X) * Landscape->GetGridSize(), static_cast(InCellCoord.Y) * Landscape->GetGridSize()); ALandscapeProxy* LandscapeProxy = CastChecked(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(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& 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(); TArray LandscapeComponents; LandscapeComponents.Reserve(InLandscapeInfo->XYtoComponentMap.Num()); InLandscapeInfo->ForAllLandscapeComponents([&LandscapeComponents](ULandscapeComponent* LandscapeComponent) { LandscapeComponents.Add(LandscapeComponent); }); TSet ProxiesToDelete; FActorPartitionGridHelper::ForEachIntersectingCell(ALandscapeStreamingProxy::StaticClass(), Extent, InLandscapeInfo->LandscapeActor->GetLevel(), [ActorPartitionSubsystem, InLandscapeInfo, InNewGridSizeInComponents, &LandscapeComponents, &ProxiesToDelete](const UActorPartitionSubsystem::FCellCoord& CellCoord, const FIntRect& CellBounds) { TMap ComponentMaterials; TMap ComponentHoleMaterials; TMap > ComponentLODMaterials; TArray 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& 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()); } 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 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 PerLODOverrideMaterialsForComponent; TArray 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(PreviousLODMaterial) }); } else // If it already differs it means that the component differs from it, override on component { PerLODOverrideMaterialsForComponent.Add({ LODIndex, TObjectPtr(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()) { 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 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::StaticClass(), LandscapeTransform, SpawnParams); NewLandscape->CopySharedProperties(FirstProxy); InLandscapeInfo->RegisterActor(NewLandscape); } } auto MoveControlPointToNewSplineActor = [&NewSplineActors, InLandscapeInfo](ULandscapeSplineControlPoint* ControlPoint) { AActor* CurrentOwner = ControlPoint->GetTypedOuter(); // 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> OldSplineActors(InLandscapeInfo->GetSplineActors()); for (TScriptInterface PreviousSplineActor : OldSplineActors) { if (ULandscapeSplinesComponent* SplineComponent = PreviousSplineActor->GetSplinesComponent()) { SplineComponent->ForEachControlPoint(MoveControlPointToNewSplineActor); } } TSet 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& OutHeightData, TArray& 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 SrcHeightData; TArray 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 NewHeightData; TArray 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 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 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& OldEntry : InLandscapeInfo->XYtoComponentMap) { ULandscapeHeightfieldCollisionComponent* OldCollisionComponent = OldEntry.Value->GetCollisionComponent(); if (OldCollisionComponent) { UWorld* World = OldCollisionComponent->GetWorld(); for (const TPair& 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& 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& OutActorsToDelete, TSet& 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(); 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(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(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