This commit is contained in:
Rainer Leit
2026-09-25 17:02:24 +03:00
parent cc43ed8dc8
commit 9597629951
2149 changed files with 460234 additions and 1770 deletions
@@ -2,12 +2,15 @@
#include "SaltyEditor.h"
#include "Landscape.h"
#include "LandscapeInfo.h"
#include "LandscapeComponent.h"
#include "LandscapeImportHelper.h"
#include "LandscapeLayerInfoObject.h"
#include "LandscapeSubsystem.h"
#include "Engine/World.h"
#include "Materials/MaterialInterface.h"
#include "RenderingThread.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "UObject/Package.h"
ALandscape* ULandscapeAuthoringLibrary::CreateLandscapeFromHeightmap(UWorld* World, const FString& HeightmapFile,
const TArray<FLandscapeAuthoringWeightmap>& Weightmaps, UMaterialInterface* Material,
@@ -80,6 +83,22 @@ ALandscape* ULandscapeAuthoringLibrary::CreateLandscapeFromHeightmap(UWorld* Wor
FLandscapeImportHelper::TransformWeightmapImportData(Info.LayerData, Expanded, WeightDescriptor.ImportResolutions[0], RequiredResolution, ELandscapeImportTransformType::ExpandCentered);
Info.LayerData = MoveTemp(Expanded);
}
// An empty layer would be dropped without a word further down: ALandscapeProxy::Import only calls
// SetAlphaData when `MaterialLayerInfo.LayerData.Num() > 0`, so no samples here would mean a layer that
// is silently never painted. It should not be reachable - FLandscapeImportHelper's reader calls
// SetNumZeroed(TotalPixels) before it decodes anything, so a descriptor that resolved at all comes back
// full-sized - which is exactly why it is worth a check rather than a comment: the day it is reachable
// is the day a whole world comes out shaded as the material's first layer with nothing logged anywhere.
if (Info.LayerData.Num() == 0)
{
UE_LOG(LogSaltyEditor, Error, TEXT("CreateLandscapeFromHeightmap: layer %s read no samples from %s; the engine would have dropped it silently"),
*Info.LayerName.ToString(), *Weightmap.File);
return nullptr;
}
UE_LOG(LogSaltyEditor, Log, TEXT("CreateLandscapeFromHeightmap: layer %s <- %s, %d samples at %ux%u"),
*Info.LayerName.ToString(), *Weightmap.File, Info.LayerData.Num(),
WeightDescriptor.ImportResolutions[0].Width, WeightDescriptor.ImportResolutions[0].Height);
LayerInfos.Add(MoveTemp(Info));
}
@@ -146,7 +165,80 @@ ALandscape* ULandscapeAuthoringLibrary::CreateLandscapeFromHeightmap(UWorld* Wor
UE_LOG(LogSaltyEditor, Warning, TEXT("CreateLandscapeFromHeightmap: the edit-layer update did not finish; render heightmaps may be stale"));
}
// Did the weights actually survive the import and the merge? A component with no weightmap allocations is a
// landscape the material will shade entirely from its preview weights, which looks like a material problem
// and is not one. Checked rather than assumed, because every failure mode above this line is silent.
{
TArray<ULandscapeComponent*> Components;
Landscape->GetComponents<ULandscapeComponent>(Components);
if (Components.Num() > 0 && Components[0] != nullptr)
{
const int32 Allocations = Components[0]->GetWeightmapLayerAllocations().Num();
const int32 Textures = Components[0]->GetWeightmapTextures().Num();
if (Allocations == 0 && LayerInfos.Num() > 0)
{
UE_LOG(LogSaltyEditor, Error, TEXT("CreateLandscapeFromHeightmap: %d layers were imported but component 0 has no weightmap allocations; the ground will render as the material's first layer everywhere"),
LayerInfos.Num());
}
else
{
UE_LOG(LogSaltyEditor, Log, TEXT("CreateLandscapeFromHeightmap: component 0 carries %d weightmap allocation(s) across %d texture(s)"),
Allocations, Textures);
}
}
}
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;
}
ULandscapeLayerInfoObject* ULandscapeAuthoringLibrary::CreateLayerInfo(const FString& PackagePath,
const FString& AssetName, FName LayerName)
{
if (PackagePath.IsEmpty() || AssetName.IsEmpty() || LayerName.IsNone())
{
UE_LOG(LogSaltyEditor, Error, TEXT("CreateLayerInfo: needs a package path, an asset name and a layer name"));
return nullptr;
}
const FString PackageName = PackagePath / AssetName;
// Rerunnable: an existing layer info is returned as it is. Replacing one would break every landscape that
// already paints with it, and the thing a caller wants here is "make sure this exists".
if (ULandscapeLayerInfoObject* Existing = LoadObject<ULandscapeLayerInfoObject>(nullptr, *(PackageName + TEXT(".") + AssetName)))
{
if (Existing->GetLayerName() != LayerName)
{
UE_LOG(LogSaltyEditor, Warning, TEXT("CreateLayerInfo: %s already exists and its LayerName is %s, not %s; left alone"),
*PackageName, *Existing->GetLayerName().ToString(), *LayerName.ToString());
}
return Existing;
}
UPackage* Package = CreatePackage(*PackageName);
if (!Package)
{
UE_LOG(LogSaltyEditor, Error, TEXT("CreateLayerInfo: could not create package %s"), *PackageName);
return nullptr;
}
ULandscapeLayerInfoObject* Info = NewObject<ULandscapeLayerInfoObject>(
Package, *AssetName, RF_Public | RF_Standalone | RF_Transactional);
if (!Info)
{
UE_LOG(LogSaltyEditor, Error, TEXT("CreateLayerInfo: could not create %s"), *PackageName);
return nullptr;
}
// The whole reason this function exists: LayerName is VisibleAnywhere, so Python cannot set it and a
// duplicated layer info keeps the name it was copied from. Through the setter, not the member: the member
// is deprecated and goes private next release, and bInModify false because the object was made a moment
// ago and has nothing to record an undo against.
Info->SetLayerName(LayerName, /*bInModify=*/false);
Info->MarkPackageDirty();
FAssetRegistryModule::AssetCreated(Info);
UE_LOG(LogSaltyEditor, Log, TEXT("CreateLayerInfo: %s with LayerName %s"), *PackageName, *LayerName.ToString());
return Info;
}