1083 lines
44 KiB
C++
1083 lines
44 KiB
C++
|
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||
|
|
|
||
|
|
|
||
|
|
#include "GaeaSubsystem.h"
|
||
|
|
|
||
|
|
#include "DesktopPlatformModule.h"
|
||
|
|
#include "FileHelpers.h"
|
||
|
|
#include "Widgets/SWindow.h"
|
||
|
|
#include "GWindow.h"
|
||
|
|
#include "LandscapeEditorObject.h"
|
||
|
|
#include "SlateBasics.h"
|
||
|
|
#include "Widgets/Layout/SBox.h"
|
||
|
|
#include "Widgets/Text/STextBlock.h"
|
||
|
|
#include "Widgets/Input/SButton.h"
|
||
|
|
#include "Landscape.h"
|
||
|
|
#include "LandscapeProxy.h"
|
||
|
|
#include "LandscapeRegionUtils.h"
|
||
|
|
#include "LandscapeStreamingProxy.h"
|
||
|
|
#include "Serialization/JsonSerializer.h"
|
||
|
|
#include "JsonObjectConverter.h"
|
||
|
|
#include "LandscapeEditorUtils.h"
|
||
|
|
#include "LandscapeSubsystem.h"
|
||
|
|
#include "LocationVolume.h"
|
||
|
|
#include "ActorFactories/ActorFactory.h"
|
||
|
|
#include "ToolMenus.h"
|
||
|
|
#include "Builders/CubeBuilder.h"
|
||
|
|
#include "Subsystems/EditorActorSubsystem.h"
|
||
|
|
#include "VT/VirtualTexture.h"
|
||
|
|
#include "GaeaLandscapeComponent.h"
|
||
|
|
#include "AssetRegistry/AssetRegistryModule.h"
|
||
|
|
#include "AssetToolsModule.h"
|
||
|
|
#include "AsyncTreeDifferences.h"
|
||
|
|
#include "EditorAssetLibrary.h"
|
||
|
|
#include "Modules/ModuleManager.h"
|
||
|
|
#include "Misc/PackageName.h"
|
||
|
|
#include "LandscapeInfo.h"
|
||
|
|
#include "Editor.h"
|
||
|
|
#include "LandscapeEditLayer.h"
|
||
|
|
#include "LandscapeEdit.h"
|
||
|
|
|
||
|
|
|
||
|
|
#include "Materials/MaterialExpressionLandscapeLayerBlend.h"
|
||
|
|
#include "WorldPartition/WorldPartition.h"
|
||
|
|
|
||
|
|
|
||
|
|
DEFINE_LOG_CATEGORY(GaeaSubsystem)
|
||
|
|
|
||
|
|
#define LOCTEXT_NAMESPACE "GaeaSubsystem"
|
||
|
|
|
||
|
|
|
||
|
|
UGaeaSubsystem* UGaeaSubsystem::GetGaeaSubsystem()
|
||
|
|
{
|
||
|
|
return GEditor->GetEditorSubsystem<UGaeaSubsystem>();
|
||
|
|
}
|
||
|
|
|
||
|
|
/*void UGaeaSubsystem::SpawnGMCWindow()
|
||
|
|
{
|
||
|
|
//Load Property Module
|
||
|
|
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
||
|
|
|
||
|
|
//Create Details View Arguments
|
||
|
|
FDetailsViewArgs Args;
|
||
|
|
Args.bAllowSearch = false;
|
||
|
|
Args.bHideSelectionTip = true;
|
||
|
|
Args.bShowScrollBar = true;
|
||
|
|
|
||
|
|
//Create Panel Settings Object
|
||
|
|
PanelSettings = NewObject<UGMCSettings>();
|
||
|
|
PropertyWidget = PropertyModule.CreateDetailView(Args);
|
||
|
|
PropertyWidget->SetObject(PanelSettings);
|
||
|
|
|
||
|
|
// Create Gaea Window Instance
|
||
|
|
const TSharedRef<SWindow> WindowRef = SNew(SGaeaWindow)
|
||
|
|
.Title(FText::FromString("Gaea Landscape Material Creator"))
|
||
|
|
.ClientSize(FVector2D(475, 200))
|
||
|
|
.SizingRule(ESizingRule::UserSized);
|
||
|
|
|
||
|
|
|
||
|
|
// Create Window with Details View and Button
|
||
|
|
WindowRef->SetContent(
|
||
|
|
SNew(SVerticalBox)
|
||
|
|
+ SVerticalBox::Slot()
|
||
|
|
.FillHeight(1)
|
||
|
|
[
|
||
|
|
SNew(SScrollBox)
|
||
|
|
+ SScrollBox::Slot()
|
||
|
|
[
|
||
|
|
PropertyWidget.ToSharedRef()
|
||
|
|
]
|
||
|
|
]
|
||
|
|
+ SVerticalBox::Slot()
|
||
|
|
//.Padding(0)
|
||
|
|
.AutoHeight()
|
||
|
|
.HAlign(HAlign_Fill)
|
||
|
|
.VAlign(VAlign_Bottom)
|
||
|
|
[
|
||
|
|
SNew(SButton)
|
||
|
|
.Text(FText::FromString(("Generate Landscape Material")))
|
||
|
|
.HAlign(HAlign_Center)
|
||
|
|
.VAlign(VAlign_Center)
|
||
|
|
.ButtonColorAndOpacity(FColor::Emerald)
|
||
|
|
]
|
||
|
|
);
|
||
|
|
|
||
|
|
// Check if TWeakPtr is valid before spawning a window
|
||
|
|
if(WindowValidator.IsValid()==false)
|
||
|
|
{
|
||
|
|
FSlateApplication::Get().AddWindow(WindowRef);
|
||
|
|
/*if(GEngine)
|
||
|
|
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Cyan, TEXT("Spawned the Window."));#1#
|
||
|
|
WindowValidator = WindowRef;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
WindowValidator.Pin()->BringToFront(true);
|
||
|
|
/*if(GEngine)
|
||
|
|
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Yellow, TEXT("Window Exists Already."));#1#
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
}*/
|
||
|
|
|
||
|
|
|
||
|
|
void UGaeaSubsystem::SpawnGImporterWindow()
|
||
|
|
{
|
||
|
|
if(ImporterWindowValidator.IsValid()==false)
|
||
|
|
{
|
||
|
|
const TSharedRef<SWindow> WindowRef = SNew(SGaeaImportWindow)
|
||
|
|
.Title(FText::FromString("Gaea Landscape Importer"))
|
||
|
|
.ClientSize(FVector2D(500, 385))
|
||
|
|
.SizingRule(ESizingRule::UserSized);
|
||
|
|
|
||
|
|
FSlateApplication::Get().AddWindow(WindowRef);
|
||
|
|
ImporterWindowValidator = WindowRef;
|
||
|
|
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
ImporterWindowValidator.Pin()->BringToFront(true);
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void UGaeaSubsystem::ReimportGaeaTerrain()
|
||
|
|
{
|
||
|
|
TArray<ULandscapeLayerInfoObject*> InfoObjects;
|
||
|
|
TArray<FLandscapeImportDescriptor> WeightOutImportDescriptors;
|
||
|
|
TArray<ELandscapeImportResult> WeightImportResults;
|
||
|
|
TArray<FLandscapeImportLayerInfo> MaterialImportLayers;
|
||
|
|
TArray<FText> WeightOutMessage;
|
||
|
|
bool ReimportWeightmaps = false;
|
||
|
|
TArray<uint8> WeightOutData;
|
||
|
|
TArray<uint8> FinalWeightOutData;
|
||
|
|
|
||
|
|
if (UEditorActorSubsystem* ActorSubsystem = GEditor->GetEditorSubsystem<UEditorActorSubsystem>())
|
||
|
|
{
|
||
|
|
const TArray<AActor*>& SelectedActors = ActorSubsystem->GetSelectedLevelActors(); // Use the subsystem instance to get references to selected actors.
|
||
|
|
UGaeaLandscapeComponent* GaeaComponent = SelectedActors[0]->FindComponentByClass<UGaeaLandscapeComponent>(); // Check for a Gaea Landscape Component.
|
||
|
|
if (SelectedActors.Num() > 0 && GaeaComponent) // Check if component is valid and we actually have a selected actor.
|
||
|
|
{
|
||
|
|
AActor* Actor = SelectedActors[0];
|
||
|
|
ALandscape* Landscape = Cast<ALandscape>(Actor);
|
||
|
|
|
||
|
|
ULandscapeInfo* LandscapeActorInfo = Landscape->GetLandscapeInfo();
|
||
|
|
|
||
|
|
FString JsonPath = GaeaComponent->DefinitionFilepath.FilePath;
|
||
|
|
FString HeightPath = GaeaComponent->HeightmapFilepath.FilePath;
|
||
|
|
|
||
|
|
bool bStatus = false;
|
||
|
|
FString JsonMessage = "";
|
||
|
|
const FGaeaJson GaeaDefinition = CreateStructFromJson(JsonPath, bStatus, JsonMessage);
|
||
|
|
|
||
|
|
if(bStatus)
|
||
|
|
{
|
||
|
|
UE_LOG(GaeaSubsystem, Display, TEXT("ScaleX: %f, ScaleY: %f, Height: %f, Resolution: %d"),
|
||
|
|
GaeaDefinition.ScaleX, GaeaDefinition.ScaleY, GaeaDefinition.Height, GaeaDefinition.Resolution);
|
||
|
|
FVector LandscapeLocation = FVector(0,0,GaeaDefinition.Height*100/2);
|
||
|
|
FVector LandscapeScale = FVector(GaeaDefinition.ScaleX * 100 / GaeaDefinition.Resolution,GaeaDefinition.ScaleY * 100 / GaeaDefinition.Resolution,GaeaDefinition.Height * 100 / 512); // Apply scaling formula to our passed in scale variable from the ImporterPanelSettings
|
||
|
|
|
||
|
|
constexpr bool bSingleFile = true;
|
||
|
|
FLandscapeImportDescriptor OutImportDescriptor;
|
||
|
|
OutImportDescriptor.Scale = LandscapeScale;
|
||
|
|
FText OutMessage;
|
||
|
|
|
||
|
|
|
||
|
|
ULandscapeEditorObject* DefaultValueObject = ULandscapeEditorObject::StaticClass()->GetDefaultObject<ULandscapeEditorObject>(); // Create Landscape Editor Object instance
|
||
|
|
check(DefaultValueObject);
|
||
|
|
|
||
|
|
int32 OutQuadsPerSection = DefaultValueObject->NewLandscape_QuadsPerSection;
|
||
|
|
int32 OutSectionsPerComponent = DefaultValueObject->NewLandscape_SectionsPerComponent;
|
||
|
|
FIntPoint OutComponentCount = DefaultValueObject->NewLandscape_ComponentCount;
|
||
|
|
|
||
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
/// Heightmap Data Setup
|
||
|
|
|
||
|
|
ELandscapeImportResult ImportResult = FLandscapeImportHelper::GetHeightmapImportDescriptor(HeightPath, bSingleFile, false, OutImportDescriptor, OutMessage);
|
||
|
|
|
||
|
|
int32 DescriptorIndex = OutImportDescriptor.FileResolutions.Num() / 2;
|
||
|
|
|
||
|
|
FLandscapeImportHelper::ChooseBestComponentSizeForImport(OutImportDescriptor.ImportResolutions[DescriptorIndex].Width, OutImportDescriptor.ImportResolutions[DescriptorIndex].Height, OutQuadsPerSection, OutSectionsPerComponent, OutComponentCount);
|
||
|
|
|
||
|
|
TArray<uint16> ImportData;
|
||
|
|
ImportResult = FLandscapeImportHelper::GetHeightmapImportData(OutImportDescriptor, DescriptorIndex, ImportData, OutMessage);
|
||
|
|
|
||
|
|
const int32 QuadsPerComponent = OutSectionsPerComponent * OutQuadsPerSection;
|
||
|
|
const int32 SizeX = OutComponentCount.X * QuadsPerComponent + 1;
|
||
|
|
const int32 SizeY = OutComponentCount.Y * QuadsPerComponent + 1;
|
||
|
|
|
||
|
|
TArray<uint16> FinalHeightData;
|
||
|
|
FLandscapeImportHelper::TransformHeightmapImportData(ImportData, FinalHeightData, OutImportDescriptor.ImportResolutions[DescriptorIndex], FLandscapeImportResolution(SizeX, SizeY), ELandscapeImportTransformType::ExpandCentered);
|
||
|
|
|
||
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
/// Setup prerequisites for SetHeightData and SetAlphaData
|
||
|
|
|
||
|
|
FIntRect ComponentsRect = Landscape->GetBoundingRect() + Landscape->GetSectionBase();
|
||
|
|
const int32 CompSizeX = ComponentsRect.Width() + 1;
|
||
|
|
const int32 CompSizeY = ComponentsRect.Height() + 1;
|
||
|
|
|
||
|
|
FLandscapeEditDataInterface LandscapeEdit(Landscape->GetLandscapeInfo());
|
||
|
|
|
||
|
|
// New setup for 5.6 to get the Guid of the base edit layer. Required for SetEditLayer and therefore SetHeight/Weight Data to function properly.
|
||
|
|
ULandscapeEditLayerBase* Layer = Landscape->GetEditLayer(0);
|
||
|
|
LandscapeEdit.SetEditLayer(Layer->GetGuid());
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
/// Weightmap Data Setup and SetAlphaData Execution
|
||
|
|
|
||
|
|
if (LandscapeActorInfo && !GaeaComponent->WeightmapFilepaths.IsEmpty())
|
||
|
|
{
|
||
|
|
|
||
|
|
// Loop through all layers associated with the landscape and get the relevant layer objects.
|
||
|
|
for (const FLandscapeInfoLayerSettings& LayerSettings : LandscapeActorInfo->Layers)
|
||
|
|
{
|
||
|
|
if (ULandscapeLayerInfoObject* LayerInfoObject = LayerSettings.LayerInfoObj)
|
||
|
|
{
|
||
|
|
InfoObjects.Add(LayerInfoObject);
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for (int32 i = 0; i < InfoObjects.Num(); i++)
|
||
|
|
{
|
||
|
|
if (InfoObjects[i])
|
||
|
|
{
|
||
|
|
Landscape->ClearEditLayer(i, nullptr, ELandscapeToolTargetTypeFlags::Heightmap);
|
||
|
|
Landscape->ClearEditLayer(i, nullptr, ELandscapeToolTargetTypeFlags::Weightmap);
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
WeightOutMessage.AddDefaulted(InfoObjects.Num());
|
||
|
|
WeightOutImportDescriptors.AddDefaulted(InfoObjects.Num());
|
||
|
|
WeightImportResults.AddDefaulted(InfoObjects.Num());
|
||
|
|
|
||
|
|
TArray<uint8> ClearWeightData;
|
||
|
|
ClearWeightData.SetNum(CompSizeX * CompSizeY); // Ensure the size matches the landscape component
|
||
|
|
FMemory::Memset(ClearWeightData.GetData(), 255, CompSizeX * CompSizeY);
|
||
|
|
|
||
|
|
|
||
|
|
if (GaeaComponent->WeightmapFilepaths.Num() >= InfoObjects.Num() - 1)
|
||
|
|
{
|
||
|
|
LandscapeEdit.SetAlphaData(InfoObjects[0], ComponentsRect.Min.X, ComponentsRect.Min.Y, ComponentsRect.Max.X, ComponentsRect.Max.Y, ClearWeightData.GetData(), 0, ELandscapeLayerPaintingRestriction::None);
|
||
|
|
|
||
|
|
for(int32 i = 1; i < InfoObjects.Num(); i++)
|
||
|
|
{
|
||
|
|
int32 WeightmapIndex = i - 1;
|
||
|
|
if (WeightmapIndex < GaeaComponent->WeightmapFilepaths.Num())
|
||
|
|
{
|
||
|
|
FLandscapeImportHelper::GetWeightmapImportDescriptor(GaeaComponent->WeightmapFilepaths[WeightmapIndex].FilePath, true, false, InfoObjects[i]->GetLayerName(), WeightOutImportDescriptors[i], WeightOutMessage[i]);
|
||
|
|
FLandscapeImportHelper::GetWeightmapImportData(WeightOutImportDescriptors[i], DescriptorIndex, InfoObjects[i]->GetLayerName(), WeightOutData, WeightOutMessage[i]);
|
||
|
|
FLandscapeImportHelper::TransformWeightmapImportData(WeightOutData, FinalWeightOutData, OutImportDescriptor.ImportResolutions[DescriptorIndex], FLandscapeImportResolution(SizeX, SizeY), ELandscapeImportTransformType::ExpandCentered);
|
||
|
|
|
||
|
|
if(FinalWeightOutData.Num() == CompSizeX * CompSizeY)
|
||
|
|
{
|
||
|
|
LandscapeEdit.SetAlphaData(InfoObjects[i], ComponentsRect.Min.X, ComponentsRect.Min.Y, ComponentsRect.Max.X, ComponentsRect.Max.Y, FinalWeightOutData.GetData(), 0, ELandscapeLayerPaintingRestriction::None);
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
Landscape->RequestLayersContentUpdateForceAll(Update_All);
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
/// Execute SetHeightData
|
||
|
|
|
||
|
|
if (FinalHeightData.Num() == CompSizeX * CompSizeY)
|
||
|
|
{
|
||
|
|
|
||
|
|
LandscapeEdit.SetHeightData( ComponentsRect.Min.X, ComponentsRect.Min.Y, ComponentsRect.Max.X, ComponentsRect.Max.Y,
|
||
|
|
FinalHeightData.GetData(),
|
||
|
|
0,
|
||
|
|
true,
|
||
|
|
nullptr, // Normals data
|
||
|
|
nullptr, // Alpha blend data
|
||
|
|
nullptr, // Raise/lower data
|
||
|
|
false, // Do not create new components
|
||
|
|
nullptr, // No custom heightmap texture
|
||
|
|
nullptr, // No XY offsetmap texture
|
||
|
|
true, // Update bounds
|
||
|
|
true, // Update collision
|
||
|
|
true // Generate mipmaps
|
||
|
|
);
|
||
|
|
|
||
|
|
Landscape->RequestLayersContentUpdateForceAll(ELandscapeLayerUpdateMode::Update_Heightmap_All);
|
||
|
|
|
||
|
|
Landscape->SetActorScale3D(LandscapeScale);
|
||
|
|
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
if (GEngine)
|
||
|
|
{
|
||
|
|
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("Original landscape resolution does not match the reimport texture resolution"));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void UGaeaSubsystem::ReimportGaeaWPTerrain()
|
||
|
|
{
|
||
|
|
TArray<ULandscapeLayerInfoObject*> InfoObjects;
|
||
|
|
TArray<FLandscapeImportDescriptor> WeightOutImportDescriptors;
|
||
|
|
TArray<ELandscapeImportResult> WeightImportResults;
|
||
|
|
TArray<FText> WeightOutMessage;
|
||
|
|
bool ReimportWeightmaps = false;
|
||
|
|
TArray<uint8> WeightOutData;
|
||
|
|
TArray<uint8> FinalWeightOutData;
|
||
|
|
|
||
|
|
UEditorActorSubsystem* ActorSubsystem = GEditor->GetEditorSubsystem<UEditorActorSubsystem>(); // Get an instance of the Editor Actor Subsystem.
|
||
|
|
if (ActorSubsystem)
|
||
|
|
{
|
||
|
|
const TArray<AActor*>& SelectedActors = ActorSubsystem->GetSelectedLevelActors();
|
||
|
|
if (SelectedActors.Num() > 0)
|
||
|
|
{
|
||
|
|
AActor* Actor = SelectedActors[0];
|
||
|
|
ALandscape* Landscape = Cast<ALandscape>(Actor);
|
||
|
|
ULandscapeInfo* LandscapeActorInfo = Landscape->GetLandscapeInfo();
|
||
|
|
|
||
|
|
if (Landscape)
|
||
|
|
{
|
||
|
|
if (UGaeaLandscapeComponent* GaeaComponent = Actor->FindComponentByClass<UGaeaLandscapeComponent>())
|
||
|
|
{
|
||
|
|
FString JsonPath = GaeaComponent->DefinitionFilepath.FilePath;
|
||
|
|
FString HeightPath = GaeaComponent->HeightmapFilepath.FilePath;
|
||
|
|
|
||
|
|
bool bStatus = false;
|
||
|
|
FString JsonMessage = "";
|
||
|
|
const FGaeaJson GaeaDefinition = CreateStructFromJson(JsonPath, bStatus, JsonMessage);
|
||
|
|
|
||
|
|
if(bStatus)
|
||
|
|
{
|
||
|
|
FVector LandscapeLocation = FVector(0, 0, GaeaDefinition.Height * 100 / 2);
|
||
|
|
FVector LandscapeScale = FVector(
|
||
|
|
GaeaDefinition.ScaleX * 100 / GaeaDefinition.Resolution,
|
||
|
|
GaeaDefinition.ScaleY * 100 / GaeaDefinition.Resolution,
|
||
|
|
GaeaDefinition.Height * 100 / 512
|
||
|
|
);
|
||
|
|
|
||
|
|
constexpr bool bSingleFile = true;
|
||
|
|
FLandscapeImportDescriptor OutImportDescriptor;
|
||
|
|
OutImportDescriptor.Scale = LandscapeScale;
|
||
|
|
FText OutMessage;
|
||
|
|
|
||
|
|
ELandscapeImportResult ImportResult = FLandscapeImportHelper::GetHeightmapImportDescriptor(HeightPath, bSingleFile, false, OutImportDescriptor, OutMessage);
|
||
|
|
|
||
|
|
if (ImportResult == ELandscapeImportResult::Success)
|
||
|
|
{
|
||
|
|
int32 DescriptorIndex = OutImportDescriptor.FileResolutions.Num() / 2;
|
||
|
|
|
||
|
|
ULandscapeEditorObject* DefaultValueObject = ULandscapeEditorObject::StaticClass()->GetDefaultObject<ULandscapeEditorObject>();
|
||
|
|
check(DefaultValueObject);
|
||
|
|
|
||
|
|
int32 OutQuadsPerSection = DefaultValueObject->NewLandscape_QuadsPerSection;
|
||
|
|
int32 OutSectionsPerComponent = DefaultValueObject->NewLandscape_SectionsPerComponent;
|
||
|
|
FIntPoint OutComponentCount = DefaultValueObject->NewLandscape_ComponentCount;
|
||
|
|
|
||
|
|
FLandscapeImportHelper::ChooseBestComponentSizeForImport(
|
||
|
|
OutImportDescriptor.ImportResolutions[DescriptorIndex].Width,
|
||
|
|
OutImportDescriptor.ImportResolutions[DescriptorIndex].Height,
|
||
|
|
OutQuadsPerSection,
|
||
|
|
OutSectionsPerComponent,
|
||
|
|
OutComponentCount
|
||
|
|
);
|
||
|
|
|
||
|
|
TArray<uint16> ImportData;
|
||
|
|
ImportResult = FLandscapeImportHelper::GetHeightmapImportData(OutImportDescriptor, DescriptorIndex, ImportData, OutMessage);
|
||
|
|
|
||
|
|
if (ImportResult == ELandscapeImportResult::Success)
|
||
|
|
{
|
||
|
|
TArray<uint16> FinalHeightData;
|
||
|
|
|
||
|
|
// Calculate SizeX and SizeY based on the component count and the number of quads per component
|
||
|
|
const int32 QuadsPerComponent = OutSectionsPerComponent * OutQuadsPerSection;
|
||
|
|
const int32 SizeX = OutComponentCount.X * QuadsPerComponent + 1;
|
||
|
|
const int32 SizeY = OutComponentCount.Y * QuadsPerComponent + 1;
|
||
|
|
|
||
|
|
FLandscapeImportHelper::TransformHeightmapImportData(
|
||
|
|
ImportData,
|
||
|
|
FinalHeightData,
|
||
|
|
OutImportDescriptor.ImportResolutions[DescriptorIndex],
|
||
|
|
FLandscapeImportResolution(SizeX, SizeY),
|
||
|
|
ELandscapeImportTransformType::ExpandCentered
|
||
|
|
);
|
||
|
|
|
||
|
|
if (LandscapeActorInfo && !GaeaComponent->WeightmapFilepaths.IsEmpty())
|
||
|
|
{
|
||
|
|
// Loop through all layers associated with the landscape and get the relevant layer objects.
|
||
|
|
for (const FLandscapeInfoLayerSettings& LayerSettings : LandscapeActorInfo->Layers)
|
||
|
|
{
|
||
|
|
if (ULandscapeLayerInfoObject* LayerInfoObject = LayerSettings.LayerInfoObj)
|
||
|
|
{
|
||
|
|
InfoObjects.Add(LayerInfoObject);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for (int32 i = 0; i < InfoObjects.Num(); i++)
|
||
|
|
{
|
||
|
|
if (InfoObjects[i])
|
||
|
|
{
|
||
|
|
Landscape->ClearEditLayer(i,nullptr,ELandscapeToolTargetTypeFlags::Weightmap); // Have to clear all layers, or SetAlphaData will accumulate or present with visual artifacting.
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
WeightOutMessage.AddDefaulted(InfoObjects.Num());
|
||
|
|
WeightOutImportDescriptors.AddDefaulted(InfoObjects.Num());
|
||
|
|
WeightImportResults.AddDefaulted(InfoObjects.Num());
|
||
|
|
}
|
||
|
|
|
||
|
|
// Collect and process each landscape proxy
|
||
|
|
TArray<ALandscapeProxy*> AllProxies;
|
||
|
|
Landscape->GetLandscapeInfo()->ForEachLandscapeProxy([&AllProxies](ALandscapeProxy* Proxy)
|
||
|
|
{
|
||
|
|
AllProxies.Add(Proxy);
|
||
|
|
return true; // Continue iteration
|
||
|
|
});
|
||
|
|
|
||
|
|
for (ALandscapeProxy* Proxy : AllProxies)
|
||
|
|
{
|
||
|
|
FLandscapeEditDataInterface LandscapeEdit(Proxy->GetLandscapeInfo());
|
||
|
|
|
||
|
|
FIntRect ComponentsRect = Proxy->GetBoundingRect() + Proxy->GetSectionBase();
|
||
|
|
const int32 CompSizeX = ComponentsRect.Width() + 1;
|
||
|
|
const int32 CompSizeY = ComponentsRect.Height() + 1;
|
||
|
|
|
||
|
|
// Define the region in FinalHeightData corresponding to this proxy
|
||
|
|
TArray<uint16> ProxyHeightData;
|
||
|
|
ProxyHeightData.SetNum(CompSizeX * CompSizeY);
|
||
|
|
|
||
|
|
for (int32 Y = 0; Y < CompSizeY; ++Y)
|
||
|
|
{
|
||
|
|
for (int32 X = 0; X < CompSizeX; ++X)
|
||
|
|
{
|
||
|
|
int32 SrcX = ComponentsRect.Min.X + X;
|
||
|
|
int32 SrcY = ComponentsRect.Min.Y + Y;
|
||
|
|
int32 SrcIndex = SrcY * SizeX + SrcX; // SizeX is the width of the full landscape
|
||
|
|
|
||
|
|
int32 DestIndex = Y * CompSizeX + X;
|
||
|
|
if (FinalHeightData.IsValidIndex(SrcIndex) && ProxyHeightData.IsValidIndex(DestIndex))
|
||
|
|
{
|
||
|
|
ProxyHeightData[DestIndex] = FinalHeightData[SrcIndex];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ProxyHeightData.Num() == CompSizeX * CompSizeY)
|
||
|
|
{
|
||
|
|
|
||
|
|
ULandscapeEditLayerBase* Layer = Landscape->GetEditLayer(0);
|
||
|
|
LandscapeEdit.SetEditLayer(Layer->GetGuid());
|
||
|
|
|
||
|
|
LandscapeEdit.SetHeightData(
|
||
|
|
ComponentsRect.Min.X, ComponentsRect.Min.Y,
|
||
|
|
ComponentsRect.Max.X, ComponentsRect.Max.Y,
|
||
|
|
ProxyHeightData.GetData(),
|
||
|
|
0, true, nullptr, nullptr, nullptr,
|
||
|
|
false, nullptr, nullptr,
|
||
|
|
true, true, true
|
||
|
|
);
|
||
|
|
|
||
|
|
Landscape->RequestLayersContentUpdateForceAll(ELandscapeLayerUpdateMode::Update_All);
|
||
|
|
|
||
|
|
|
||
|
|
Proxy->SetActorScale3D(LandscapeScale);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
if (GEngine)
|
||
|
|
{
|
||
|
|
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("Resolution mismatch for proxy."));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
if(InfoObjects.Num() > 0)
|
||
|
|
{
|
||
|
|
TArray<uint8> FirstLayerData;
|
||
|
|
FirstLayerData.SetNum(CompSizeX * CompSizeY);
|
||
|
|
FMemory::Memset(FirstLayerData.GetData(), 255, CompSizeX * CompSizeY);
|
||
|
|
LandscapeEdit.SetAlphaData(InfoObjects[0],ComponentsRect.Min.X, ComponentsRect.Min.Y,ComponentsRect.Max.X, ComponentsRect.Max.Y,FirstLayerData.GetData(),0);
|
||
|
|
|
||
|
|
for (int32 i = 1; i < InfoObjects.Num(); i++) // Start from 1 to skip first layer
|
||
|
|
{
|
||
|
|
int32 WeightmapIndex = i - 1;
|
||
|
|
|
||
|
|
if (WeightmapIndex < GaeaComponent->WeightmapFilepaths.Num())
|
||
|
|
{
|
||
|
|
FLandscapeImportHelper::GetWeightmapImportDescriptor(GaeaComponent->WeightmapFilepaths[WeightmapIndex].FilePath, true, false, InfoObjects[i]->GetLayerName(), WeightOutImportDescriptors[i], WeightOutMessage[i]);
|
||
|
|
FLandscapeImportHelper::GetWeightmapImportData(WeightOutImportDescriptors[i], DescriptorIndex, InfoObjects[i]->GetLayerName(), WeightOutData, WeightOutMessage[i]);
|
||
|
|
FLandscapeImportHelper::TransformWeightmapImportData(WeightOutData, FinalWeightOutData, OutImportDescriptor.ImportResolutions[DescriptorIndex], FLandscapeImportResolution(SizeX, SizeY), ELandscapeImportTransformType::ExpandCentered);
|
||
|
|
|
||
|
|
// Apply weightmap data for the current proxy
|
||
|
|
TArray<uint8> ProxyWeightData;
|
||
|
|
ProxyWeightData.SetNum(CompSizeX * CompSizeY);
|
||
|
|
|
||
|
|
for (int32 Y = 0; Y < CompSizeY; ++Y)
|
||
|
|
{
|
||
|
|
for (int32 X = 0; X < CompSizeX; ++X)
|
||
|
|
{
|
||
|
|
int32 SrcX = ComponentsRect.Min.X + X;
|
||
|
|
int32 SrcY = ComponentsRect.Min.Y + Y;
|
||
|
|
int32 SrcIndex = SrcY * SizeX + SrcX; // SizeX is the width of the full landscape
|
||
|
|
|
||
|
|
int32 DestIndex = Y * CompSizeX + X;
|
||
|
|
if (FinalWeightOutData.IsValidIndex(SrcIndex) && ProxyWeightData.IsValidIndex(DestIndex))
|
||
|
|
{
|
||
|
|
ProxyWeightData[DestIndex] = FinalWeightOutData[SrcIndex];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ProxyWeightData.Num() == CompSizeX * CompSizeY)
|
||
|
|
{
|
||
|
|
|
||
|
|
ULandscapeEditLayerBase* Layer = Landscape->GetEditLayer(0);
|
||
|
|
LandscapeEdit.SetEditLayer(Layer->GetGuid());
|
||
|
|
|
||
|
|
LandscapeEdit.SetAlphaData(
|
||
|
|
InfoObjects[i],
|
||
|
|
ComponentsRect.Min.X, ComponentsRect.Min.Y,
|
||
|
|
ComponentsRect.Max.X, ComponentsRect.Max.Y,
|
||
|
|
ProxyWeightData.GetData(),
|
||
|
|
0, ELandscapeLayerPaintingRestriction::None);
|
||
|
|
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
if (GEngine)
|
||
|
|
{
|
||
|
|
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("Original landscape resolution does not match the reimport texture resolution"));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
UE_LOG(GaeaSubsystem, Error, TEXT("InfoObject count is 0."));
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
void UGaeaSubsystem::ImportHeightmap(FString& Heightmap, FString& JSON, FVector& Scale, FVector& Location, TArray<FString>& Weightmaps, FString& CachedPath)
|
||
|
|
{
|
||
|
|
check(ImporterWindowValidator!= nullptr);
|
||
|
|
JSON.Empty(); // Empty out json path from ImporterPanelSettings
|
||
|
|
Heightmap.Empty(); // Empty out heightmap path from ImporterPanelSettings
|
||
|
|
|
||
|
|
const TSharedPtr<FGenericWindow> LocalNativeWindow = ImporterWindowValidator.Pin()->GetNativeWindow();
|
||
|
|
check(LocalNativeWindow!= nullptr);
|
||
|
|
const void* ParentWindow = LocalNativeWindow->GetOSWindowHandle();
|
||
|
|
|
||
|
|
const FString DialogTitle = TEXT("Import Heightmap"); // Create dialog window title.
|
||
|
|
const FString DefaultPath = DefaultDialogPath.IsEmpty() ? FPaths::ProjectDir() : DefaultDialogPath; // Set DefaultPath to DefaultDialogPath if it isn't empty, otherwise use ProjectDir().
|
||
|
|
const FString DefaultFile = TEXT("");
|
||
|
|
FString OutPath = TEXT("");
|
||
|
|
const FString FileFilter = TEXT("Heightmap files (*.r16, *.raw, *.png)|*.r16;*.raw;*.png|"); // Heightmap type file filter.
|
||
|
|
|
||
|
|
TArray<FString> SelectedFilePath;
|
||
|
|
|
||
|
|
if (FDesktopPlatformModule::Get()->OpenFileDialog(ParentWindow, DialogTitle, DefaultPath, DefaultFile, FileFilter, EFileDialogFlags::None, SelectedFilePath)) {
|
||
|
|
|
||
|
|
Heightmap = SelectedFilePath[0]; // Set path to heightmap.
|
||
|
|
|
||
|
|
OutPath = FPaths::GetPath(Heightmap); // Remove asset name & extension to create our folder directory.
|
||
|
|
|
||
|
|
CachedPath = OutPath; // Store path onto details panel uobject for secondary array generation when the frontend array is reordered.
|
||
|
|
|
||
|
|
DefaultDialogPath = OutPath; // Set DefaultDialogPath to OutPath so that next time the user clicks the import button, we open to a previously known directory.
|
||
|
|
|
||
|
|
IFileManager& FileManager = IFileManager::Get(); // Get instance of File Manager.
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
TArray<FString> WeightFilesInDirectory;
|
||
|
|
|
||
|
|
FileManager.FindFiles(WeightFilesInDirectory, *(OutPath / TEXT("*.png*")), true, false); // Get all pngs from directory.
|
||
|
|
|
||
|
|
for(int i = 0; i < WeightFilesInDirectory.Num(); i++)
|
||
|
|
{
|
||
|
|
if (WeightFilesInDirectory[i].Contains(TEXT("W_"), ESearchCase::IgnoreCase))
|
||
|
|
{
|
||
|
|
Weightmaps.Add(WeightFilesInDirectory[i]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
TArray<FString> FilesInDirectory; // Create array of strings.
|
||
|
|
|
||
|
|
FileManager.FindFiles(FilesInDirectory, *(OutPath / TEXT("*.json*")), true, false); // Get all instances of json files from directory.
|
||
|
|
|
||
|
|
if (FilesInDirectory.IsEmpty()) // Early exit if json isn't present.
|
||
|
|
{
|
||
|
|
JSON = TEXT("None");
|
||
|
|
UE_LOG(GaeaSubsystem, Log, TEXT("No json files found."));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
bool bDefinitionFound = false; // Flag to indicate whether a definition file is found
|
||
|
|
|
||
|
|
for(int i = 0; i < FilesInDirectory.Num(); i++)
|
||
|
|
{
|
||
|
|
if (FilesInDirectory[i].Contains(TEXT("definition"), ESearchCase::IgnoreCase))
|
||
|
|
{
|
||
|
|
JSON = FPaths::Combine(*OutPath, *FilesInDirectory[i]); // Write json filepath
|
||
|
|
bDefinitionFound = true; // Set the flag to true
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!bDefinitionFound)
|
||
|
|
{
|
||
|
|
// If the flag is still false after the loop, no definition file was found
|
||
|
|
UE_LOG(GaeaSubsystem, Warning, TEXT("No Definition.json found."));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// No file was selected; the file dialog was closed without selecting a file.
|
||
|
|
UE_LOG(GaeaSubsystem, Log, TEXT("Dialog was closed. No files selected"));
|
||
|
|
return;
|
||
|
|
|
||
|
|
}
|
||
|
|
bool bStatus = false;
|
||
|
|
FString JsonMessage = "";
|
||
|
|
const FGaeaJson GaeaDefinition = CreateStructFromJson(JSON, bStatus, JsonMessage);
|
||
|
|
|
||
|
|
if(bStatus)
|
||
|
|
{
|
||
|
|
UE_LOG(GaeaSubsystem, Display, TEXT("ScaleX: %f, ScaleY: %f, Height: %f, Resolution: %d"),
|
||
|
|
GaeaDefinition.ScaleX, GaeaDefinition.ScaleY, GaeaDefinition.Height, GaeaDefinition.Resolution);
|
||
|
|
Location = FVector(0,0,GaeaDefinition.Height*100/2);
|
||
|
|
Scale = FVector(GaeaDefinition.ScaleX * 100 / GaeaDefinition.Resolution,GaeaDefinition.ScaleY * 100 / GaeaDefinition.Resolution,GaeaDefinition.Height * 100 / 512); // Apply scaling formula to our passed in scale variable from the ImporterPanelSettings
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
FString UGaeaSubsystem::ReadStringFromFile(FString Path, bool& bOutSuccess, FString& OutMessage)
|
||
|
|
{
|
||
|
|
if (!FPlatformFileManager::Get().GetPlatformFile().FileExists(*Path))
|
||
|
|
{
|
||
|
|
bOutSuccess = false;
|
||
|
|
OutMessage = FString::Printf(TEXT("Failed to read file - File does not exist - '%s'"), *Path);\
|
||
|
|
|
||
|
|
UE_LOG(GaeaSubsystem, Error, TEXT("Failed to read file - File does not exist."));
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
|
||
|
|
FString OutString = "";
|
||
|
|
|
||
|
|
if(!FFileHelper::LoadFileToString(OutString, *Path))
|
||
|
|
{
|
||
|
|
bOutSuccess = false;
|
||
|
|
OutMessage = FString::Printf(TEXT("Failed to read file - Is this a text file? - '%s'"), *Path);
|
||
|
|
UE_LOG(GaeaSubsystem, Error, TEXT("Failed to read file - Is this a text file?"));
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
|
||
|
|
bOutSuccess = true;
|
||
|
|
OutMessage = FString::Printf(TEXT("File read successfully - '%s'"), *Path);
|
||
|
|
//UE_LOG(LogTemp, Log, TEXT("File read successfully."));
|
||
|
|
return OutString;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
TSharedPtr<FJsonObject> UGaeaSubsystem::ReadJson(FString Path, bool& bOutSuccess, FString& OutMessage)
|
||
|
|
{
|
||
|
|
const FString JSONString = ReadStringFromFile(Path, bOutSuccess, OutMessage);
|
||
|
|
if (!bOutSuccess)
|
||
|
|
{
|
||
|
|
return nullptr;
|
||
|
|
}
|
||
|
|
|
||
|
|
TSharedPtr<FJsonObject> RetJsonObject;
|
||
|
|
|
||
|
|
if(!FJsonSerializer::Deserialize(TJsonReaderFactory<>::Create(JSONString), RetJsonObject))
|
||
|
|
{
|
||
|
|
bOutSuccess = false;
|
||
|
|
OutMessage = FString::Printf(TEXT("Json read failed - '%s'"), *Path);
|
||
|
|
UE_LOG(GaeaSubsystem, Error, TEXT("Json read failed."));
|
||
|
|
return nullptr;
|
||
|
|
}
|
||
|
|
|
||
|
|
bOutSuccess = true;
|
||
|
|
OutMessage = FString::Printf(TEXT("Successfully read Json - '%s'"), *Path);
|
||
|
|
UE_LOG(GaeaSubsystem, Log, TEXT("Successfully read Json."));
|
||
|
|
return RetJsonObject;
|
||
|
|
}
|
||
|
|
|
||
|
|
FGaeaJson UGaeaSubsystem::CreateStructFromJson(FString Path, bool& bOutSuccess, FString& OutMessage)
|
||
|
|
{
|
||
|
|
const TSharedPtr<FJsonObject> JsonObject = ReadJson(Path, bOutSuccess, OutMessage);
|
||
|
|
if(!bOutSuccess)
|
||
|
|
{
|
||
|
|
return FGaeaJson();
|
||
|
|
}
|
||
|
|
|
||
|
|
FGaeaJson RetGaeaJson;
|
||
|
|
|
||
|
|
if(!FJsonObjectConverter::JsonObjectToUStruct<FGaeaJson>(JsonObject.ToSharedRef(), &RetGaeaJson))
|
||
|
|
{
|
||
|
|
bOutSuccess = false;
|
||
|
|
OutMessage = FString::Printf(TEXT("Json conversion failed - '%s'"), *Path);
|
||
|
|
UE_LOG(GaeaSubsystem, Error, TEXT("Json conversion failed."));
|
||
|
|
return FGaeaJson();
|
||
|
|
}
|
||
|
|
|
||
|
|
bOutSuccess = true;
|
||
|
|
OutMessage = FString::Printf(TEXT("Json conversion succeeded - '%s'"), *Path);
|
||
|
|
//UE_LOG(LogTemp, Log, TEXT("Json conversion succeeded."));
|
||
|
|
return RetGaeaJson;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
ALandscape* UGaeaSubsystem::GetLandscape(ULandscapeInfo* LandscapeInfo) const
|
||
|
|
{
|
||
|
|
ALandscape* LandscapeActor = LandscapeInfo->LandscapeActor.Get();
|
||
|
|
if (LandscapeActor != nullptr)
|
||
|
|
{
|
||
|
|
return LandscapeActor;
|
||
|
|
}
|
||
|
|
return nullptr;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* FGuid UGaeaSubsystem::GetLayerGuidFromIndex(int32 Index, ULandscapeInfo* LandscapeInfo) const
|
||
|
|
{
|
||
|
|
ALandscape* Landscape = GetLandscape(LandscapeInfo);
|
||
|
|
FLandscapeLayer* Layer = Landscape ? Landscape->GetLayer(Index) : nullptr;
|
||
|
|
return Layer ? Layer->Guid : FGuid();
|
||
|
|
}*/
|
||
|
|
|
||
|
|
void UGaeaSubsystem::CreateLandscapeActor(UImporterPanelSettings* Settings)
|
||
|
|
{
|
||
|
|
constexpr bool bSingleFile = true;
|
||
|
|
FLandscapeImportDescriptor OutImportDescriptor;
|
||
|
|
TArray<FLandscapeImportDescriptor> WeightOutImportDescriptors;
|
||
|
|
TArray<ELandscapeImportResult> WeightImportResults;
|
||
|
|
TArray<FLandscapeImportLayerInfo> MaterialImportLayers;
|
||
|
|
TArray<ULandscapeLayerInfoObject*> LayerInfoObjects;
|
||
|
|
OutImportDescriptor.Scale = Settings->Scale;
|
||
|
|
FText OutMessage;
|
||
|
|
TArray<FText> WeightOutMessage;
|
||
|
|
bool ImportWeightmaps = false;
|
||
|
|
|
||
|
|
UWorld* World = nullptr;
|
||
|
|
{
|
||
|
|
FWorldContext& EditorWorldContext = GEditor->GetEditorWorldContext();
|
||
|
|
World = EditorWorldContext.World(); // We want to create the landscape in the editor world.
|
||
|
|
}
|
||
|
|
|
||
|
|
ULandscapeSubsystem* LandscapeSubsystem = World->GetSubsystem<ULandscapeSubsystem>();
|
||
|
|
bool bIsGridBased = LandscapeSubsystem->IsGridBased();
|
||
|
|
|
||
|
|
|
||
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
/// Heightmap Import/Data Creation
|
||
|
|
|
||
|
|
ELandscapeImportResult ImportResult = FLandscapeImportHelper::GetHeightmapImportDescriptor(Settings->HeightMapFileName, bSingleFile, Settings->FlipYAxis, OutImportDescriptor, OutMessage);
|
||
|
|
int32 DescriptorIndex = OutImportDescriptor.FileResolutions.Num() / 2;
|
||
|
|
|
||
|
|
ULandscapeEditorObject* DefaultValueObject = ULandscapeEditorObject::StaticClass()->GetDefaultObject<ULandscapeEditorObject>(); // Create Landscape Editor Object instance
|
||
|
|
check(DefaultValueObject);
|
||
|
|
|
||
|
|
int32 OutQuadsPerSection = DefaultValueObject->NewLandscape_QuadsPerSection;
|
||
|
|
int32 OutSectionsPerComponent = DefaultValueObject->NewLandscape_SectionsPerComponent;
|
||
|
|
FIntPoint OutComponentCount = DefaultValueObject->NewLandscape_ComponentCount;
|
||
|
|
|
||
|
|
// We automatically adjust/fit the data so users avoid having to guess.
|
||
|
|
FLandscapeImportHelper::ChooseBestComponentSizeForImport(OutImportDescriptor.ImportResolutions[DescriptorIndex].Width, OutImportDescriptor.ImportResolutions[DescriptorIndex].Height, OutQuadsPerSection, OutSectionsPerComponent, OutComponentCount);
|
||
|
|
|
||
|
|
TArray<uint16> ImportData;
|
||
|
|
ImportResult = FLandscapeImportHelper::GetHeightmapImportData(OutImportDescriptor, DescriptorIndex, ImportData, OutMessage);
|
||
|
|
|
||
|
|
|
||
|
|
const int32 QuadsPerComponent = OutSectionsPerComponent * OutQuadsPerSection;
|
||
|
|
const int32 SizeX = OutComponentCount.X * QuadsPerComponent + 1;
|
||
|
|
const int32 SizeY = OutComponentCount.Y * QuadsPerComponent + 1;
|
||
|
|
|
||
|
|
TArray<uint16> FinalHeightData;
|
||
|
|
FLandscapeImportHelper::TransformHeightmapImportData(ImportData, FinalHeightData, OutImportDescriptor.ImportResolutions[DescriptorIndex], FLandscapeImportResolution(SizeX, SizeY), ELandscapeImportTransformType::ExpandCentered);
|
||
|
|
|
||
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
/// Weightmap Import/Data Creation
|
||
|
|
|
||
|
|
FString PackagePath = Settings->LayerInfoFolder.Path;
|
||
|
|
FString Name;
|
||
|
|
bool PathExists = false;
|
||
|
|
|
||
|
|
if(!PackagePath.IsEmpty())
|
||
|
|
{
|
||
|
|
//Check if path exists. User may have altered it after picking via the widget.
|
||
|
|
PathExists = UEditorAssetLibrary::DoesDirectoryExist(PackagePath);
|
||
|
|
}
|
||
|
|
|
||
|
|
UE_LOG(GaeaSubsystem, Display, TEXT("LandscapeMaterialLayerNames Count: %d"), Settings->LandscapeMaterialLayerNames.Num());
|
||
|
|
UE_LOG(GaeaSubsystem, Display, TEXT("LayerInfoFolder Path: %s"), *Settings->LayerInfoFolder.Path);
|
||
|
|
|
||
|
|
if (!Settings->LandscapeMaterialLayerNames.IsEmpty() && PathExists && Settings->LandscapeMaterialLayerNames.Num() >= 2 && Settings->WeightmapFileNames.Num() == (Settings->LandscapeMaterialLayerNames.Num() - 1))
|
||
|
|
{
|
||
|
|
//Checks to see if we have weightmaps to import. If there are 2 or less, we skip this.
|
||
|
|
ImportWeightmaps = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
if(ImportWeightmaps)
|
||
|
|
{
|
||
|
|
// Load the AssetToolsModule. This is needed to create layer info objects. However, we may add the option to choose existing info objects instead of always creating new ones as it is not technically needed to have unique info objects.
|
||
|
|
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
|
||
|
|
|
||
|
|
//Create LandscapeLayerInfoObjects for use with weightmaps.
|
||
|
|
for(int i = 0; i < Settings->LandscapeMaterialLayerNames.Num(); i++)
|
||
|
|
{
|
||
|
|
Name = Settings->LandscapeMaterialLayerNames[i].ToString();
|
||
|
|
Name = Name.Replace(TEXT(" "), TEXT("")); // Remove whitespaces from Name
|
||
|
|
FString NewAssetName;
|
||
|
|
FString DummyPackageName;
|
||
|
|
AssetToolsModule.Get().CreateUniqueAssetName(PackagePath / Name, TEXT(""), DummyPackageName, NewAssetName);
|
||
|
|
UObject* CreatedAsset = AssetToolsModule.Get().CreateAsset(NewAssetName, PackagePath, ULandscapeLayerInfoObject::StaticClass(), nullptr);
|
||
|
|
|
||
|
|
ULandscapeLayerInfoObject* LayerInfoObj = Cast<ULandscapeLayerInfoObject>(CreatedAsset);
|
||
|
|
|
||
|
|
if(LayerInfoObj)
|
||
|
|
{
|
||
|
|
LayerInfoObj->SetLayerName(Settings->LandscapeMaterialLayerNames[i], true);
|
||
|
|
LayerInfoObj->SetBlendMethod(ELandscapeTargetLayerBlendMethod::PremultipliedAlphaBlending, true);
|
||
|
|
}
|
||
|
|
LayerInfoObjects.Add(LayerInfoObj);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
//Set layer names
|
||
|
|
for(int i = 0; i < Settings->LandscapeMaterialLayerNames.Num(); i++)
|
||
|
|
{
|
||
|
|
FLandscapeImportLayerInfo LayerInfo;
|
||
|
|
LayerInfo.LayerName = Settings->LandscapeMaterialLayerNames[i];
|
||
|
|
MaterialImportLayers.Add(LayerInfo);
|
||
|
|
MaterialImportLayers[i].LayerName = LayerInfo.LayerName;
|
||
|
|
}
|
||
|
|
|
||
|
|
//Fill first Layer with data. Layer 1 should never have areas with no data, as that results in black spots in the final material render.
|
||
|
|
MaterialImportLayers[0].LayerData = TArray<uint8>();
|
||
|
|
MaterialImportLayers[0].SourceFilePath = ""; // Base layer doesn't contain a file
|
||
|
|
MaterialImportLayers[0].LayerInfo = LayerInfoObjects[0];
|
||
|
|
const int32 DataSize = SizeX * SizeY;
|
||
|
|
MaterialImportLayers[0].LayerData.AddUninitialized(DataSize);
|
||
|
|
uint8* ByteData = MaterialImportLayers[0].LayerData.GetData();
|
||
|
|
FMemory::Memset(ByteData, 255, DataSize);
|
||
|
|
|
||
|
|
|
||
|
|
WeightOutImportDescriptors.AddDefaulted(Settings->LandscapeMaterialLayerNames.Num());
|
||
|
|
WeightOutMessage.AddDefaulted(Settings->LandscapeMaterialLayerNames.Num());
|
||
|
|
WeightImportResults.AddDefaulted(Settings->LandscapeMaterialLayerNames.Num());
|
||
|
|
|
||
|
|
if(Settings->WeightmapFilePaths.IsEmpty()) // If the user hasn't touched the File Names order, we create the full file paths now.
|
||
|
|
{
|
||
|
|
for(int i = 0; i < Settings->WeightmapFileNames.Num(); i++)
|
||
|
|
{
|
||
|
|
FString FullPath = FPaths::Combine(*Settings->StoredPath, *Settings->WeightmapFileNames[i]);
|
||
|
|
UE_LOG(GaeaSubsystem, Display, TEXT("Weightmap Full Path: %s"), *FullPath);
|
||
|
|
Settings->WeightmapFilePaths.Add(FullPath);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// The first landscape layer is already filled, so the user should always be using 1 weightmap file less than the total amount of landscape layers.
|
||
|
|
for(int32 i = 1; i < Settings->LandscapeMaterialLayerNames.Num(); i++)
|
||
|
|
{
|
||
|
|
int32 WeightmapIndex = i - 1;
|
||
|
|
if (WeightmapIndex < Settings->WeightmapFilePaths.Num())
|
||
|
|
{
|
||
|
|
WeightImportResults[i] = FLandscapeImportHelper::GetWeightmapImportDescriptor(Settings->WeightmapFilePaths[WeightmapIndex], bSingleFile, Settings->FlipYAxis, Settings->LandscapeMaterialLayerNames[i], WeightOutImportDescriptors[i],WeightOutMessage[i]);
|
||
|
|
|
||
|
|
TArray<uint8> WeightOutData;
|
||
|
|
FLandscapeImportHelper::GetWeightmapImportData(WeightOutImportDescriptors[i], DescriptorIndex, Settings->LandscapeMaterialLayerNames[i], WeightOutData, WeightOutMessage[i]);
|
||
|
|
|
||
|
|
TArray<uint8> FinalWeightOutData;
|
||
|
|
FLandscapeImportHelper::TransformWeightmapImportData(WeightOutData, FinalWeightOutData, OutImportDescriptor.ImportResolutions[DescriptorIndex], FLandscapeImportResolution(SizeX, SizeY), ELandscapeImportTransformType::ExpandCentered);
|
||
|
|
|
||
|
|
MaterialImportLayers[i].LayerName = Settings->LandscapeMaterialLayerNames[i];
|
||
|
|
MaterialImportLayers[i].LayerInfo = LayerInfoObjects[i];
|
||
|
|
MaterialImportLayers[i].LayerData = MoveTemp(FinalWeightOutData);
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
UE_LOG(GaeaSubsystem, Display, TEXT("MaterialImportLayers Length: %d"), MaterialImportLayers.Num());
|
||
|
|
}
|
||
|
|
|
||
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
/// Misc Settings for Landscape Actor Creation
|
||
|
|
|
||
|
|
const FVector Offset = FTransform(Settings->Rotation,FVector::ZeroVector, Settings->Scale).TransformVector(FVector(-OutComponentCount.X * QuadsPerComponent / 2., -OutComponentCount.Y * QuadsPerComponent / 2., 0.));
|
||
|
|
|
||
|
|
ALandscape* Landscape = World->SpawnActor<ALandscape>(Settings->Location + Offset, Settings->Rotation);
|
||
|
|
Landscape->LandscapeMaterial = Settings->LandscapeMaterial;
|
||
|
|
Landscape->SetActorRelativeScale3D(Settings->Scale);
|
||
|
|
|
||
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
// Create Gaea Landscape Component
|
||
|
|
|
||
|
|
// This component stores filenames on the actor for any potential reimport. More features will eventually be added to this component.
|
||
|
|
UGaeaLandscapeComponent* GaeaComponent = NewObject<UGaeaLandscapeComponent>(Landscape, UGaeaLandscapeComponent::StaticClass(), TEXT("Gaea Landscape Component"));
|
||
|
|
|
||
|
|
if(GaeaComponent)
|
||
|
|
{
|
||
|
|
GaeaComponent->RegisterComponent();
|
||
|
|
Landscape->AddInstanceComponent(GaeaComponent); // Must use this so that the component appears in the landscape actor details panel.
|
||
|
|
Landscape->AddOwnedComponent(GaeaComponent); // Landscape actor will manage the lifetime of the component.
|
||
|
|
|
||
|
|
// Set component height/json paths from our Settings object. We'll use these stored paths for the reimport feature.
|
||
|
|
GaeaComponent->HeightmapFilepath.FilePath = Settings->HeightMapFileName;
|
||
|
|
GaeaComponent->DefinitionFilepath.FilePath = Settings->jsonFileName;
|
||
|
|
|
||
|
|
// Set the weightmap file paths to the file path array on the component
|
||
|
|
if (!Settings->WeightmapFilePaths.IsEmpty())
|
||
|
|
{
|
||
|
|
GaeaComponent->WeightmapFilepaths.SetNum(Settings->WeightmapFilePaths.Num());
|
||
|
|
|
||
|
|
for (int32 i = 0; i < Settings->WeightmapFilePaths.Num(); i++)
|
||
|
|
{
|
||
|
|
GaeaComponent->WeightmapFilepaths[i].FilePath = Settings->WeightmapFilePaths[i];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
/// Call Import Function With Height & Weight Data On Landscape Actor
|
||
|
|
|
||
|
|
TMap<FGuid, TArray<uint16>> HeightmapDataPerLayers;
|
||
|
|
HeightmapDataPerLayers.Add(FGuid(), MoveTemp(FinalHeightData));
|
||
|
|
TMap<FGuid, TArray<FLandscapeImportLayerInfo>> MaterialLayerDataPerLayers;
|
||
|
|
|
||
|
|
if(Settings->LandscapeMaterialLayerNames.IsEmpty() || !PathExists || Settings->LandscapeMaterialLayerNames.Num() < 2)
|
||
|
|
{
|
||
|
|
MaterialLayerDataPerLayers.Add(FGuid(), TArray<FLandscapeImportLayerInfo>());
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
MaterialLayerDataPerLayers.Add(FGuid(), MaterialImportLayers);
|
||
|
|
}
|
||
|
|
|
||
|
|
// This is the black box function the engine uses to inject all of our height/weight data into our landscape actor.
|
||
|
|
Landscape->Import(FGuid::NewGuid(), 0, 0, SizeX - 1, SizeY - 1, OutSectionsPerComponent, OutQuadsPerSection, HeightmapDataPerLayers, *Settings->HeightMapFileName, MaterialLayerDataPerLayers, ELandscapeImportAlphamapType::Additive, TArrayView<const FLandscapeLayer>());
|
||
|
|
|
||
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
/// Calculate Lighting LOD and Update Final Settings
|
||
|
|
|
||
|
|
// automatically calculate a lighting LOD that won't crash lightmass (hopefully)
|
||
|
|
// < 2048x2048 -> LOD0
|
||
|
|
// >=2048x2048 -> LOD1
|
||
|
|
// >= 4096x4096 -> LOD2
|
||
|
|
// >= 8192x8192 -> LOD3
|
||
|
|
Landscape->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((SizeX * SizeY) / (2048 * 2048) + 1), (uint32)2);
|
||
|
|
|
||
|
|
ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo();
|
||
|
|
check(LandscapeInfo);
|
||
|
|
LandscapeInfo->UpdateLayerInfoMap(Landscape);
|
||
|
|
|
||
|
|
Landscape->RegisterAllComponents();
|
||
|
|
|
||
|
|
for(int32 i = 0; i < Settings->LandscapeMaterialLayerNames.Num(); i++)
|
||
|
|
{
|
||
|
|
if(MaterialImportLayers[i].LayerInfo != nullptr)
|
||
|
|
{
|
||
|
|
// New method introduced in 5.5 to create Target Layers. Without this, weightmap import will not work.
|
||
|
|
Landscape->AddTargetLayer(MaterialImportLayers[i].LayerName, FLandscapeTargetLayerSettings(MaterialImportLayers[i].LayerInfo, MaterialImportLayers[i].SourceFilePath));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
/// World Partition Setup
|
||
|
|
|
||
|
|
if (bIsGridBased && Settings->bIsWorldPartition) // This can be considered a partial WP solution, as a significant portion of the needed code is hidden behind the landscape editor mode and private files. This results in slower creation of landscape proxies and no region volume support.
|
||
|
|
{
|
||
|
|
//Fake scoped task. ChangeGridSize doesn't provide any out data to hook into, so this is really only so the user doesn't think their editor is frozen.
|
||
|
|
FScopedSlowTask SlowTask(100.f, FText::FromString(TEXT("Creating landscape chunks, importing bits and bytes. Might be here for awhile.")));
|
||
|
|
SlowTask.MakeDialog(false);
|
||
|
|
SlowTask.EnterProgressFrame(10.f);
|
||
|
|
LandscapeSubsystem->ChangeGridSize(LandscapeInfo, Settings->WorldPartitionGridSize); // WP function call
|
||
|
|
SlowTask.EnterProgressFrame(90.f);
|
||
|
|
}
|
||
|
|
|
||
|
|
ImporterWindowValidator.Pin()->RequestDestroyWindow();
|
||
|
|
}
|
||
|
|
|
||
|
|
TArray<UMaterialExpressionLandscapeLayerBlend*> UGaeaSubsystem::GetLandscapeLayerBlendNodes(UMaterialInterface* MaterialInterface)
|
||
|
|
{
|
||
|
|
TArray<const UMaterialExpressionLandscapeLayerBlend*> ConstAllExpressions;
|
||
|
|
TArray<UMaterialExpressionLandscapeLayerBlend*> OutExpressions;
|
||
|
|
|
||
|
|
if (MaterialInterface && MaterialInterface->GetBaseMaterial())
|
||
|
|
{
|
||
|
|
MaterialInterface->GetBaseMaterial()->GetAllExpressionsOfType<UMaterialExpressionLandscapeLayerBlend>(ConstAllExpressions);
|
||
|
|
|
||
|
|
for (const UMaterialExpressionLandscapeLayerBlend* Expression : ConstAllExpressions)
|
||
|
|
{
|
||
|
|
OutExpressions.Add(const_cast<UMaterialExpressionLandscapeLayerBlend*>(Expression));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return OutExpressions;
|
||
|
|
}
|
||
|
|
|
||
|
|
TArray<FName> UGaeaSubsystem::GetLandscapeLayerBlendNames(TArray<UMaterialExpressionLandscapeLayerBlend*> LayerBlends, TArray<FName>& Names)
|
||
|
|
{
|
||
|
|
|
||
|
|
TArray<FName> OutNames;
|
||
|
|
|
||
|
|
for (UMaterialExpressionLandscapeLayerBlend* LayerBlend : LayerBlends)
|
||
|
|
{
|
||
|
|
if (LayerBlend)
|
||
|
|
{
|
||
|
|
for (const FLayerBlendInput& Layer : LayerBlend->Layers)
|
||
|
|
{
|
||
|
|
OutNames.AddUnique(Layer.LayerName);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Names = OutNames;
|
||
|
|
return OutNames;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
void UGaeaSubsystem::GetLandscapeActorProxies(ALandscape* Landscape,TArray<ALandscapeProxy*>& LandscapeStreamingProxies)
|
||
|
|
{
|
||
|
|
if (Landscape)
|
||
|
|
{
|
||
|
|
Landscape->GetLandscapeInfo()->ForEachLandscapeProxy([&LandscapeStreamingProxies](ALandscapeProxy* Proxy)
|
||
|
|
{
|
||
|
|
LandscapeStreamingProxies.Add(Proxy);
|
||
|
|
return true;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
#undef LOCTEXT_NAMESPACE
|