CelticCraft/Plugins/VoxelFree/Source/Voxel/Private/VoxelTools/VoxelBlueprintLibrary.cpp

1205 lines
No EOL
36 KiB
C++

// Copyright 2020 Phyronnaz
#include "VoxelTools/VoxelBlueprintLibrary.h"
#include "VoxelTools/VoxelToolHelpers.h"
#include "VoxelTools/VoxelDataTools.h"
#include "VoxelIntBox.h"
#include "VoxelWorld.h"
#include "VoxelData/VoxelData.h"
#include "VoxelData/VoxelDataUtilities.h"
#include "VoxelData/VoxelDataUtilities.inl"
#include "VoxelRender/IVoxelLODManager.h"
#include "VoxelRender/IVoxelRenderer.h"
#include "VoxelRender/VoxelMaterialInterface.h"
#include "VoxelRender/VoxelChunkMesh.h"
#include "VoxelRender/VoxelProcMeshBuffers.h"
#include "VoxelRender/VoxelProceduralMeshComponent.h"
#include "VoxelSpawners/VoxelHierarchicalInstancedStaticMeshComponent.h"
#include "VoxelEvents/VoxelEventManager.h"
#include "VoxelAssets/VoxelDataAssetData.h"
#include "VoxelAssets/VoxelHeightmapAssetData.h"
#include "IVoxelPool.h"
#include "VoxelDefaultPool.h"
#include "VoxelMessages.h"
#include "VoxelUtilities/VoxelGeneratorUtilities.h"
#include "Async/Async.h"
#include "Engine/StaticMesh.h"
#include "EngineUtils.h"
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
bool UVoxelBlueprintLibrary::IsVoxelPluginPro()
{
return false;
}
void UVoxelBlueprintLibrary::RaiseInfo(FString Message, UObject* Object)
{
FVoxelMessages::Info(Message, Object);
}
void UVoxelBlueprintLibrary::RaiseWarning(FString Message, UObject* Object)
{
FVoxelMessages::Warning(Message, Object);
}
void UVoxelBlueprintLibrary::RaiseError(FString Message, UObject* Object)
{
FVoxelMessages::Error(Message, Object);
}
int32 UVoxelBlueprintLibrary::NumberOfCores()
{
return FPlatformMisc::NumberOfCores();
}
float UVoxelBlueprintLibrary::GetMemoryUsageInMB(EVoxelMemoryUsageType Type)
{
#if ENABLE_VOXEL_MEMORY_STATS
#define CASE(X) return X.GetValue() / double(1 << 20);
switch (Type)
{
case EVoxelMemoryUsageType::Total:
CASE(STAT_TotalVoxelMemory_MemoryUsage);
case EVoxelMemoryUsageType::VoxelsDirtyValuesData:
CASE(STAT_VoxelDataOctreeDirtyValuesMemory_MemoryUsage);
case EVoxelMemoryUsageType::VoxelsDirtyMaterialsData:
CASE(STAT_VoxelDataOctreeDirtyMaterialsMemory_MemoryUsage);
case EVoxelMemoryUsageType::VoxelsCachedValuesData:
CASE(STAT_VoxelDataOctreeCachedValuesMemory_MemoryUsage);
case EVoxelMemoryUsageType::VoxelsCachedMaterialsData:
CASE(STAT_VoxelDataOctreeCachedMaterialsMemory_MemoryUsage);
case EVoxelMemoryUsageType::UndoRedo:
CASE(STAT_VoxelUndoRedoMemory_MemoryUsage);
case EVoxelMemoryUsageType::Multiplayer:
CASE(STAT_VoxelMultiplayerMemory_MemoryUsage);
case EVoxelMemoryUsageType::IntermediateBuffers:
CASE(STAT_VoxelChunkMeshMemory_MemoryUsage);
case EVoxelMemoryUsageType::MeshesIndices:
CASE(STAT_VoxelProcMeshMemory_Indices_MemoryUsage);
case EVoxelMemoryUsageType::MeshesTessellationIndices:
CASE(STAT_VoxelProcMeshMemory_Adjacency_MemoryUsage);
case EVoxelMemoryUsageType::MeshesVertices:
CASE(STAT_VoxelProcMeshMemory_Positions_MemoryUsage);
case EVoxelMemoryUsageType::MeshesColors:
CASE(STAT_VoxelProcMeshMemory_Colors_MemoryUsage);
case EVoxelMemoryUsageType::MeshesUVsAndTangents:
CASE(STAT_VoxelProcMeshMemory_UVs_Tangents_MemoryUsage);
case EVoxelMemoryUsageType::DataAssets:
CASE(STAT_VoxelDataAssetMemory_MemoryUsage);
case EVoxelMemoryUsageType::HeightmapAssets:
CASE(STAT_VoxelHeightmapAssetMemory_MemoryUsage);
case EVoxelMemoryUsageType::UncompressedSaves:
CASE(STAT_VoxelUncompressedSavesMemory_MemoryUsage);
case EVoxelMemoryUsageType::CompressedSaves:
CASE(STAT_VoxelCompressedSavesMemory_MemoryUsage);
default:
ensure(false);
return 0.f;
}
#undef CASE
#else
FVoxelMessages::Error(FUNCTION_ERROR("Requires ENABLE_VOXEL_MEMORY_STATS=1"));
return 0.f;
#endif
}
float UVoxelBlueprintLibrary::GetPeakMemoryUsageInMB(EVoxelMemoryUsageType Type)
{
#if ENABLE_VOXEL_MEMORY_STATS
#define CASE(X) return X.GetValue() / double(1 << 20);
switch (Type)
{
case EVoxelMemoryUsageType::Total:
CASE(STAT_TotalVoxelMemory_MemoryPeak);
case EVoxelMemoryUsageType::VoxelsDirtyValuesData:
CASE(STAT_VoxelDataOctreeDirtyValuesMemory_MemoryPeak);
case EVoxelMemoryUsageType::VoxelsDirtyMaterialsData:
CASE(STAT_VoxelDataOctreeDirtyMaterialsMemory_MemoryPeak);
case EVoxelMemoryUsageType::VoxelsCachedValuesData:
CASE(STAT_VoxelDataOctreeCachedValuesMemory_MemoryPeak);
case EVoxelMemoryUsageType::VoxelsCachedMaterialsData:
CASE(STAT_VoxelDataOctreeCachedMaterialsMemory_MemoryPeak);
case EVoxelMemoryUsageType::UndoRedo:
CASE(STAT_VoxelUndoRedoMemory_MemoryPeak);
case EVoxelMemoryUsageType::Multiplayer:
CASE(STAT_VoxelMultiplayerMemory_MemoryPeak);
case EVoxelMemoryUsageType::IntermediateBuffers:
CASE(STAT_VoxelChunkMeshMemory_MemoryPeak);
case EVoxelMemoryUsageType::MeshesIndices:
CASE(STAT_VoxelProcMeshMemory_Indices_MemoryPeak);
case EVoxelMemoryUsageType::MeshesTessellationIndices:
CASE(STAT_VoxelProcMeshMemory_Adjacency_MemoryPeak);
case EVoxelMemoryUsageType::MeshesVertices:
CASE(STAT_VoxelProcMeshMemory_Positions_MemoryPeak);
case EVoxelMemoryUsageType::MeshesColors:
CASE(STAT_VoxelProcMeshMemory_Colors_MemoryPeak);
case EVoxelMemoryUsageType::MeshesUVsAndTangents:
CASE(STAT_VoxelProcMeshMemory_UVs_Tangents_MemoryPeak);
case EVoxelMemoryUsageType::DataAssets:
CASE(STAT_VoxelDataAssetMemory_MemoryPeak);
case EVoxelMemoryUsageType::HeightmapAssets:
CASE(STAT_VoxelHeightmapAssetMemory_MemoryPeak);
case EVoxelMemoryUsageType::UncompressedSaves:
CASE(STAT_VoxelUncompressedSavesMemory_MemoryPeak);
case EVoxelMemoryUsageType::CompressedSaves:
CASE(STAT_VoxelCompressedSavesMemory_MemoryPeak);
default:
ensure(false);
return 0.f;
}
#undef CASE
#else
FVoxelMessages::Error(FUNCTION_ERROR("Requires ENABLE_VOXEL_MEMORY_STATS=1"));
return 0.f;
#endif
}
void UVoxelBlueprintLibrary::LogMemoryStats()
{
#if ENABLE_VOXEL_MEMORY_STATS
struct FNameAndUsage
{
const TCHAR* Name = nullptr;
int64 Usage = 0;
};
TArray<FNameAndUsage> Usages;
TArray<FNameAndUsage> Peaks;
for (auto& It : GetVoxelMemoryCounters())
{
Usages.Add(FNameAndUsage{ It.Key, It.Value.UsageCounterPtr->GetValue() });
Peaks.Add(FNameAndUsage{ It.Key, It.Value.PeakCounterPtr->GetValue() });
}
Usages.Sort([](FNameAndUsage A, FNameAndUsage B) { return A.Usage > B.Usage; });
Peaks.Sort([](FNameAndUsage A, FNameAndUsage B) { return A.Usage > B.Usage; });
LOG_VOXEL(Log, TEXT("--------------------------------------"));
LOG_VOXEL(Log, TEXT("Voxel Memory Usage:"));
for (auto& Usage : Usages)
{
LOG_VOXEL(Log, TEXT("%50s: %6fMB"), Usage.Name, Usage.Usage / double(1 << 20));
}
LOG_VOXEL(Log, TEXT("--------------------------------------"));
LOG_VOXEL(Log, TEXT("--------------------------------------"));
LOG_VOXEL(Log, TEXT("Voxel Memory Peaks:"));
for (auto& Usage : Peaks)
{
LOG_VOXEL(Log, TEXT("%50s: %6fMB"), Usage.Name, Usage.Usage / double(1 << 20));
}
LOG_VOXEL(Log, TEXT("--------------------------------------"));
#else
FVoxelMessages::Error(FUNCTION_ERROR("Requires ENABLE_VOXEL_MEMORY_STATS=1"));
return 0.f;
#endif
}
float UVoxelBlueprintLibrary::GetEstimatedCollisionsMemoryUsageInMB(AVoxelWorld* World)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED();
uint64 MemoryUsage = 0;
for (auto* Component : World->GetComponents())
{
auto* ProcMeshComponent = Cast<UVoxelProceduralMeshComponent>(Component);
if (!ProcMeshComponent) continue;
ProcMeshComponent->IterateSections([&](auto& Settings, const FVoxelProcMeshBuffers& Buffers)
{
MemoryUsage += Buffers.IndexBuffer.GetAllocatedSize();
MemoryUsage += Buffers.VertexBuffers.PositionVertexBuffer.GetNumVertices() * Buffers.VertexBuffers.PositionVertexBuffer.GetStride();
});
}
return MemoryUsage / double(1 << 20);
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
FVoxelIntBox UVoxelBlueprintLibrary::TransformGlobalBoxToVoxelBox(AVoxelWorld* World, FBox Box)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED();
FVoxelIntBoxWithValidity Result;
const auto Add = [&](const FVector& Position)
{
const FVoxelVector LocalPosition = World->GlobalToLocalFloat(Position);
Result += FVoxelUtilities::FloorToInt(LocalPosition);
Result += FVoxelUtilities::CeilToInt(LocalPosition);
};
Add({ Box.Min.X, Box.Min.Y, Box.Min.Z });
Add({ Box.Max.X, Box.Min.Y, Box.Min.Z });
Add({ Box.Min.X, Box.Max.Y, Box.Min.Z });
Add({ Box.Max.X, Box.Max.Y, Box.Min.Z });
Add({ Box.Min.X, Box.Min.Y, Box.Max.Z });
Add({ Box.Max.X, Box.Min.Y, Box.Max.Z });
Add({ Box.Min.X, Box.Max.Y, Box.Max.Z });
Add({ Box.Max.X, Box.Max.Y, Box.Max.Z });
return Result.GetBox();
}
FBox UVoxelBlueprintLibrary::TransformVoxelBoxToGlobalBox(AVoxelWorld* World, FVoxelIntBox Box)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED();
FBox Result(ForceInit);
for (const auto& Corner : Box.GetCorners(1))
{
Result += World->LocalToGlobal(Corner);
}
return Result;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
AVoxelWorld* UVoxelBlueprintLibrary::GetVoxelWorldContainingPosition(UObject* WorldContextObject, FVector Position)
{
VOXEL_FUNCTION_COUNTER();
if (!WorldContextObject)
{
FVoxelMessages::Error(FUNCTION_ERROR("No world context!"));
return {};
}
const auto VoxelWorlds = GetAllVoxelWorldsContainingPosition(WorldContextObject, Position);
if (VoxelWorlds.Num() == 0)
{
return nullptr;
}
if (VoxelWorlds.Num() > 1)
{
FVoxelMessages::Warning(FUNCTION_ERROR("More than one voxel world is containing position! Consider using GetAllVoxelWorldsContainingPosition instead"));
}
return VoxelWorlds[0];
}
TArray<AVoxelWorld*> UVoxelBlueprintLibrary::GetAllVoxelWorldsContainingPosition(UObject* WorldContextObject, FVector Position)
{
VOXEL_FUNCTION_COUNTER();
if (!WorldContextObject)
{
FVoxelMessages::Error(FUNCTION_ERROR("No world context!"));
return {};
}
TArray<AVoxelWorld*> Result;
for (auto* VoxelWorld : TActorRange<AVoxelWorld>(WorldContextObject->GetWorld()))
{
if (VoxelWorld->IsCreated())
{
const FVoxelIntBox WorldBounds = VoxelWorld->GetWorldBounds();
const FVoxelVector LocalPosition = VoxelWorld->GlobalToLocalFloat(Position);
if (WorldBounds.ContainsFloat(LocalPosition))
{
Result.Add(VoxelWorld);
}
}
}
return Result;
}
AVoxelWorld* UVoxelBlueprintLibrary::GetVoxelWorldOverlappingBox(UObject* WorldContextObject, FBox Box)
{
VOXEL_FUNCTION_COUNTER();
if (!WorldContextObject)
{
FVoxelMessages::Error(FUNCTION_ERROR("No world context!"));
return {};
}
const auto VoxelWorlds = GetAllVoxelWorldsOverlappingBox(WorldContextObject, Box);
if (VoxelWorlds.Num() == 0)
{
return nullptr;
}
if (VoxelWorlds.Num() > 1)
{
FVoxelMessages::Warning(FUNCTION_ERROR("More than one voxel world is overlapping box! Consider using GetAllVoxelWorldsOverlappingBox instead"));
}
return VoxelWorlds[0];
}
TArray<AVoxelWorld*> UVoxelBlueprintLibrary::GetAllVoxelWorldsOverlappingBox(UObject* WorldContextObject, FBox Box)
{
VOXEL_FUNCTION_COUNTER();
if (!WorldContextObject)
{
FVoxelMessages::Error(FUNCTION_ERROR("No world context!"));
return {};
}
TArray<AVoxelWorld*> Result;
for (auto* VoxelWorld : TActorRange<AVoxelWorld>(WorldContextObject->GetWorld()))
{
if (VoxelWorld->IsCreated())
{
const FVoxelIntBox WorldBounds = VoxelWorld->GetWorldBounds();
const FVoxelIntBox LocalBox = TransformGlobalBoxToVoxelBox(VoxelWorld, Box);
if (WorldBounds.Intersect(LocalBox))
{
Result.Add(VoxelWorld);
}
}
}
return Result;
}
AVoxelWorld* UVoxelBlueprintLibrary::GetVoxelWorldOverlappingActor(AActor* Actor)
{
VOXEL_FUNCTION_COUNTER();
if (!Actor)
{
FVoxelMessages::Error(FUNCTION_ERROR("No Actor!"));
return {};
}
const auto VoxelWorlds = GetAllVoxelWorldsOverlappingActor(Actor);
if (VoxelWorlds.Num() == 0)
{
return nullptr;
}
if (VoxelWorlds.Num() > 1)
{
FVoxelMessages::Warning(FUNCTION_ERROR("More than one voxel world is overlapping actor! Consider using GetAllVoxelWorldsOverlappingActor instead"));
}
return VoxelWorlds[0];
}
TArray<AVoxelWorld*> UVoxelBlueprintLibrary::GetAllVoxelWorldsOverlappingActor(AActor* Actor)
{
VOXEL_FUNCTION_COUNTER();
if (!Actor)
{
FVoxelMessages::Error(FUNCTION_ERROR("No Actor!"));
return {};
}
return GetAllVoxelWorldsOverlappingBox(Actor, Actor->GetComponentsBoundingBox(true));
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void UVoxelBlueprintLibrary::SpawnVoxelSpawnerActorsInArea(
TArray<AVoxelSpawnerActor*>& OutActors,
AVoxelWorld* World,
FVoxelIntBox Bounds,
EVoxelSpawnerActorSpawnType SpawnType)
{
FVoxelMessages::Info(FUNCTION_ERROR("Voxel Spawners require Voxel Plugin Pro"));
}
AVoxelSpawnerActor* UVoxelBlueprintLibrary::SpawnVoxelSpawnerActorByInstanceIndex(
AVoxelWorld* World,
UVoxelHierarchicalInstancedStaticMeshComponent* Component,
int32 InstanceIndex)
{
FVoxelMessages::Info(FUNCTION_ERROR("Voxel Spawners require Voxel Plugin Pro"));
return nullptr;
}
void UVoxelBlueprintLibrary::AddInstances(
AVoxelWorld* const World,
UStaticMesh* const Mesh,
const TArray<FTransform>& Transforms,
const TArray<FLinearColor>& Colors,
FVoxelInstancedMeshSettings InstanceSettings,
FVoxelSpawnerActorSettings ActorSettings,
const FVector FloatingDetectionOffset)
{
FVoxelMessages::Info(FUNCTION_ERROR("Voxel Spawners require Voxel Plugin Pro"));
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void UVoxelBlueprintLibrary::RegenerateSpawners(AVoxelWorld* World, FVoxelIntBox Bounds)
{
FVoxelMessages::Info(FUNCTION_ERROR("Voxel Spawners require Voxel Plugin Pro"));
}
void UVoxelBlueprintLibrary::MarkSpawnersDirty(AVoxelWorld* World, FVoxelIntBox Bounds)
{
FVoxelMessages::Info(FUNCTION_ERROR("Voxel Spawners require Voxel Plugin Pro"));
}
FVoxelSpawnersSave UVoxelBlueprintLibrary::GetSpawnersSave(AVoxelWorld* World)
{
FVoxelMessages::Info(FUNCTION_ERROR("Voxel Spawners require Voxel Plugin Pro"));
return {};
}
void UVoxelBlueprintLibrary::LoadFromSpawnersSave(AVoxelWorld* World, const FVoxelSpawnersSave& Save)
{
FVoxelMessages::Info(FUNCTION_ERROR("Voxel Spawners require Voxel Plugin Pro"));
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
bool UVoxelBlueprintLibrary::Undo(AVoxelWorld* World, TArray<FVoxelIntBox>& OutUpdatedBounds)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED();
auto& Data = World->GetData();
if (!Data.bEnableUndoRedo)
{
FVoxelMessages::Error(FUNCTION_ERROR("bEnableUndoRedo is false!"));
return false;
}
if (!Data.IsCurrentFrameEmpty())
{
FVoxelMessages::Error("Undo: Undo called but edits have been made since last SaveFrame. Please call SaveFrame after every edits");
return false;
}
OutUpdatedBounds.Reset();
if (!Data.Undo(OutUpdatedBounds))
{
return false;
}
World->GetLODManager().UpdateBounds(OutUpdatedBounds);
return true;
}
bool UVoxelBlueprintLibrary::Undo(AVoxelWorld* World)
{
TArray<FVoxelIntBox> Dummy;
return Undo(World, Dummy);
}
bool UVoxelBlueprintLibrary::Redo(AVoxelWorld* World, TArray<FVoxelIntBox>& OutUpdatedBounds)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED();
auto& Data = World->GetData();
if (!Data.bEnableUndoRedo)
{
FVoxelMessages::Error(FUNCTION_ERROR("bEnableUndoRedo is false!"));
return false;
}
if (!Data.IsCurrentFrameEmpty())
{
FVoxelMessages::Error("Redo: Redo called but edits have been made since last SaveFrame. Please call SaveFrame after every edits");
return false;
}
OutUpdatedBounds.Reset();
if (!Data.Redo(OutUpdatedBounds))
{
return false;
}
World->GetLODManager().UpdateBounds(OutUpdatedBounds);
return true;
}
bool UVoxelBlueprintLibrary::Redo(AVoxelWorld* World)
{
TArray<FVoxelIntBox> Dummy;
return Redo(World, Dummy);
}
void UVoxelBlueprintLibrary::SaveFrame(AVoxelWorld* World)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED_VOID();
auto& Data = World->GetData();
if (!Data.bEnableUndoRedo)
{
FVoxelMessages::Error(FUNCTION_ERROR("bEnableUndoRedo is false!"));
return;
}
Data.SaveFrame(FVoxelIntBox::Infinite);
}
void UVoxelBlueprintLibrary::ClearFrames(AVoxelWorld* World)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED_VOID();
auto& Data = World->GetData();
if (!Data.bEnableUndoRedo)
{
FVoxelMessages::Error(FUNCTION_ERROR("bEnableUndoRedo is false!"));
return;
}
Data.ClearFrames();
}
int32 UVoxelBlueprintLibrary::GetHistoryPosition(AVoxelWorld* World)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED();
auto& Data = World->GetData();
if (!Data.bEnableUndoRedo)
{
FVoxelMessages::Error(FUNCTION_ERROR("bEnableUndoRedo is false!"));
return 0;
}
return Data.GetHistoryPosition();
}
///////////////////////////////////////////////////////////////////////////////
FVector UVoxelBlueprintLibrary::GetNormal(AVoxelWorld* World, FIntVector Position)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED();
const auto& Data = World->GetData();
FVoxelReadScopeLock Lock(Data, FVoxelIntBox(Position - FIntVector(1), Position + FIntVector(2)), "GetNormal");
return FVoxelDataUtilities::GetGradientFromGetValue<int32>(FVoxelDataUtilities::MakeFloatData(Data), Position.X, Position.Y, Position.Z, 0);
}
float UVoxelBlueprintLibrary::GetFloatOutput(AVoxelWorld* World, FName Name, float X, float Y, float Z, float DefaultValue)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED();
auto& Data = World->GetData();
if (!Data.Generator->GetOutputsPtrMap<v_flt>().Contains(Name))
{
FVoxelMessages::Error(FUNCTION_ERROR(FVoxelUtilities::GetMissingGeneratorOutputErrorString<v_flt>(Name, *Data.Generator)));
return 0;
}
return Data.GetCustomOutput<v_flt>(DefaultValue, Name, X, Y, Z, 0);
}
int32 UVoxelBlueprintLibrary::GetIntOutput(AVoxelWorld* World, FName Name, float X, float Y, float Z, int32 DefaultValue)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED();
auto& Data = World->GetData();
if (!Data.Generator->GetOutputsPtrMap<int32>().Contains(Name))
{
FVoxelMessages::Error(FUNCTION_ERROR(FVoxelUtilities::GetMissingGeneratorOutputErrorString<int32>(Name, *Data.Generator)));
return 0;
}
return Data.GetCustomOutput<int32>(DefaultValue, Name, X, Y, Z, 0);
}
FVoxelIntBox UVoxelBlueprintLibrary::GetBounds(AVoxelWorld* World)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED();
return World->GetData().WorldBounds;
}
void UVoxelBlueprintLibrary::ClearAllData(AVoxelWorld* World, bool bUpdateRender)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED_VOID();
World->GetData().ClearData();
if (bUpdateRender)
{
World->GetLODManager().UpdateBounds(FVoxelIntBox::Infinite);
}
}
void UVoxelBlueprintLibrary::ClearValueData(AVoxelWorld* World, bool bUpdateRender)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED_VOID();
auto& Data = World->GetData();
{
FVoxelWriteScopeLock Lock(Data, FVoxelIntBox::Infinite, FUNCTION_FNAME);
FVoxelDataUtilities::ClearData<FVoxelValue>(Data);
}
if (bUpdateRender)
{
World->GetLODManager().UpdateBounds(FVoxelIntBox::Infinite);
}
}
void UVoxelBlueprintLibrary::ClearMaterialData(AVoxelWorld* World, bool bUpdateRender)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED_VOID();
auto& Data = World->GetData();
{
FVoxelWriteScopeLock Lock(Data, FVoxelIntBox::Infinite, FUNCTION_FNAME);
FVoxelDataUtilities::ClearData<FVoxelMaterial>(Data);
}
if (bUpdateRender)
{
World->GetLODManager().UpdateBounds(FVoxelIntBox::Infinite);
}
}
bool UVoxelBlueprintLibrary::HasValueData(AVoxelWorld* World)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED();
auto& Data = World->GetData();
FVoxelReadScopeLock Lock(Data, FVoxelIntBox::Infinite, FUNCTION_FNAME);
return FVoxelDataUtilities::HasData<FVoxelValue>(World->GetData());
}
bool UVoxelBlueprintLibrary::HasMaterialData(AVoxelWorld* World)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED();
auto& Data = World->GetData();
FVoxelReadScopeLock Lock(Data, FVoxelIntBox::Infinite, FUNCTION_FNAME);
return FVoxelDataUtilities::HasData<FVoxelMaterial>(Data);
}
void UVoxelBlueprintLibrary::ClearDirtyData(AVoxelWorld* World, bool bUpdateRender)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED_VOID();
auto& Data = World->GetData();
TArray<FVoxelIntBox> OutBoundsToUpdate;
{
FVoxelWriteScopeLock Lock(Data, FVoxelIntBox::Infinite, FUNCTION_FNAME);
Data.ClearOctreeData(OutBoundsToUpdate);
}
if (bUpdateRender)
{
World->GetLODManager().UpdateBounds(OutBoundsToUpdate);
}
}
void UVoxelBlueprintLibrary::ScaleData(AVoxelWorld* World, const FVector& Scale)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED_VOID();
auto& SourceData = World->GetData();
const auto DestData = SourceData.Clone();
{
FVoxelReadScopeLock LockA(SourceData, FVoxelIntBox::Infinite, FUNCTION_FNAME);
FVoxelWriteScopeLock LockB(*DestData, FVoxelIntBox::Infinite, FUNCTION_FNAME);
FVoxelDataUtilities::ScaleWorldData<FVoxelValue>(SourceData, *DestData, Scale);
}
World->DestroyWorld();
FVoxelWorldCreateInfo Info;
Info.bOverrideData = true;
Info.DataOverride_Raw = DestData;
World->CreateWorld(Info);
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void UVoxelBlueprintLibrary::UpdatePosition(AVoxelWorld* World, FIntVector Position)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED_VOID();
World->GetLODManager().UpdateBounds(FVoxelIntBox(Position));
}
void UVoxelBlueprintLibrary::UpdateBounds(AVoxelWorld* World, FVoxelIntBox Bounds)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED_VOID();
CHECK_BOUNDS_ARE_VALID_VOID();
World->GetLODManager().UpdateBounds(Bounds);
}
void UVoxelBlueprintLibrary::UpdateAll(AVoxelWorld* World)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED_VOID();
World->GetLODManager().UpdateBounds(FVoxelIntBox::Infinite);
}
void UVoxelBlueprintLibrary::ApplyLODSettings(AVoxelWorld* World)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED_VOID();
World->UpdateDynamicLODSettings();
World->GetLODManager().ForceLODsUpdate();
}
bool UVoxelBlueprintLibrary::AreCollisionsEnabled(AVoxelWorld* World, FVector InPosition, int32& LOD, bool bConvertToVoxelSpace /*= true*/)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED();
LOD = 0;
const FVoxelVector Position = FVoxelToolHelpers::GetRealPosition(World, InPosition, bConvertToVoxelSpace);
auto& LODManager = World->GetLODManager();
if (!LODManager.Settings.WorldBounds.ContainsFloat(Position))
{
return false;
}
uint8 OutLOD;
const bool bResult = LODManager.AreCollisionsEnabled(FVoxelUtilities::RoundToInt(Position), OutLOD);
LOD = OutLOD;
return bResult;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
int32 UVoxelBlueprintLibrary::GetTaskCount(AVoxelWorld* World)
{
return World && World->IsCreated() ? FMath::Max(World->GetRenderer().GetTaskCount(), 0) : 0;
}
bool UVoxelBlueprintLibrary::IsVoxelWorldMeshLoading(AVoxelWorld* World)
{
return World && World->IsCreated() && World->GetRenderer().GetTaskCount() > 0;
}
bool UVoxelBlueprintLibrary::IsVoxelWorldFoliageLoading(AVoxelWorld* World)
{
FVoxelMessages::Info(FUNCTION_ERROR("Voxel Spawners require Voxel Plugin Pro"));
return false;
}
void UVoxelBlueprintLibrary::ApplyNewMaterials(AVoxelWorld* World)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED_VOID();
World->UpdateDynamicRendererSettings();
World->GetRenderer().ApplyNewMaterials();
}
void UVoxelBlueprintLibrary::RecreateRender(AVoxelWorld* World)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED_VOID();
World->RecreateRender();
}
void UVoxelBlueprintLibrary::RecreateSpawners(AVoxelWorld* World)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED_VOID();
World->RecreateSpawners();
}
void UVoxelBlueprintLibrary::Recreate(AVoxelWorld* World, bool bSaveData)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED_VOID();
FVoxelScopedFastSaveLoad FastSaveScope;
const bool bDataIsDirty = World->GetData().IsDirty();
FVoxelWorldCreateInfo Info;
if (bSaveData)
{
Info.bOverrideSave = true;
UVoxelDataTools::GetSave(World, Info.SaveOverride);
// Clear dirty flag to avoid popup
World->GetData().ClearDirtyFlag();
}
World->RecreateAll(Info);
if (bSaveData && bDataIsDirty)
{
// Set back dirty flag
World->GetData().MarkAsDirty();
}
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void UVoxelBlueprintLibrary::BindVoxelChunkEvents(
AVoxelWorld* World,
FChunkDynamicDelegate OnActivate,
FChunkDynamicDelegate OnDeactivate,
bool bFireExistingOnes,
int32 ChunkSize,
int32 ActivationDistanceInChunks)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED_VOID();
auto OnActivateLambda = [OnActivate](const FVoxelIntBox& Bounds)
{
OnActivate.ExecuteIfBound(Bounds);
};
auto OnDeactivateLambda = [OnDeactivate](const FVoxelIntBox& Bounds)
{
OnDeactivate.ExecuteIfBound(Bounds);
};
World->GetEventManager().BindEvent(
bFireExistingOnes,
FMath::Max(1, ChunkSize),
FMath::Max(0, ActivationDistanceInChunks),
FChunkDelegate::CreateLambda(OnActivateLambda),
FChunkDelegate::CreateLambda(OnDeactivateLambda));
}
void UVoxelBlueprintLibrary::BindVoxelGenerationEvent(
AVoxelWorld* World,
FChunkDynamicDelegate OnGenerate,
bool bFireExistingOnes,
int32 ChunkSize,
int32 GenerationDistanceInChunks)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED_VOID();
auto OnGenerateLambda = [OnGenerate](const FVoxelIntBox& Bounds)
{
OnGenerate.ExecuteIfBound(Bounds);
};
World->GetEventManager().BindGenerationEvent(
bFireExistingOnes,
FMath::Max(1, ChunkSize),
FMath::Max(0, GenerationDistanceInChunks),
FChunkDelegate::CreateLambda(OnGenerateLambda));
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
bool UVoxelBlueprintLibrary::IsValidRef(AVoxelWorld* World, FVoxelToolRenderingRef Ref)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED();
return Ref.Id.IsValid() && World->GetToolRenderingManager().IsValidTool(Ref.Id);
}
FVoxelToolRenderingRef UVoxelBlueprintLibrary::CreateToolRendering(AVoxelWorld* World)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED();
return { World->GetToolRenderingManager().CreateTool() };
}
#define CHECK_TOOL_RENDERING_REF() \
if (!Ref.Id.IsValid()) { FVoxelMessages::Error(FUNCTION_ERROR("Unitilialized tool rendering reference")); return; } \
if (!World->GetToolRenderingManager().IsValidTool(Ref.Id)) { FVoxelMessages::Error(FUNCTION_ERROR("Outdated tool rendering reference")); return; }
void UVoxelBlueprintLibrary::DestroyToolRendering(AVoxelWorld* World, FVoxelToolRenderingRef Ref)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED_VOID();
CHECK_TOOL_RENDERING_REF();
World->GetToolRenderingManager().RemoveTool(Ref.Id);
}
void UVoxelBlueprintLibrary::SetToolRenderingMaterial(AVoxelWorld* World, FVoxelToolRenderingRef Ref, UMaterialInterface* Material)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED_VOID();
CHECK_TOOL_RENDERING_REF();
World->GetToolRenderingManager().EditTool(Ref.Id, [&](auto& Tool) { Tool.Material = FVoxelMaterialInterfaceManager::Get().CreateMaterial(Material); });
}
void UVoxelBlueprintLibrary::SetToolRenderingBounds(AVoxelWorld* World, FVoxelToolRenderingRef Ref, FBox Bounds)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED_VOID();
CHECK_TOOL_RENDERING_REF();
World->GetToolRenderingManager().EditTool(Ref.Id, [&](auto& Tool) { Tool.WorldBounds = Bounds; });
}
void UVoxelBlueprintLibrary::SetToolRenderingEnabled(AVoxelWorld* World, FVoxelToolRenderingRef Ref, bool bEnabled)
{
VOXEL_FUNCTION_COUNTER();
CHECK_VOXELWORLD_IS_CREATED_VOID();
CHECK_TOOL_RENDERING_REF();
World->GetToolRenderingManager().EditTool(Ref.Id, [&](auto& Tool) { Tool.bEnabled = bEnabled; });
}
#undef CHECK_TOOL_RENDERING_REF
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void UVoxelBlueprintLibrary::CreateGlobalVoxelThreadPool(
const TMap<EVoxelTaskType, int32>& PriorityCategoriesOverrides,
const TMap<EVoxelTaskType, int32>& PriorityOffsetsOverrides,
int32 NumberOfThreads,
bool bConstantPriorities)
{
VOXEL_FUNCTION_COUNTER();
if (IsGlobalVoxelPoolCreated())
{
FVoxelMessages::Error(FUNCTION_ERROR("Global pool already created"));
return;
}
const auto Pool = FVoxelDefaultPool::Create(
FMath::Max(1, NumberOfThreads),
bConstantPriorities,
PriorityCategoriesOverrides,
PriorityOffsetsOverrides);
IVoxelPool::SetGlobalPool(Pool, __FUNCTION__);
}
void UVoxelBlueprintLibrary::DestroyGlobalVoxelThreadPool()
{
VOXEL_FUNCTION_COUNTER();
if (!IsGlobalVoxelPoolCreated())
{
FVoxelMessages::Error(FUNCTION_ERROR("Global pool not created"));
return;
}
IVoxelPool::DestroyGlobalPool();
}
bool UVoxelBlueprintLibrary::IsGlobalVoxelPoolCreated()
{
return IVoxelPool::GetGlobalPool().IsValid();
}
void UVoxelBlueprintLibrary::CreateWorldVoxelThreadPool(
UWorld* World,
const TMap<EVoxelTaskType, int32>& PriorityCategoriesOverrides,
const TMap<EVoxelTaskType, int32>& PriorityOffsetsOverrides,
int32 NumberOfThreads,
bool bConstantPriorities)
{
VOXEL_FUNCTION_COUNTER();
if (!World)
{
FVoxelMessages::Error(FUNCTION_ERROR("World is NULL"));
return;
}
if (IsWorldVoxelPoolCreated(World))
{
FVoxelMessages::Error(FUNCTION_ERROR("Pool already created for this world"));
return;
}
const auto Pool = FVoxelDefaultPool::Create(
FMath::Max(1, NumberOfThreads),
bConstantPriorities,
PriorityCategoriesOverrides,
PriorityOffsetsOverrides);
IVoxelPool::SetWorldPool(World, Pool, __FUNCTION__);
}
void UVoxelBlueprintLibrary::DestroyWorldVoxelThreadPool(UWorld* World)
{
VOXEL_FUNCTION_COUNTER();
if (!World)
{
FVoxelMessages::Error(FUNCTION_ERROR("World is NULL"));
return;
}
if (!IsWorldVoxelPoolCreated(World))
{
FVoxelMessages::Error(FUNCTION_ERROR("No voxel pool created for this world"));
return;
}
IVoxelPool::DestroyWorldPool(World);
}
bool UVoxelBlueprintLibrary::IsWorldVoxelPoolCreated(UWorld* World)
{
return IVoxelPool::GetWorldPool(World).IsValid();
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
FVoxelIntBox UVoxelBlueprintLibrary::MakeIntBoxFromGlobalPositionAndRadius(AVoxelWorld* World, FVector GlobalPosition, float Radius)
{
CHECK_VOXELWORLD_IS_CREATED();
return FVoxelIntBox::SafeConstruct(
World->GlobalToLocal(GlobalPosition - Radius, EVoxelWorldCoordinatesRounding::RoundDown),
World->GlobalToLocal(GlobalPosition + Radius, EVoxelWorldCoordinatesRounding::RoundUp)
);
}
FVoxelIntBox UVoxelBlueprintLibrary::GetRenderBoundsOverlappingDataBounds(AVoxelWorld* World, FVoxelIntBox Bounds, int32 LOD)
{
CHECK_VOXELWORLD_IS_CREATED();
CHECK_BOUNDS_ARE_VALID();
LOD = FVoxelUtilities::ClampDepth<RENDER_CHUNK_SIZE>(LOD);
Bounds = Bounds.MakeMultipleOfBigger(RENDER_CHUNK_SIZE << LOD);
return Bounds.Extend(2 << LOD); // Account for the normals reads
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
FVoxelPaintMaterial UVoxelBlueprintLibrary::CreateFiveWayBlendPaintMaterial(FVoxelPaintMaterialFiveWayBlend FiveWayBlend)
{
if (!(0 <= FiveWayBlend.Channel && FiveWayBlend.Channel < 5))
{
FVoxelMessages::Error(FUNCTION_ERROR("Channel needs to be between 0 and 4"));
FiveWayBlend.Channel = 0;
}
FVoxelPaintMaterial PaintMaterial;
PaintMaterial.Type = EVoxelPaintMaterialType::FiveWayBlend;
PaintMaterial.FiveWayBlend = FiveWayBlend;
return PaintMaterial;
}
void UVoxelBlueprintLibrary::GetMultiIndex(
FVoxelMaterial Material,
bool bSortByStrength,
float& Strength0, uint8& Index0,
float& Strength1, uint8& Index1,
float& Strength2, uint8& Index2,
float& Strength3, uint8& Index3,
float& Wetness)
{
const TVoxelStaticArray<float, 4> Strengths = FVoxelUtilities::GetMultiIndexStrengths(Material);
Strength0 = Strengths[0];
Strength1 = Strengths[1];
Strength2 = Strengths[2];
Strength3 = Strengths[3];
Index0 = Material.GetMultiIndex_Index0();
Index1 = Material.GetMultiIndex_Index1();
Index2 = Material.GetMultiIndex_Index2();
Index3 = Material.GetMultiIndex_Index3();
Wetness = Material.GetMultiIndex_Wetness_AsFloat();
if (bSortByStrength)
{
#define SWAP(A, B) if (Strength##A < Strength##B) { Swap(Strength##A, Strength##B); Swap(Index##A, Index##B); }
SWAP(0, 1);
SWAP(2, 3);
SWAP(0, 2);
SWAP(1, 3);
SWAP(1, 2);
#undef SWAP
ensure(Strength0 >= Strength1 && Strength1 >= Strength2 && Strength2 >= Strength3);
}
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void UVoxelBlueprintLibrary::AddNeighborsToSet(const TSet<FIntVector>& InSet, TSet<FIntVector>& OutSet)
{
VOXEL_FUNCTION_COUNTER();
OutSet.Reset();
for (auto& P : InSet)
{
OutSet.Add(FIntVector(P.X - 1, P.Y - 1, P.Z - 1));
OutSet.Add(FIntVector(P.X - 0, P.Y - 1, P.Z - 1));
OutSet.Add(FIntVector(P.X + 1, P.Y - 1, P.Z - 1));
OutSet.Add(FIntVector(P.X - 1, P.Y + 0, P.Z - 1));
OutSet.Add(FIntVector(P.X - 0, P.Y + 0, P.Z - 1));
OutSet.Add(FIntVector(P.X + 1, P.Y + 0, P.Z - 1));
OutSet.Add(FIntVector(P.X - 1, P.Y + 1, P.Z - 1));
OutSet.Add(FIntVector(P.X - 0, P.Y + 1, P.Z - 1));
OutSet.Add(FIntVector(P.X + 1, P.Y + 1, P.Z - 1));
OutSet.Add(FIntVector(P.X - 1, P.Y - 1, P.Z + 0));
OutSet.Add(FIntVector(P.X - 0, P.Y - 1, P.Z + 0));
OutSet.Add(FIntVector(P.X + 1, P.Y - 1, P.Z + 0));
OutSet.Add(FIntVector(P.X - 1, P.Y + 0, P.Z + 0));
OutSet.Add(FIntVector(P.X - 0, P.Y + 0, P.Z + 0));
OutSet.Add(FIntVector(P.X + 1, P.Y + 0, P.Z + 0));
OutSet.Add(FIntVector(P.X - 1, P.Y + 1, P.Z + 0));
OutSet.Add(FIntVector(P.X - 0, P.Y + 1, P.Z + 0));
OutSet.Add(FIntVector(P.X + 1, P.Y + 1, P.Z + 0));
OutSet.Add(FIntVector(P.X - 1, P.Y - 1, P.Z + 1));
OutSet.Add(FIntVector(P.X - 0, P.Y - 1, P.Z + 1));
OutSet.Add(FIntVector(P.X + 1, P.Y - 1, P.Z + 1));
OutSet.Add(FIntVector(P.X - 1, P.Y + 0, P.Z + 1));
OutSet.Add(FIntVector(P.X - 0, P.Y + 0, P.Z + 1));
OutSet.Add(FIntVector(P.X + 1, P.Y + 0, P.Z + 1));
OutSet.Add(FIntVector(P.X - 1, P.Y + 1, P.Z + 1));
OutSet.Add(FIntVector(P.X - 0, P.Y + 1, P.Z + 1));
OutSet.Add(FIntVector(P.X + 1, P.Y + 1, P.Z + 1));
}
}