518 lines
16 KiB
C++
518 lines
16 KiB
C++
|
// Copyright 2020 Phyronnaz
|
||
|
|
||
|
#include "VoxelWorldDetails.h"
|
||
|
#include "VoxelWorld.h"
|
||
|
#include "VoxelStaticWorld.h"
|
||
|
#include "VoxelData/VoxelData.h"
|
||
|
#include "VoxelRender/VoxelProceduralMeshComponent.h"
|
||
|
#include "VoxelRender/VoxelProcMeshBuffers.h"
|
||
|
#include "VoxelRender/VoxelMaterialInterface.h"
|
||
|
#include "VoxelTools/VoxelBlueprintLibrary.h"
|
||
|
#include "VoxelTools/VoxelDataTools.inl"
|
||
|
#include "VoxelEditorDetailsUtilities.h"
|
||
|
#include "VoxelMessages.h"
|
||
|
#include "VoxelFeedbackContext.h"
|
||
|
#include "VoxelScopedTransaction.h"
|
||
|
|
||
|
#include "Modules/ModuleManager.h"
|
||
|
|
||
|
#include "Materials/MaterialInstanceDynamic.h"
|
||
|
#include "Framework/Application/SlateApplication.h"
|
||
|
#include "DesktopPlatformModule.h"
|
||
|
#include "Misc/FileHelper.h"
|
||
|
#include "Misc/MessageDialog.h"
|
||
|
#include "DetailLayoutBuilder.h"
|
||
|
#include "DetailCategoryBuilder.h"
|
||
|
#include "AssetRegistryModule.h"
|
||
|
#include "Editor.h"
|
||
|
#include "RawMesh.h"
|
||
|
#include "PhysicsEngine/BodySetup.h"
|
||
|
|
||
|
// The sort order is being silly, so force set it
|
||
|
#include "Editor/PropertyEditor/Private/DetailCategoryBuilderImpl.h"
|
||
|
|
||
|
inline FString GetVoxelWorldSaveFilePath(AVoxelWorld& World, bool bIsLoad)
|
||
|
{
|
||
|
if ((bIsLoad && FPaths::FileExists(World.SaveFilePath)) || (!bIsLoad && !World.SaveFilePath.IsEmpty()))
|
||
|
{
|
||
|
return World.SaveFilePath;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
TArray<FString> OutFiles;
|
||
|
if (FDesktopPlatformModule::Get()->OpenFileDialog(
|
||
|
FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr),
|
||
|
TEXT("Choose File"),
|
||
|
FPaths::ProjectSavedDir(),
|
||
|
"",
|
||
|
TEXT("Voxel Save (*.voxelsave)|*.voxelsave"),
|
||
|
EFileDialogFlags::None,
|
||
|
OutFiles))
|
||
|
{
|
||
|
ensure(OutFiles.Num() == 1);
|
||
|
return OutFiles[0];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return "";
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void FVoxelWorldDetails::CustomizeDetails(IDetailLayoutBuilder& DetailLayout)
|
||
|
{
|
||
|
FVoxelEditorUtilities::EnableRealtime();
|
||
|
TArray<TWeakObjectPtr<UObject>> Objects;
|
||
|
DetailLayout.GetObjectsBeingCustomized(Objects);
|
||
|
// Disabled as it makes BP compilation crash when calling PostEditChange
|
||
|
//for (auto& Object : Objects)
|
||
|
//{
|
||
|
// World->UpdateCollisionProfile();
|
||
|
// World->PostEditChange();
|
||
|
//}
|
||
|
|
||
|
// Material config specific setup
|
||
|
if (Objects.Num() == 1)
|
||
|
{
|
||
|
auto* World = CastChecked<AVoxelWorld>(Objects[0]);
|
||
|
switch (World->MaterialConfig)
|
||
|
{
|
||
|
case EVoxelMaterialConfig::RGB:
|
||
|
DetailLayout.HideProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, MaterialCollection));
|
||
|
DetailLayout.HideProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, LODMaterialCollections));
|
||
|
if (World->RGBHardness != EVoxelRGBHardness::FourWayBlend && World->RGBHardness != EVoxelRGBHardness::FiveWayBlend)
|
||
|
{
|
||
|
DetailLayout.HideProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, MaterialsHardness));
|
||
|
}
|
||
|
break;
|
||
|
case EVoxelMaterialConfig::SingleIndex:
|
||
|
case EVoxelMaterialConfig::MultiIndex:
|
||
|
DetailLayout.HideProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, VoxelMaterial));
|
||
|
DetailLayout.HideProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, LODMaterials));
|
||
|
DetailLayout.HideProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, RGBHardness));
|
||
|
break;
|
||
|
default:
|
||
|
ensure(false);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
switch (World->UVConfig)
|
||
|
{
|
||
|
case EVoxelUVConfig::GlobalUVs:
|
||
|
break;
|
||
|
case EVoxelUVConfig::PackWorldUpInUVs:
|
||
|
case EVoxelUVConfig::PerVoxelUVs:
|
||
|
DetailLayout.HideProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, UVScale));
|
||
|
break;
|
||
|
default:
|
||
|
ensure(false);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
const FSimpleDelegate RefreshDelegate = FSimpleDelegate::CreateLambda([Properties = MakeWeakPtr(DetailLayout.GetPropertyUtilities())]()
|
||
|
{
|
||
|
if (Properties.IsValid())
|
||
|
{
|
||
|
Properties.Pin()->ForceRefresh();
|
||
|
}
|
||
|
});
|
||
|
DetailLayout.GetProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, MaterialConfig))->SetOnPropertyValueChanged(RefreshDelegate);
|
||
|
DetailLayout.GetProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, UVConfig))->SetOnPropertyValueChanged(RefreshDelegate);
|
||
|
DetailLayout.GetProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, RGBHardness))->SetOnPropertyValueChanged(RefreshDelegate);
|
||
|
}
|
||
|
|
||
|
DetailLayout.HideProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, EditorOnly_NewScale));
|
||
|
|
||
|
if (bIsDataAssetEditor)
|
||
|
{
|
||
|
DetailLayout.HideCategory("Voxel - Save");
|
||
|
DetailLayout.HideProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, Generator));
|
||
|
DetailLayout.HideProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, bCreateWorldAutomatically));
|
||
|
DetailLayout.HideProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, bUseCameraIfNoInvokersFound));
|
||
|
DetailLayout.HideProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, bEnableUndoRedo));
|
||
|
DetailLayout.HideProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, bEnableCustomWorldRebasing));
|
||
|
DetailLayout.HideProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, bMergeAssetActors));
|
||
|
DetailLayout.HideProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, bMergeDisableEditsBoxes));
|
||
|
DetailLayout.HideProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, bCreateGlobalPool));
|
||
|
DetailLayout.HideProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, ProcMeshClass));
|
||
|
DetailLayout.HideProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, bRenderWorld));
|
||
|
DetailLayout.HideProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, bStaticWorld));
|
||
|
DetailLayout.HideCategory("Voxel - Spawners");
|
||
|
DetailLayout.HideCategory("Physics");
|
||
|
DetailLayout.HideCategory("Voxel - Collisions");
|
||
|
DetailLayout.HideCategory("Voxel - Navmesh");
|
||
|
DetailLayout.HideCategory("Voxel - Multiplayer");
|
||
|
DetailLayout.HideCategory("Replication");
|
||
|
DetailLayout.HideCategory("Input");
|
||
|
DetailLayout.HideCategory("Actor");
|
||
|
DetailLayout.HideCategory("Cooking");
|
||
|
DetailLayout.HideCategory("TransformCommon");
|
||
|
DetailLayout.HideCategory("ComponentReplication");
|
||
|
DetailLayout.HideCategory("Variable");
|
||
|
DetailLayout.HideCategory("Tick");
|
||
|
DetailLayout.HideCategory("Voxel - Preview");
|
||
|
DetailLayout.HideCategory("Voxel - Bake");
|
||
|
}
|
||
|
|
||
|
{
|
||
|
// Component settings not affecting the voxel world
|
||
|
DetailLayout.HideCategory("Lighting");
|
||
|
DetailLayout.HideCategory("Tags");
|
||
|
DetailLayout.HideCategory("Activation");
|
||
|
DetailLayout.HideCategory("Rendering");
|
||
|
DetailLayout.HideCategory("AssetUserData");
|
||
|
DetailLayout.HideCategory("Mobile");
|
||
|
|
||
|
// No HLOD for voxels
|
||
|
DetailLayout.HideCategory("HLOD");
|
||
|
|
||
|
// Manually handling those
|
||
|
DetailLayout.HideCategory("Collision");
|
||
|
|
||
|
const auto SortCategory = [&](FName Name, uint32 Order, bool bCollapsed, FString NewName = {})
|
||
|
{
|
||
|
FText NewNameText;
|
||
|
if (!NewName.IsEmpty())
|
||
|
{
|
||
|
NewNameText = FText::FromString(NewName);
|
||
|
}
|
||
|
auto& Builder = static_cast<FDetailCategoryImpl&>(DetailLayout.EditCategory(Name, NewNameText));
|
||
|
Builder.SetSortOrder(Order);
|
||
|
Builder.InitiallyCollapsed(bCollapsed);
|
||
|
};
|
||
|
|
||
|
uint32 Order = 1000;
|
||
|
SortCategory("Voxel - Preview", Order++, false);
|
||
|
SortCategory("Voxel - Save", Order++, true);
|
||
|
SortCategory("Voxel - General", Order++, false);
|
||
|
SortCategory("Voxel - World Size", Order++, false);
|
||
|
SortCategory("Voxel - Rendering", Order++, false);
|
||
|
SortCategory("Voxel - Materials", Order++, false);
|
||
|
SortCategory("Voxel - Spawners", Order++, true);
|
||
|
SortCategory("Physics", Order++, true, "Voxel - Physics");
|
||
|
SortCategory("Voxel - Collisions", Order++, true);
|
||
|
SortCategory("Voxel - Navmesh", Order++, true);
|
||
|
SortCategory("Voxel - LOD Settings", Order++, true);
|
||
|
SortCategory("Voxel - Performance", Order++, true);
|
||
|
SortCategory("Voxel - Multiplayer", Order++, true);
|
||
|
SortCategory("Voxel - Bake", Order++, true);
|
||
|
SortCategory("Replication", Order++, true);
|
||
|
SortCategory("Input", Order++, true);
|
||
|
SortCategory("Actor", Order++, true);
|
||
|
SortCategory("Cooking", Order++, true);
|
||
|
SortCategory("VirtualTexture", Order++, true);
|
||
|
}
|
||
|
|
||
|
const auto CreateWorldsDelegate = [Objects](auto Lambda)
|
||
|
{
|
||
|
return FOnClicked::CreateLambda([=]()
|
||
|
{
|
||
|
for (auto& Object : Objects)
|
||
|
{
|
||
|
auto* World = Cast<AVoxelWorld>(Object);
|
||
|
if (World)
|
||
|
{
|
||
|
Lambda(*World);
|
||
|
}
|
||
|
}
|
||
|
return FReply::Handled();
|
||
|
});
|
||
|
};
|
||
|
const auto CreateWorldsEnabledDelegate = [Objects](auto Lambda)
|
||
|
{
|
||
|
return TAttribute<bool>::Create([=]()
|
||
|
{
|
||
|
for (auto& Object : Objects)
|
||
|
{
|
||
|
auto* World = Cast<AVoxelWorld>(Object);
|
||
|
if (World)
|
||
|
{
|
||
|
if (!Lambda(*World))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
});
|
||
|
};
|
||
|
|
||
|
bool bIsBPEditor = false;
|
||
|
bool bIsEditor = false;
|
||
|
for (auto& Object : Objects)
|
||
|
{
|
||
|
bIsBPEditor = Object->GetWorld() == nullptr;
|
||
|
bIsEditor = !bIsBPEditor && Object->GetWorld()->WorldType == EWorldType::Editor;
|
||
|
}
|
||
|
|
||
|
if (!bIsBPEditor && !bIsDataAssetEditor)
|
||
|
{
|
||
|
if (bIsEditor)
|
||
|
{
|
||
|
FVoxelEditorUtilities::AddButtonToCategory(
|
||
|
DetailLayout,
|
||
|
"Voxel - Preview",
|
||
|
VOXEL_LOCTEXT("Toggle"),
|
||
|
VOXEL_LOCTEXT("Toggle World Preview"),
|
||
|
VOXEL_LOCTEXT("Toggle"),
|
||
|
false,
|
||
|
CreateWorldsDelegate([](AVoxelWorld& World)
|
||
|
{
|
||
|
World.Toggle();
|
||
|
GEditor->SelectActor(&World, true, true, true, true);
|
||
|
}));
|
||
|
|
||
|
FVoxelEditorUtilities::AddPropertyToCategory(
|
||
|
DetailLayout,
|
||
|
"Voxel - Preview",
|
||
|
GET_MEMBER_NAME_STATIC(AVoxelWorld, EditorOnly_NewScale),
|
||
|
true);
|
||
|
FVoxelEditorUtilities::AddButtonToCategory(
|
||
|
DetailLayout,
|
||
|
"Voxel - Preview",
|
||
|
VOXEL_LOCTEXT("Scale"),
|
||
|
VOXEL_LOCTEXT("Scale World Data"),
|
||
|
VOXEL_LOCTEXT("Scale"),
|
||
|
true,
|
||
|
CreateWorldsDelegate([](AVoxelWorld& World)
|
||
|
{
|
||
|
if (World.IsCreated())
|
||
|
{
|
||
|
if (EAppReturnType::Yes == FMessageDialog::Open(
|
||
|
EAppMsgType::YesNoCancel,
|
||
|
VOXEL_LOCTEXT("Scaling data might take a while/crash your PC! Do you want to continue?")))
|
||
|
{
|
||
|
FVoxelScopedSlowTask Scope(1.f, VOXEL_LOCTEXT("Scaling data"));
|
||
|
Scope.MakeDialog();
|
||
|
Scope.EnterProgressFrame();
|
||
|
|
||
|
FVoxelScopedTransaction Transaction(&World, "Scaling data", EVoxelChangeType::DataSwap);
|
||
|
UVoxelBlueprintLibrary::ScaleData(&World, World.EditorOnly_NewScale);
|
||
|
}
|
||
|
}
|
||
|
}));
|
||
|
|
||
|
FVoxelEditorUtilities::AddButtonToCategory(
|
||
|
DetailLayout,
|
||
|
"Voxel - Preview",
|
||
|
VOXEL_LOCTEXT("Clear All"),
|
||
|
VOXEL_LOCTEXT("Clear World Data"),
|
||
|
VOXEL_LOCTEXT("Clear All"),
|
||
|
true,
|
||
|
CreateWorldsDelegate([](AVoxelWorld& World)
|
||
|
{
|
||
|
if (World.IsCreated())
|
||
|
{
|
||
|
if (EAppReturnType::Yes == FMessageDialog::Open(
|
||
|
EAppMsgType::YesNoCancel,
|
||
|
VOXEL_LOCTEXT("This will clear all the voxel world edits! Do you want to continue?")))
|
||
|
{
|
||
|
World.GetData().ClearData();
|
||
|
World.Toggle();
|
||
|
World.Toggle();
|
||
|
}
|
||
|
}
|
||
|
}));
|
||
|
|
||
|
FVoxelEditorUtilities::AddButtonToCategory(
|
||
|
DetailLayout,
|
||
|
"Voxel - Preview",
|
||
|
VOXEL_LOCTEXT("Clear Values"),
|
||
|
VOXEL_LOCTEXT("Clear Value Data"),
|
||
|
VOXEL_LOCTEXT("Clear Values"),
|
||
|
true,
|
||
|
CreateWorldsDelegate([](AVoxelWorld& World)
|
||
|
{
|
||
|
if (World.IsCreated())
|
||
|
{
|
||
|
if (EAppReturnType::Yes == FMessageDialog::Open(
|
||
|
EAppMsgType::YesNoCancel,
|
||
|
VOXEL_LOCTEXT("This will clear all the voxel world value edits! Do you want to continue?")))
|
||
|
{
|
||
|
FVoxelScopedTransaction Transaction(&World, "Clear values", EVoxelChangeType::DataSwap);
|
||
|
UVoxelBlueprintLibrary::ClearValueData(&World);
|
||
|
}
|
||
|
}
|
||
|
}));
|
||
|
FVoxelEditorUtilities::AddButtonToCategory(
|
||
|
DetailLayout,
|
||
|
"Voxel - Preview",
|
||
|
VOXEL_LOCTEXT("Clear Materials"),
|
||
|
VOXEL_LOCTEXT("Clear Material Data"),
|
||
|
VOXEL_LOCTEXT("Clear Materials"),
|
||
|
true,
|
||
|
CreateWorldsDelegate([](AVoxelWorld& World)
|
||
|
{
|
||
|
if (World.IsCreated())
|
||
|
{
|
||
|
if (EAppReturnType::Yes == FMessageDialog::Open(
|
||
|
EAppMsgType::YesNoCancel,
|
||
|
VOXEL_LOCTEXT("This will clear all the voxel world material edits! Do you want to continue?")))
|
||
|
{
|
||
|
FVoxelScopedTransaction Transaction(&World, "Clear materials", EVoxelChangeType::DataSwap);
|
||
|
UVoxelBlueprintLibrary::ClearMaterialData(&World);
|
||
|
}
|
||
|
}
|
||
|
}));
|
||
|
FVoxelEditorUtilities::AddButtonToCategory(
|
||
|
DetailLayout,
|
||
|
"Voxel - Preview",
|
||
|
VOXEL_LOCTEXT("Set Values Dirty"),
|
||
|
VOXEL_LOCTEXT("Set Values as Dirty"),
|
||
|
VOXEL_LOCTEXT("Set Values Dirty"),
|
||
|
true,
|
||
|
CreateWorldsDelegate([](AVoxelWorld& World)
|
||
|
{
|
||
|
if (World.IsCreated())
|
||
|
{
|
||
|
if (EAppReturnType::Yes == FMessageDialog::Open(
|
||
|
EAppMsgType::YesNoCancel,
|
||
|
VOXEL_LOCTEXT("Setting values as dirty might take a while/crash your PC! Make sure your World Size is as small as possible. Do you want to continue?")))
|
||
|
{
|
||
|
FVoxelScopedSlowTask Scope(1.f, VOXEL_LOCTEXT("Setting values as dirty"));
|
||
|
Scope.MakeDialog();
|
||
|
Scope.EnterProgressFrame();
|
||
|
|
||
|
FVoxelScopedTransaction Transaction(&World, "Setting values as dirty", EVoxelChangeType::DataSwap);
|
||
|
UVoxelDataTools::SetBoxAsDirty(&World, FVoxelIntBox::Infinite, true, false);
|
||
|
World.GetData().MarkAsDirty();
|
||
|
}
|
||
|
}
|
||
|
}));
|
||
|
FVoxelEditorUtilities::AddButtonToCategory(
|
||
|
DetailLayout,
|
||
|
"Voxel - Preview",
|
||
|
VOXEL_LOCTEXT("Set Materials Dirty"),
|
||
|
VOXEL_LOCTEXT("Set Materials as Dirty"),
|
||
|
VOXEL_LOCTEXT("Set Materials Dirty"),
|
||
|
true,
|
||
|
CreateWorldsDelegate([](AVoxelWorld& World)
|
||
|
{
|
||
|
if (World.IsCreated())
|
||
|
{
|
||
|
if (EAppReturnType::Yes == FMessageDialog::Open(
|
||
|
EAppMsgType::YesNoCancel,
|
||
|
VOXEL_LOCTEXT("Setting materials as dirty might take a while/crash your PC! Make sure your World Size is as small as possible. Do you want to continue?")))
|
||
|
{
|
||
|
FVoxelScopedSlowTask Scope(1.f, VOXEL_LOCTEXT("Setting materials as dirty"));
|
||
|
Scope.MakeDialog();
|
||
|
Scope.EnterProgressFrame();
|
||
|
|
||
|
FVoxelScopedTransaction Transaction(&World, "Setting materials as dirty", EVoxelChangeType::DataSwap);
|
||
|
UVoxelDataTools::SetBoxAsDirty(&World, FVoxelIntBox::Infinite, false, true);
|
||
|
World.GetData().MarkAsDirty();
|
||
|
}
|
||
|
}
|
||
|
}));
|
||
|
FVoxelEditorUtilities::AddButtonToCategory(
|
||
|
DetailLayout,
|
||
|
"Voxel - Bake",
|
||
|
VOXEL_LOCTEXT("Bake"),
|
||
|
VOXEL_LOCTEXT("Bake World To Static Meshes"),
|
||
|
VOXEL_LOCTEXT("Bake"),
|
||
|
false,
|
||
|
CreateWorldsDelegate([](AVoxelWorld& World)
|
||
|
{
|
||
|
if (World.IsCreated())
|
||
|
{
|
||
|
BakeWorld(World);
|
||
|
}
|
||
|
}),
|
||
|
CreateWorldsEnabledDelegate([](AVoxelWorld& World)
|
||
|
{
|
||
|
return World.IsCreated();
|
||
|
}));
|
||
|
}
|
||
|
|
||
|
FVoxelEditorUtilities::AddButtonToCategory(
|
||
|
DetailLayout,
|
||
|
"Voxel - Save",
|
||
|
VOXEL_LOCTEXT("Load"),
|
||
|
VOXEL_LOCTEXT("Load from Save Object"),
|
||
|
VOXEL_LOCTEXT("Load"),
|
||
|
false,
|
||
|
CreateWorldsDelegate([](AVoxelWorld& World)
|
||
|
{
|
||
|
if (ensure(World.IsCreated()) && ensure(World.SaveObject))
|
||
|
{
|
||
|
World.LoadFromSaveObjectEditor();
|
||
|
}
|
||
|
}),
|
||
|
CreateWorldsEnabledDelegate([](AVoxelWorld& World)
|
||
|
{
|
||
|
return World.IsCreated() && World.SaveObject;
|
||
|
}));
|
||
|
|
||
|
DetailLayout.HideProperty(GET_MEMBER_NAME_STATIC(AVoxelWorld, SaveObject));
|
||
|
FVoxelEditorUtilities::AddPropertyToCategory(
|
||
|
DetailLayout,
|
||
|
"Voxel - Save",
|
||
|
GET_MEMBER_NAME_STATIC(AVoxelWorld, SaveObject),
|
||
|
false);
|
||
|
|
||
|
FVoxelEditorUtilities::AddButtonToCategory(
|
||
|
DetailLayout,
|
||
|
"Voxel - Save",
|
||
|
VOXEL_LOCTEXT("Save File"),
|
||
|
VOXEL_LOCTEXT("Save to File"),
|
||
|
VOXEL_LOCTEXT("Save"),
|
||
|
true,
|
||
|
CreateWorldsDelegate([](AVoxelWorld& World)
|
||
|
{
|
||
|
if (!ensure(World.IsCreated())) return;
|
||
|
|
||
|
const FString Path = GetVoxelWorldSaveFilePath(World, false);
|
||
|
if (Path.IsEmpty()) return;
|
||
|
|
||
|
FText Error;
|
||
|
if (World.SaveToFile(Path, Error))
|
||
|
{
|
||
|
World.SaveFilePath = Path;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
FMessageDialog::Open(EAppMsgType::Ok, Error);
|
||
|
}
|
||
|
}),
|
||
|
CreateWorldsEnabledDelegate([](AVoxelWorld& World)
|
||
|
{
|
||
|
return World.IsCreated();
|
||
|
}));
|
||
|
|
||
|
FVoxelEditorUtilities::AddButtonToCategory(
|
||
|
DetailLayout,
|
||
|
"Voxel - Save",
|
||
|
VOXEL_LOCTEXT("Load File"),
|
||
|
VOXEL_LOCTEXT("Load from File"),
|
||
|
VOXEL_LOCTEXT("Load"),
|
||
|
true,
|
||
|
CreateWorldsDelegate([](AVoxelWorld& World)
|
||
|
{
|
||
|
if (!ensure(World.IsCreated())) return;
|
||
|
|
||
|
const FString Path = GetVoxelWorldSaveFilePath(World, true);
|
||
|
if (Path.IsEmpty()) return;
|
||
|
|
||
|
FText Error;
|
||
|
if (World.LoadFromFile(Path, Error))
|
||
|
{
|
||
|
World.SaveFilePath = Path;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
FMessageDialog::Open(EAppMsgType::Ok, Error);
|
||
|
}
|
||
|
|
||
|
}),
|
||
|
CreateWorldsEnabledDelegate([](AVoxelWorld& World)
|
||
|
{
|
||
|
return World.IsCreated();
|
||
|
}));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void FVoxelWorldDetails::BakeWorld(AVoxelWorld& World)
|
||
|
{
|
||
|
FVoxelMessages::Info("Baking to static mesh requires Voxel Plugin Pro");
|
||
|
}
|