153 lines
7.2 KiB
C++
153 lines
7.2 KiB
C++
#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<FLandscapeAuthoringWeightmap>& 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<uint16> 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<uint16> Expanded;
|
|
FLandscapeImportHelper::TransformHeightmapImportData(HeightData, Expanded, FileResolution, RequiredResolution, ELandscapeImportTransformType::ExpandCentered);
|
|
HeightData = MoveTemp(Expanded);
|
|
}
|
|
|
|
TArray<FLandscapeImportLayerInfo> 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<uint8> Expanded;
|
|
FLandscapeImportHelper::TransformWeightmapImportData(Info.LayerData, Expanded, WeightDescriptor.ImportResolutions[0], RequiredResolution, ELandscapeImportTransformType::ExpandCentered);
|
|
Info.LayerData = MoveTemp(Expanded);
|
|
}
|
|
LayerInfos.Add(MoveTemp(Info));
|
|
}
|
|
|
|
TMap<FGuid, TArray<uint16>> HeightDataPerLayer;
|
|
HeightDataPerLayer.Add(FGuid(), MoveTemp(HeightData));
|
|
TMap<FGuid, TArray<FLandscapeImportLayerInfo>> 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<ALandscape>(Location + Offset, FRotator::ZeroRotator);
|
|
if (!Landscape)
|
|
{
|
|
UE_LOG(LogSaltyEditor, Error, TEXT("CreateLandscapeFromHeightmap: SpawnActor<ALandscape> 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<const FLandscapeLayer>());
|
|
|
|
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<ULandscapeSubsystem>())
|
|
{
|
|
if (Subsystem->IsGridBased() && WorldPartitionGridSize > 0)
|
|
{
|
|
Subsystem->ChangeGridSize(LandscapeInfo, static_cast<uint32>(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;
|
|
}
|