#include "Authoring/LandscapeAuthoringLibrary.h" #include "SaltyEditor.h" #include "Landscape.h" #include "LandscapeInfo.h" #include "LandscapeImportHelper.h" #include "LandscapeLayerInfoObject.h" #include "LandscapeSubsystem.h" #include "Engine/World.h" #include "Materials/MaterialInterface.h" #include "RenderingThread.h" ALandscape* ULandscapeAuthoringLibrary::CreateLandscapeFromHeightmap(UWorld* World, const FString& HeightmapFile, const TArray& Weightmaps, UMaterialInterface* Material, FVector Location, FVector Scale, int32 WorldPartitionGridSize) { if (!World) { UE_LOG(LogSaltyEditor, Error, TEXT("CreateLandscapeFromHeightmap: no world")); return nullptr; } // The heightmap decides the resolution; everything else follows it. FLandscapeImportDescriptor HeightDescriptor; FText Message; if (FLandscapeImportHelper::GetHeightmapImportDescriptor(HeightmapFile, /*bSingleFile*/ true, /*bFlipYAxis*/ false, HeightDescriptor, Message) == ELandscapeImportResult::Error || HeightDescriptor.ImportResolutions.Num() == 0) { UE_LOG(LogSaltyEditor, Error, TEXT("CreateLandscapeFromHeightmap: cannot read %s: %s"), *HeightmapFile, *Message.ToString()); return nullptr; } const FLandscapeImportResolution FileResolution = HeightDescriptor.ImportResolutions[0]; TArray HeightData; if (FLandscapeImportHelper::GetHeightmapImportData(HeightDescriptor, 0, HeightData, Message) == ELandscapeImportResult::Error) { UE_LOG(LogSaltyEditor, Error, TEXT("CreateLandscapeFromHeightmap: cannot import %s: %s"), *HeightmapFile, *Message.ToString()); return nullptr; } // The same layout choice the editor makes for an imported file. int32 QuadsPerSection = 63; int32 SectionsPerComponent = 1; FIntPoint ComponentCount; FLandscapeImportHelper::ChooseBestComponentSizeForImport(FileResolution.Width, FileResolution.Height, QuadsPerSection, SectionsPerComponent, ComponentCount); const int32 QuadsPerComponent = QuadsPerSection * SectionsPerComponent; const FLandscapeImportResolution RequiredResolution(ComponentCount.X * QuadsPerComponent + 1, ComponentCount.Y * QuadsPerComponent + 1); if (RequiredResolution != FileResolution) { UE_LOG(LogSaltyEditor, Warning, TEXT("CreateLandscapeFromHeightmap: %ux%u does not fit a component layout; expanding to %ux%u"), FileResolution.Width, FileResolution.Height, RequiredResolution.Width, RequiredResolution.Height); TArray Expanded; FLandscapeImportHelper::TransformHeightmapImportData(HeightData, Expanded, FileResolution, RequiredResolution, ELandscapeImportTransformType::ExpandCentered); HeightData = MoveTemp(Expanded); } TArray LayerInfos; for (const FLandscapeAuthoringWeightmap& Weightmap : Weightmaps) { if (!Weightmap.LayerInfo) { UE_LOG(LogSaltyEditor, Warning, TEXT("CreateLandscapeFromHeightmap: a weightmap has no layer info; skipped")); continue; } FLandscapeImportLayerInfo Info(Weightmap.LayerInfo->GetLayerName()); Info.LayerInfo = Weightmap.LayerInfo; Info.SourceFilePath = Weightmap.File; FLandscapeImportDescriptor WeightDescriptor; if (FLandscapeImportHelper::GetWeightmapImportDescriptor(Weightmap.File, true, false, Info.LayerName, WeightDescriptor, Message) == ELandscapeImportResult::Error || WeightDescriptor.ImportResolutions.Num() == 0 || FLandscapeImportHelper::GetWeightmapImportData(WeightDescriptor, 0, Info.LayerName, Info.LayerData, Message) == ELandscapeImportResult::Error) { UE_LOG(LogSaltyEditor, Error, TEXT("CreateLandscapeFromHeightmap: cannot read weightmap %s: %s"), *Weightmap.File, *Message.ToString()); return nullptr; } if (WeightDescriptor.ImportResolutions[0] != RequiredResolution) { TArray Expanded; FLandscapeImportHelper::TransformWeightmapImportData(Info.LayerData, Expanded, WeightDescriptor.ImportResolutions[0], RequiredResolution, ELandscapeImportTransformType::ExpandCentered); Info.LayerData = MoveTemp(Expanded); } LayerInfos.Add(MoveTemp(Info)); } TMap> HeightDataPerLayer; HeightDataPerLayer.Add(FGuid(), MoveTemp(HeightData)); TMap> LayerDataPerLayer; LayerDataPerLayer.Add(FGuid(), LayerInfos); // Centre the landscape on Location, as the editor centres it on the gizmo. const FVector Offset = FTransform(FRotator::ZeroRotator, FVector::ZeroVector, Scale) .TransformVector(FVector(-ComponentCount.X * QuadsPerComponent / 2.0, -ComponentCount.Y * QuadsPerComponent / 2.0, 0.0)); ALandscape* Landscape = World->SpawnActor(Location + Offset, FRotator::ZeroRotator); if (!Landscape) { UE_LOG(LogSaltyEditor, Error, TEXT("CreateLandscapeFromHeightmap: SpawnActor failed")); return nullptr; } Landscape->LandscapeMaterial = Material; Landscape->SetActorRelativeScale3D(Scale); Landscape->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((RequiredResolution.Width * RequiredResolution.Height) / (2048 * 2048) + 1), (uint32)2); Landscape->Import(FGuid::NewGuid(), 0, 0, RequiredResolution.Width - 1, RequiredResolution.Height - 1, SectionsPerComponent, QuadsPerSection, HeightDataPerLayer, *HeightmapFile, LayerDataPerLayer, ELandscapeImportAlphamapType::Additive, TArrayView()); ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo(); if (!LandscapeInfo) { UE_LOG(LogSaltyEditor, Error, TEXT("CreateLandscapeFromHeightmap: Import produced no landscape info")); return nullptr; } Landscape->SetActorLabel(TEXT("Landscape")); LandscapeInfo->UpdateLayerInfoMap(Landscape); for (const FLandscapeImportLayerInfo& Info : LayerInfos) { Landscape->AddTargetLayer(Info.LayerInfo->GetLayerName(), FLandscapeTargetLayerSettings(Info.LayerInfo, Info.SourceFilePath)); const int32 LayerIndex = LandscapeInfo->GetLayerInfoIndex(Info.LayerName); if (LayerIndex != INDEX_NONE) { LandscapeInfo->Layers[LayerIndex].LayerInfoObj = Info.LayerInfo; } } if (ULandscapeSubsystem* Subsystem = World->GetSubsystem()) { if (Subsystem->IsGridBased() && WorldPartitionGridSize > 0) { Subsystem->ChangeGridSize(LandscapeInfo, static_cast(WorldPartitionGridSize)); } } // Edit layers are always on, and the render heightmaps and weightmaps are produced from the imported layer // data by the edit-layer merge, which normally runs on the editor tick. A commandlet never ticks, so without // this the saved level has the imported collision heights but flat, empty render textures, and the landscape // is invisible from below. The editor's own import does the same full update before saving. Landscape->ForceLayersFullUpdate(); for (int32 Attempt = 0; Attempt < 500 && !Landscape->IsUpToDate(); ++Attempt) { Landscape->TickLayers(1.0f / 30.0f); FlushRenderingCommands(); } if (!Landscape->IsUpToDate()) { UE_LOG(LogSaltyEditor, Warning, TEXT("CreateLandscapeFromHeightmap: the edit-layer update did not finish; render heightmaps may be stale")); } UE_LOG(LogSaltyEditor, Log, TEXT("CreateLandscapeFromHeightmap: %ux%u vertices, %dx%d components of %d quads, scale (%g, %g, %g), %d paint layers"), RequiredResolution.Width, RequiredResolution.Height, ComponentCount.X, ComponentCount.Y, QuadsPerComponent, Scale.X, Scale.Y, Scale.Z, LayerInfos.Num()); return Landscape; }