1173 lines
38 KiB
C++
1173 lines
38 KiB
C++
|
// Copyright 2020 Phyronnaz
|
||
|
|
||
|
#include "VoxelTools/VoxelDataTools.h"
|
||
|
#include "VoxelTools/VoxelToolHelpers.h"
|
||
|
#include "VoxelRender/IVoxelLODManager.h"
|
||
|
#include "VoxelData/VoxelDataIncludes.h"
|
||
|
#include "VoxelData/VoxelSaveUtilities.h"
|
||
|
#include "VoxelAssets/VoxelHeightmapAsset.h"
|
||
|
#include "VoxelAssets/VoxelHeightmapAssetSamplerWrapper.h"
|
||
|
#include "VoxelFeedbackContext.h"
|
||
|
|
||
|
#define VOXEL_DATA_TOOL_PREFIX const FVoxelIntBox Bounds(Position);
|
||
|
|
||
|
void UVoxelDataTools::GetValue(float& Value, AVoxelWorld* World, FIntVector Position)
|
||
|
{
|
||
|
VOXEL_TOOL_HELPER(Read, DoNotUpdateRender, VOXEL_DATA_TOOL_PREFIX, Value = Data.GetValue(Position, 0).ToFloat());
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::GetInterpolatedValue(float& Value, AVoxelWorld* World, FVector Position)
|
||
|
{
|
||
|
const FVoxelIntBox Bounds = FVoxelIntBox(Position).Extend(2);
|
||
|
VOXEL_TOOL_HELPER(Read, DoNotUpdateRender, NO_PREFIX, Value = FVoxelDataUtilities::MakeBilinearInterpolatedData(Data).GetValue(Position, 0));
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::SetValue(AVoxelWorld* World, FIntVector Position, float Value)
|
||
|
{
|
||
|
VOXEL_TOOL_HELPER(Write, UpdateRender, VOXEL_DATA_TOOL_PREFIX, Data.SetValue(Position, FVoxelValue(Value)));
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::GetMaterial(FVoxelMaterial& Material, AVoxelWorld* World, FIntVector Position)
|
||
|
{
|
||
|
VOXEL_TOOL_HELPER(Read, DoNotUpdateRender, VOXEL_DATA_TOOL_PREFIX, Material = Data.GetMaterial(Position, 0));
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::SetMaterial(AVoxelWorld* World, FIntVector Position, FVoxelMaterial Material)
|
||
|
{
|
||
|
VOXEL_TOOL_HELPER(Write, UpdateRender, VOXEL_DATA_TOOL_PREFIX, Data.SetMaterial(Position, Material));
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::CacheValues(AVoxelWorld* World, FVoxelIntBox Bounds, bool bMultiThreaded)
|
||
|
{
|
||
|
VOXEL_TOOL_HELPER(Write, DoNotUpdateRender, NO_PREFIX, Data.CacheBounds<FVoxelValue>(Bounds, bMultiThreaded));
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::CacheMaterials(AVoxelWorld* World, FVoxelIntBox Bounds, bool bMultiThreaded)
|
||
|
{
|
||
|
VOXEL_TOOL_HELPER(Write, DoNotUpdateRender, NO_PREFIX, Data.CacheBounds<FVoxelMaterial>(Bounds, bMultiThreaded));
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void UVoxelDataTools::GetValueAsync(
|
||
|
UObject* WorldContextObject,
|
||
|
FLatentActionInfo LatentInfo,
|
||
|
float& Value,
|
||
|
AVoxelWorld* World,
|
||
|
FIntVector Position,
|
||
|
bool bHideLatentWarnings)
|
||
|
{
|
||
|
VOXEL_TOOL_LATENT_HELPER_WITH_VALUE(Value, Read, DoNotUpdateRender, VOXEL_DATA_TOOL_PREFIX, InValue = Data.GetValue(Position, 0).ToFloat());
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::SetValueAsync(
|
||
|
UObject* WorldContextObject,
|
||
|
FLatentActionInfo LatentInfo,
|
||
|
AVoxelWorld* World,
|
||
|
FIntVector Position,
|
||
|
float Value,
|
||
|
bool bHideLatentWarnings)
|
||
|
{
|
||
|
VOXEL_TOOL_LATENT_HELPER(Write, UpdateRender, VOXEL_DATA_TOOL_PREFIX, Data.SetValue(Position, FVoxelValue(Value)));
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::GetMaterialAsync(
|
||
|
UObject* WorldContextObject,
|
||
|
FLatentActionInfo LatentInfo,
|
||
|
FVoxelMaterial& Material,
|
||
|
AVoxelWorld* World,
|
||
|
FIntVector Position,
|
||
|
bool bHideLatentWarnings)
|
||
|
{
|
||
|
VOXEL_TOOL_LATENT_HELPER_WITH_VALUE(Material, Read, DoNotUpdateRender, VOXEL_DATA_TOOL_PREFIX, InMaterial = Data.GetMaterial(Position, 0));
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::SetMaterialAsync(
|
||
|
UObject* WorldContextObject,
|
||
|
FLatentActionInfo LatentInfo,
|
||
|
AVoxelWorld* World,
|
||
|
FIntVector Position,
|
||
|
FVoxelMaterial Material,
|
||
|
bool bHideLatentWarnings)
|
||
|
{
|
||
|
VOXEL_TOOL_LATENT_HELPER(Write, UpdateRender, VOXEL_DATA_TOOL_PREFIX, Data.SetMaterial(Position, Material));
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::CacheValuesAsync(
|
||
|
UObject* WorldContextObject,
|
||
|
FLatentActionInfo LatentInfo,
|
||
|
AVoxelWorld* World,
|
||
|
FVoxelIntBox Bounds,
|
||
|
bool bHideLatentWarnings)
|
||
|
{
|
||
|
VOXEL_TOOL_LATENT_HELPER(Write, DoNotUpdateRender, NO_PREFIX, Data.CacheBounds<FVoxelValue>(Bounds, false));
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::CacheMaterialsAsync(
|
||
|
UObject* WorldContextObject,
|
||
|
FLatentActionInfo LatentInfo,
|
||
|
AVoxelWorld* World,
|
||
|
FVoxelIntBox Bounds,
|
||
|
bool bHideLatentWarnings)
|
||
|
{
|
||
|
VOXEL_TOOL_LATENT_HELPER(Write, DoNotUpdateRender, NO_PREFIX, Data.CacheBounds<FVoxelMaterial>(Bounds, false));
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
template<typename T>
|
||
|
inline bool CheckSave(const FVoxelData& Data, const T& Save)
|
||
|
{
|
||
|
if (Save.GetDepth() == -1)
|
||
|
{
|
||
|
FVoxelMessages::Error("LoadFromSave: Invalid save (Depth == -1). You're trying to load a save object that wasn't initialized");
|
||
|
return false;
|
||
|
}
|
||
|
if (Save.GetDepth() > Data.Depth)
|
||
|
{
|
||
|
FVoxelMessages::Warning("LoadFromSave: Save depth is bigger than world depth, the save data outside world bounds will be ignored");
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
#define CHECK_SAVE() \
|
||
|
if (!CheckSave(World->GetData(), Save)) \
|
||
|
{ \
|
||
|
return false; \
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::GetSave(AVoxelWorld* World, FVoxelUncompressedWorldSave& OutSave)
|
||
|
{
|
||
|
GetSave(World, OutSave.NewMutable(), OutSave.Objects);
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::GetSave(AVoxelWorld* World, FVoxelUncompressedWorldSaveImpl& OutSave, TArray<FVoxelObjectArchiveEntry>& OutObjects)
|
||
|
{
|
||
|
CHECK_VOXELWORLD_IS_CREATED_VOID();
|
||
|
World->GetData().GetSave(OutSave, OutObjects);
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::GetCompressedSave(AVoxelWorld* World, FVoxelCompressedWorldSave& OutSave)
|
||
|
{
|
||
|
GetCompressedSave(World, OutSave.NewMutable(), OutSave.Objects);
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::GetCompressedSave(AVoxelWorld* World, FVoxelCompressedWorldSaveImpl& OutSave, TArray<FVoxelObjectArchiveEntry>& OutObjects)
|
||
|
{
|
||
|
CHECK_VOXELWORLD_IS_CREATED_VOID();
|
||
|
FVoxelUncompressedWorldSaveImpl Save;
|
||
|
World->GetData().GetSave(Save, OutObjects);
|
||
|
UVoxelSaveUtilities::CompressVoxelSave(Save, OutSave);
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::GetSaveAsync(
|
||
|
UObject* WorldContextObject,
|
||
|
FLatentActionInfo LatentInfo,
|
||
|
AVoxelWorld* World,
|
||
|
FVoxelUncompressedWorldSave& OutSave,
|
||
|
bool bHideLatentWarnings)
|
||
|
{
|
||
|
VOXEL_FUNCTION_COUNTER();
|
||
|
|
||
|
FVoxelToolHelpers::StartAsyncLatentAction_WithWorld_WithValue(
|
||
|
WorldContextObject,
|
||
|
LatentInfo,
|
||
|
World,
|
||
|
FUNCTION_FNAME,
|
||
|
bHideLatentWarnings,
|
||
|
OutSave,
|
||
|
[](FVoxelData& Data, FVoxelUncompressedWorldSave& Save)
|
||
|
{
|
||
|
Data.GetSave(Save.NewMutable(), Save.Objects);
|
||
|
},
|
||
|
EVoxelUpdateRender::DoNotUpdateRender,
|
||
|
{});
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::GetCompressedSaveAsync(
|
||
|
UObject* WorldContextObject,
|
||
|
FLatentActionInfo LatentInfo,
|
||
|
AVoxelWorld* World,
|
||
|
FVoxelCompressedWorldSave& OutSave,
|
||
|
bool bHideLatentWarnings)
|
||
|
{
|
||
|
VOXEL_FUNCTION_COUNTER();
|
||
|
|
||
|
FVoxelToolHelpers::StartAsyncLatentAction_WithWorld_WithValue(
|
||
|
WorldContextObject,
|
||
|
LatentInfo,
|
||
|
World,
|
||
|
FUNCTION_FNAME,
|
||
|
bHideLatentWarnings,
|
||
|
OutSave,
|
||
|
[](FVoxelData& Data, FVoxelCompressedWorldSave& CompressedSave)
|
||
|
{
|
||
|
FVoxelUncompressedWorldSaveImpl Save;
|
||
|
Data.GetSave(Save, CompressedSave.Objects);
|
||
|
UVoxelSaveUtilities::CompressVoxelSave(Save, CompressedSave.NewMutable());
|
||
|
},
|
||
|
EVoxelUpdateRender::DoNotUpdateRender,
|
||
|
{});
|
||
|
}
|
||
|
|
||
|
bool UVoxelDataTools::LoadFromSave(const AVoxelWorld* World, const FVoxelUncompressedWorldSave& Save)
|
||
|
{
|
||
|
return LoadFromSave(World, Save.Const(), Save.Objects);
|
||
|
}
|
||
|
|
||
|
bool UVoxelDataTools::LoadFromSave(const AVoxelWorld* World, const FVoxelUncompressedWorldSaveImpl& Save, const TArray<FVoxelObjectArchiveEntry>& Objects)
|
||
|
{
|
||
|
CHECK_VOXELWORLD_IS_CREATED();
|
||
|
CHECK_SAVE();
|
||
|
|
||
|
TArray<FVoxelIntBox> BoundsToUpdate;
|
||
|
auto& Data = World->GetData();
|
||
|
|
||
|
const FVoxelGeneratorInit WorldInit = World->GetGeneratorInit();
|
||
|
const FVoxelPlaceableItemLoadInfo LoadInfo{ &WorldInit, &Objects };
|
||
|
|
||
|
const bool bSuccess = Data.LoadFromSave(Save, LoadInfo, &BoundsToUpdate);
|
||
|
|
||
|
World->GetLODManager().UpdateBounds(BoundsToUpdate);
|
||
|
|
||
|
return bSuccess;
|
||
|
}
|
||
|
|
||
|
bool UVoxelDataTools::LoadFromCompressedSave(const AVoxelWorld* World, const FVoxelCompressedWorldSave& Save)
|
||
|
{
|
||
|
return LoadFromCompressedSave(World, Save.Const(), Save.Objects);
|
||
|
}
|
||
|
|
||
|
bool UVoxelDataTools::LoadFromCompressedSave(const AVoxelWorld* World, const FVoxelCompressedWorldSaveImpl& Save, const TArray<FVoxelObjectArchiveEntry>& Objects)
|
||
|
{
|
||
|
CHECK_VOXELWORLD_IS_CREATED();
|
||
|
CHECK_SAVE();
|
||
|
|
||
|
FVoxelUncompressedWorldSaveImpl UncompressedSave;
|
||
|
UVoxelSaveUtilities::DecompressVoxelSave(Save, UncompressedSave);
|
||
|
|
||
|
return LoadFromSave(World, UncompressedSave, Objects);
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void UVoxelDataTools::RoundVoxelsImpl(FVoxelData& Data, const FVoxelIntBox& Bounds)
|
||
|
{
|
||
|
VOXEL_ASYNC_FUNCTION_COUNTER();
|
||
|
|
||
|
int32 NumDirtyLeaves = 0;
|
||
|
uint64 NumVoxels = 0;
|
||
|
{
|
||
|
VOXEL_ASYNC_SCOPE_COUNTER("Record stats");
|
||
|
FVoxelOctreeUtilities::IterateLeavesInBounds(Data.GetOctree(), Bounds, [&](FVoxelDataOctreeLeaf& Leaf)
|
||
|
{
|
||
|
if (Leaf.GetData<FVoxelValue>().IsDirty() && Leaf.GetData<FVoxelValue>().HasAllocation())
|
||
|
{
|
||
|
NumDirtyLeaves++;
|
||
|
NumVoxels += VOXELS_PER_DATA_CHUNK;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
FVoxelScopedSlowTask SlowTask(NumDirtyLeaves, VOXEL_LOCTEXT("Rounding voxels"));
|
||
|
FScopeToolsTimeLogger ToolsLogger(__FUNCTION__, NumVoxels);
|
||
|
|
||
|
FVoxelMutableDataAccelerator OctreeAccelerator(Data, Bounds.Extend(2));
|
||
|
FVoxelOctreeUtilities::IterateLeavesInBounds(Data.GetOctree(), Bounds, [&](FVoxelDataOctreeLeaf& Leaf)
|
||
|
{
|
||
|
if (Leaf.GetData<FVoxelValue>().IsDirty() && Leaf.GetData<FVoxelValue>().HasAllocation())
|
||
|
{
|
||
|
SlowTask.EnterProgressFrame();
|
||
|
|
||
|
const FVoxelIntBox LeafBounds = Leaf.GetBounds();
|
||
|
LeafBounds.Iterate([&](int32 X, int32 Y, int32 Z)
|
||
|
{
|
||
|
const FVoxelCellIndex Index = FVoxelDataOctreeUtilities::IndexFromGlobalCoordinates(LeafBounds.Min, X, Y, Z);
|
||
|
const FVoxelValue& Value = Leaf.GetData<FVoxelValue>().Get(Index);
|
||
|
|
||
|
if (Value.IsTotallyEmpty() || Value.IsTotallyFull()) return;
|
||
|
|
||
|
const bool bEmpty = Value.IsEmpty();
|
||
|
for (int32 OtherX = X - 2; OtherX <= X + 2; OtherX++)
|
||
|
{
|
||
|
for (int32 OtherY = Y - 2; OtherY <= Y + 2; OtherY++)
|
||
|
{
|
||
|
for (int32 OtherZ = Z - 2; OtherZ <= Z + 2; OtherZ++)
|
||
|
{
|
||
|
if (OtherX == X && OtherY == Y && OtherZ == Z) continue;
|
||
|
const auto OtherValue = OctreeAccelerator.GetValue(OtherX, OtherY, OtherZ, 0);
|
||
|
if (OtherValue.IsEmpty() != bEmpty) return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
OctreeAccelerator.SetValue(X, Y, Z, bEmpty ? FVoxelValue::Empty() : FVoxelValue::Full());
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::RoundVoxels(AVoxelWorld* World, FVoxelIntBox InBounds)
|
||
|
{
|
||
|
const FVoxelIntBox Bounds = InBounds.Extend(2);
|
||
|
VOXEL_TOOL_HELPER(Write, DoNotUpdateRender, NO_PREFIX, RoundVoxelsImpl(Data, InBounds));
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::RoundVoxelsAsync(
|
||
|
UObject* WorldContextObject,
|
||
|
FLatentActionInfo LatentInfo,
|
||
|
AVoxelWorld* World,
|
||
|
FVoxelIntBox InBounds,
|
||
|
bool bHideLatentWarnings)
|
||
|
{
|
||
|
const FVoxelIntBox Bounds = InBounds.Extend(2);
|
||
|
VOXEL_TOOL_LATENT_HELPER(Write, DoNotUpdateRender, NO_PREFIX, RoundVoxelsImpl(Data, InBounds));
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void UVoxelDataTools::ClearUnusedMaterialsImpl(FVoxelData& Data, const FVoxelIntBox& Bounds)
|
||
|
{
|
||
|
VOXEL_ASYNC_FUNCTION_COUNTER();
|
||
|
|
||
|
int32 NumDirtyLeaves = 0;
|
||
|
uint64 NumVoxels = 0;
|
||
|
{
|
||
|
VOXEL_ASYNC_SCOPE_COUNTER("Record stats");
|
||
|
FVoxelOctreeUtilities::IterateLeavesInBounds(Data.GetOctree(), Bounds, [&](FVoxelDataOctreeLeaf& Leaf)
|
||
|
{
|
||
|
if (Leaf.GetData<FVoxelMaterial>().IsDirty() && Leaf.GetData<FVoxelMaterial>().HasAllocation())
|
||
|
{
|
||
|
NumDirtyLeaves++;
|
||
|
NumVoxels += VOXELS_PER_DATA_CHUNK;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
FVoxelScopedSlowTask SlowTask(NumDirtyLeaves, VOXEL_LOCTEXT("Clearing unused materials"));
|
||
|
FScopeToolsTimeLogger ToolsLogger(__FUNCTION__, NumVoxels);
|
||
|
|
||
|
FVoxelMutableDataAccelerator OctreeAccelerator(Data, Bounds.Extend(2));
|
||
|
FVoxelOctreeUtilities::IterateLeavesInBounds(Data.GetOctree(), Bounds, [&](FVoxelDataOctreeLeaf& Leaf)
|
||
|
{
|
||
|
if (Leaf.GetData<FVoxelMaterial>().IsDirty() && Leaf.GetData<FVoxelMaterial>().HasAllocation())
|
||
|
{
|
||
|
SlowTask.EnterProgressFrame();
|
||
|
|
||
|
bool bEdited = false;
|
||
|
|
||
|
const FVoxelIntBox LeafBounds = Leaf.GetBounds();
|
||
|
LeafBounds.Iterate([&](int32 X, int32 Y, int32 Z)
|
||
|
{
|
||
|
const FVoxelCellIndex Index = FVoxelDataOctreeUtilities::IndexFromGlobalCoordinates(LeafBounds.Min, X, Y, Z);
|
||
|
const FVoxelMaterial Material = Leaf.GetData<FVoxelMaterial>().Get(Index);
|
||
|
|
||
|
if (Material == FVoxelMaterial::Default()) return;
|
||
|
|
||
|
const FVoxelValue Value = OctreeAccelerator.GetValue(X, Y, Z, 0);
|
||
|
if (!Value.IsEmpty()) // Only not empty voxels materials can affect the surface
|
||
|
{
|
||
|
for (int32 OtherX = X - 1; OtherX <= X + 1; OtherX++)
|
||
|
{
|
||
|
for (int32 OtherY = Y - 1; OtherY <= Y + 1; OtherY++)
|
||
|
{
|
||
|
for (int32 OtherZ = Z - 1; OtherZ <= Z + 1; OtherZ++)
|
||
|
{
|
||
|
if (OtherX == X && OtherY == Y && OtherZ == Z) continue;
|
||
|
const auto OtherValue = OctreeAccelerator.GetValue(OtherX, OtherY, OtherZ, 0);
|
||
|
if (OtherValue.IsEmpty()) return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
OctreeAccelerator.SetMaterial(X, Y, Z, FVoxelMaterial::Default());
|
||
|
bEdited = true;
|
||
|
});
|
||
|
|
||
|
if (bEdited)
|
||
|
{
|
||
|
Leaf.GetData<FVoxelMaterial>().Compress(Data);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::ClearUnusedMaterials(AVoxelWorld* World, FVoxelIntBox InBounds)
|
||
|
{
|
||
|
const FVoxelIntBox Bounds = InBounds.Extend(1);
|
||
|
VOXEL_TOOL_HELPER(Write, DoNotUpdateRender, NO_PREFIX, ClearUnusedMaterialsImpl(Data, InBounds));
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::ClearUnusedMaterialsAsync(
|
||
|
UObject* WorldContextObject,
|
||
|
FLatentActionInfo LatentInfo,
|
||
|
AVoxelWorld* World,
|
||
|
FVoxelIntBox InBounds,
|
||
|
bool bHideLatentWarnings)
|
||
|
{
|
||
|
const FVoxelIntBox Bounds = InBounds.Extend(1);
|
||
|
VOXEL_TOOL_LATENT_HELPER(Write, DoNotUpdateRender, NO_PREFIX, ClearUnusedMaterialsImpl(Data, InBounds));
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void UVoxelDataTools::GetVoxelsValueAndMaterialImpl(
|
||
|
FVoxelData& Data,
|
||
|
TArray<FVoxelValueMaterial>& Voxels,
|
||
|
const FVoxelIntBox& Bounds,
|
||
|
const TArray<FIntVector>& Positions)
|
||
|
{
|
||
|
VOXEL_TOOL_FUNCTION_COUNTER(Positions.Num());
|
||
|
const FVoxelMutableDataAccelerator OctreeAccelerator(Data, Bounds);
|
||
|
for (auto& Position : Positions)
|
||
|
{
|
||
|
if (Data.IsInWorld(Position))
|
||
|
{
|
||
|
FVoxelValueMaterial Voxel;
|
||
|
Voxel.Position = Position;
|
||
|
Voxel.Value = OctreeAccelerator.GetValue(Position, 0).ToFloat();
|
||
|
Voxel.Material = OctreeAccelerator.GetMaterial(Position, 0);
|
||
|
Voxels.Add(Voxel);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::GetVoxelsValueAndMaterial(
|
||
|
TArray<FVoxelValueMaterial>& Voxels,
|
||
|
AVoxelWorld* World,
|
||
|
const TArray<FIntVector>& Positions)
|
||
|
{
|
||
|
VOXEL_FUNCTION_COUNTER();
|
||
|
CHECK_VOXELWORLD_IS_CREATED_VOID();
|
||
|
|
||
|
if (Positions.Num() == 0)
|
||
|
{
|
||
|
Voxels.Reset();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const FVoxelIntBox Bounds(Positions);
|
||
|
CHECK_BOUNDS_ARE_VALID_VOID();
|
||
|
VOXEL_TOOL_HELPER_BODY(Read, DoNotUpdateRender, GetVoxelsValueAndMaterialImpl(Data, Voxels, Bounds, Positions));
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::GetVoxelsValueAndMaterialAsync(
|
||
|
UObject* WorldContextObject,
|
||
|
FLatentActionInfo LatentInfo,
|
||
|
TArray<FVoxelValueMaterial>& Voxels,
|
||
|
AVoxelWorld* World,
|
||
|
const TArray<FIntVector>& Positions,
|
||
|
bool bHideLatentWarnings)
|
||
|
{
|
||
|
VOXEL_FUNCTION_COUNTER();
|
||
|
CHECK_VOXELWORLD_IS_CREATED_VOID();
|
||
|
|
||
|
if (Positions.Num() == 0)
|
||
|
{
|
||
|
Voxels.Reset();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const FVoxelIntBox Bounds(Positions);
|
||
|
VOXEL_TOOL_LATENT_HELPER_WITH_VALUE(Voxels, Read, DoNotUpdateRender, NO_PREFIX, GetVoxelsValueAndMaterialImpl(Data, InVoxels, Bounds, Positions));
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
FVoxelDataMemoryUsageInMB UVoxelDataTools::GetDataMemoryUsageInMB(AVoxelWorld* World)
|
||
|
{
|
||
|
CHECK_VOXELWORLD_IS_CREATED();
|
||
|
VOXEL_FUNCTION_COUNTER();
|
||
|
|
||
|
constexpr double OneMB = double(1 << 20);
|
||
|
|
||
|
auto& Data = World->GetData();
|
||
|
|
||
|
FVoxelDataMemoryUsageInMB MemoryUsage;
|
||
|
|
||
|
MemoryUsage.DirtyValues = Data.GetDirtyMemory().Values.GetValue() / OneMB;
|
||
|
MemoryUsage.DirtyMaterials = Data.GetDirtyMemory().Materials.GetValue() / OneMB;
|
||
|
|
||
|
MemoryUsage.CachedValues = Data.GetCachedMemory().Values.GetValue() / OneMB;
|
||
|
MemoryUsage.CachedMaterials = Data.GetCachedMemory().Materials.GetValue() / OneMB;
|
||
|
|
||
|
return MemoryUsage;
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void UVoxelDataTools::ClearCachedValues(
|
||
|
AVoxelWorld* World,
|
||
|
FVoxelIntBox Bounds)
|
||
|
{
|
||
|
VOXEL_TOOL_HELPER(Write, DoNotUpdateRender, NO_PREFIX, Data.ClearCacheInBounds<FVoxelValue>(Bounds));
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::ClearCachedValuesAsync(
|
||
|
UObject* WorldContextObject,
|
||
|
FLatentActionInfo LatentInfo,
|
||
|
AVoxelWorld* World,
|
||
|
FVoxelIntBox Bounds,
|
||
|
bool bHideLatentWarnings)
|
||
|
{
|
||
|
VOXEL_TOOL_LATENT_HELPER(Write, DoNotUpdateRender, NO_PREFIX, Data.ClearCacheInBounds<FVoxelValue>(Bounds));
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::ClearCachedMaterials(
|
||
|
AVoxelWorld* World,
|
||
|
FVoxelIntBox Bounds)
|
||
|
{
|
||
|
VOXEL_TOOL_HELPER(Write, DoNotUpdateRender, NO_PREFIX, Data.ClearCacheInBounds<FVoxelMaterial>(Bounds));
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::ClearCachedMaterialsAsync(
|
||
|
UObject* WorldContextObject,
|
||
|
FLatentActionInfo LatentInfo,
|
||
|
AVoxelWorld* World,
|
||
|
FVoxelIntBox Bounds,
|
||
|
bool bHideLatentWarnings)
|
||
|
{
|
||
|
VOXEL_TOOL_LATENT_HELPER(Write, DoNotUpdateRender, NO_PREFIX, Data.ClearCacheInBounds<FVoxelMaterial>(Bounds));
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void UVoxelDataTools::CheckForSingleValues(
|
||
|
AVoxelWorld* World,
|
||
|
FVoxelIntBox Bounds)
|
||
|
{
|
||
|
VOXEL_TOOL_HELPER(Write, DoNotUpdateRender, NO_PREFIX, Data.CheckIsSingle<FVoxelValue>(Bounds));
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::CheckForSingleValuesAsync(
|
||
|
UObject* WorldContextObject,
|
||
|
FLatentActionInfo LatentInfo,
|
||
|
AVoxelWorld* World,
|
||
|
FVoxelIntBox Bounds,
|
||
|
bool bHideLatentWarnings)
|
||
|
{
|
||
|
VOXEL_TOOL_LATENT_HELPER(Write, DoNotUpdateRender, NO_PREFIX, Data.CheckIsSingle<FVoxelValue>(Bounds));
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::CheckForSingleMaterials(
|
||
|
AVoxelWorld* World,
|
||
|
FVoxelIntBox Bounds)
|
||
|
{
|
||
|
VOXEL_TOOL_HELPER(Write, DoNotUpdateRender, NO_PREFIX, Data.CheckIsSingle<FVoxelMaterial>(Bounds));
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::CheckForSingleMaterialsAsync(
|
||
|
UObject* WorldContextObject,
|
||
|
FLatentActionInfo LatentInfo,
|
||
|
AVoxelWorld* World,
|
||
|
FVoxelIntBox Bounds,
|
||
|
bool bHideLatentWarnings)
|
||
|
{
|
||
|
VOXEL_TOOL_LATENT_HELPER(Write, DoNotUpdateRender, NO_PREFIX, Data.CheckIsSingle<FVoxelMaterial>(Bounds));
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
template<typename T>
|
||
|
void UVoxelDataTools::CompressIntoHeightmapImpl(FVoxelData& Data, TVoxelHeightmapAssetSamplerWrapper<T>& Wrapper, const bool bCheckAllLeaves)
|
||
|
{
|
||
|
VOXEL_ASYNC_FUNCTION_COUNTER();
|
||
|
|
||
|
ensure(Wrapper.Scale == 1.f);
|
||
|
|
||
|
FVoxelScopedSlowTask SlowTask(2, VOXEL_LOCTEXT("Compressing into heightmap"));
|
||
|
SlowTask.EnterProgressFrame();
|
||
|
|
||
|
TMap<FIntPoint, TMap<int32, FVoxelDataOctreeLeaf*>> LeavesColumns;
|
||
|
{
|
||
|
if (bCheckAllLeaves)
|
||
|
{
|
||
|
// Need to subdivide
|
||
|
FVoxelOctreeUtilities::IterateEntireTree(Data.GetOctree(), [&](FVoxelDataOctreeBase& Tree)
|
||
|
{
|
||
|
if (!Tree.IsLeaf())
|
||
|
{
|
||
|
auto& Parent = Tree.AsParent();
|
||
|
if (!Parent.HasChildren())
|
||
|
{
|
||
|
ensureThreadSafe(Parent.IsLockedForWrite());
|
||
|
Parent.CreateChildren();
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
int32 NumLeaves = 0;
|
||
|
FVoxelOctreeUtilities::IterateAllLeaves(Data.GetOctree(), [&](FVoxelDataOctreeLeaf& Leaf)
|
||
|
{
|
||
|
if (Leaf.GetData<FVoxelValue>().IsDirty() || bCheckAllLeaves)
|
||
|
{
|
||
|
NumLeaves++;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
FVoxelScopedSlowTask LocalSlowTask(NumLeaves, VOXEL_LOCTEXT("Caching data"));
|
||
|
|
||
|
FVoxelOctreeUtilities::IterateAllLeaves(Data.GetOctree(), [&](FVoxelDataOctreeLeaf& Leaf)
|
||
|
{
|
||
|
ensureThreadSafe(Leaf.IsLockedForWrite());
|
||
|
|
||
|
auto& DataHolder = Leaf.GetData<FVoxelValue>();
|
||
|
if (bCheckAllLeaves)
|
||
|
{
|
||
|
if (!DataHolder.HasData())
|
||
|
{
|
||
|
DataHolder.CreateData(Data, [&](FVoxelValue* RESTRICT DataPtr)
|
||
|
{
|
||
|
TVoxelQueryZone<FVoxelValue> QueryZone(Leaf.GetBounds(), DataPtr);
|
||
|
Leaf.GetFromGeneratorAndAssets(*Data.Generator, QueryZone, 0);
|
||
|
});
|
||
|
// Reduce memory usage
|
||
|
DataHolder.Compress(Data);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (!DataHolder.IsDirty())
|
||
|
{
|
||
|
// Flush cache
|
||
|
DataHolder.ClearData(Data);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
LocalSlowTask.EnterProgressFrame();
|
||
|
|
||
|
const FIntVector Min = Leaf.GetMin();
|
||
|
|
||
|
auto& Column = LeavesColumns.FindOrAdd(FIntPoint(Min.X, Min.Y));
|
||
|
check(!Column.Contains(Min.Z));
|
||
|
Column.Add(Min.Z, &Leaf);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
SlowTask.EnterProgressFrame();
|
||
|
FVoxelScopedSlowTask LocalSlowTask(LeavesColumns.Num(), VOXEL_LOCTEXT("Finding heights"));
|
||
|
|
||
|
FVoxelMutableDataAccelerator Accelerator(Data, FVoxelIntBox::Infinite);
|
||
|
for (auto& ColumnsIt : LeavesColumns)
|
||
|
{
|
||
|
LocalSlowTask.EnterProgressFrame();
|
||
|
|
||
|
const FIntPoint LeafMinXY = ColumnsIt.Key;
|
||
|
auto& Leaves = ColumnsIt.Value;
|
||
|
|
||
|
int32 MinLeafMinZ = MAX_int32;
|
||
|
int32 MaxLeafMinZ = MIN_int32;
|
||
|
for (auto& LeavesIt : Leaves)
|
||
|
{
|
||
|
MinLeafMinZ = FMath::Min(LeavesIt.Key, MinLeafMinZ);
|
||
|
MaxLeafMinZ = FMath::Max(LeavesIt.Key, MaxLeafMinZ);
|
||
|
}
|
||
|
check(MinLeafMinZ != MAX_int32);
|
||
|
check(MaxLeafMinZ != MIN_int32);
|
||
|
|
||
|
const auto GetHeightmapPosition = [&](int32 X, int32 Y)
|
||
|
{
|
||
|
// Note: HeightmapAssets are offset by (-Wrapper.GetWidth() / 2, -Wrapper.GetHeight() / 2)
|
||
|
return FIntPoint(LeafMinXY.X + X + Wrapper.GetWidth() / 2, LeafMinXY.Y + Y + Wrapper.GetHeight() / 2);
|
||
|
};
|
||
|
const auto IsInBounds = [&](const FIntPoint& HeightmapPosition)
|
||
|
{
|
||
|
return
|
||
|
HeightmapPosition.X >= 0 &&
|
||
|
HeightmapPosition.Y >= 0 &&
|
||
|
HeightmapPosition.X < Wrapper.GetWidth() &&
|
||
|
HeightmapPosition.Y < Wrapper.GetHeight();
|
||
|
};
|
||
|
|
||
|
TStaticArray<float, DATA_CHUNK_SIZE * DATA_CHUNK_SIZE> NewHeights;
|
||
|
for (int32 X = 0; X < DATA_CHUNK_SIZE; X++)
|
||
|
{
|
||
|
for (int32 Y = 0; Y < DATA_CHUNK_SIZE; Y++)
|
||
|
{
|
||
|
const FIntPoint HeightmapPosition = GetHeightmapPosition(X, Y);
|
||
|
if (!IsInBounds(HeightmapPosition)) continue;
|
||
|
|
||
|
const auto GetNewHeight = [&]()
|
||
|
{
|
||
|
const float HeightmapHeight = Wrapper.GetHeight(HeightmapPosition.X, HeightmapPosition.Y, EVoxelSamplerMode::Clamp);
|
||
|
if (HeightmapHeight >= MaxLeafMinZ + DATA_CHUNK_SIZE)
|
||
|
{
|
||
|
// Heightmap is above all leaves, can't do anything
|
||
|
return HeightmapHeight;
|
||
|
}
|
||
|
|
||
|
// Go down chunk by chunk until we find the height
|
||
|
for (int32 LeafMinZ = MaxLeafMinZ; LeafMinZ >= MinLeafMinZ; LeafMinZ -= DATA_CHUNK_SIZE)
|
||
|
{
|
||
|
auto* Leaf = Leaves.FindRef(LeafMinZ);
|
||
|
if (!Leaf)
|
||
|
{
|
||
|
checkVoxelSlow(HeightmapHeight <= LeafMinZ + DATA_CHUNK_SIZE);
|
||
|
if (LeafMinZ < HeightmapHeight)
|
||
|
{
|
||
|
// HeightmapHeight above all remaining leaves, can't do anything
|
||
|
return HeightmapHeight;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Go down until we find the height
|
||
|
auto& DataHolder = Leaf->GetData<FVoxelValue>();
|
||
|
if (DataHolder.IsSingleValue() && !DataHolder.GetSingleValue().IsEmpty())
|
||
|
{
|
||
|
// Fast path
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
for (int32 Z = DATA_CHUNK_SIZE - 1; Z >= 0; Z--)
|
||
|
{
|
||
|
const FVoxelValue Value = DataHolder.Get(FVoxelDataOctreeUtilities::IndexFromCoordinates(X, Y, Z));
|
||
|
if (!Value.IsEmpty())
|
||
|
{
|
||
|
FVoxelValue ValueAbove;
|
||
|
if (Z + 1 < DATA_CHUNK_SIZE)
|
||
|
{
|
||
|
ValueAbove = DataHolder.Get(FVoxelDataOctreeUtilities::IndexFromCoordinates(X, Y, Z + 1));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ValueAbove = Accelerator.GetValue(Leaf->GetMin() + FIntVector(X, Y, Z + 1), 0);
|
||
|
}
|
||
|
// Note: not true on world upper bound
|
||
|
ensure(ValueAbove.IsEmpty());
|
||
|
|
||
|
const float NewHeight = LeafMinZ + Z + FVoxelUtilities::GetAbsDistanceFromDensities(Value.ToFloat(), ValueAbove.ToFloat());
|
||
|
const float OldHeight = HeightmapHeight;
|
||
|
|
||
|
// Mark leaves that will have their value changed by the new height as dirty so that they don't change
|
||
|
const int32 StartLeafMinZ = FMath::FloorToInt(FMath::Min(NewHeight, OldHeight) / DATA_CHUNK_SIZE) * DATA_CHUNK_SIZE;
|
||
|
const int32 EndLeafMinZ = FMath::CeilToInt(FMath::Max(NewHeight, OldHeight) / DATA_CHUNK_SIZE) * DATA_CHUNK_SIZE;
|
||
|
for (int32 ItLeafMinZ = StartLeafMinZ; ItLeafMinZ <= EndLeafMinZ; ItLeafMinZ += DATA_CHUNK_SIZE)
|
||
|
{
|
||
|
if (Leaves.Contains(ItLeafMinZ)) continue; // Values already correctly stored
|
||
|
|
||
|
// Leaf is defaulting to generator value, but this value is going to change
|
||
|
// Mark the leaf as dirty
|
||
|
|
||
|
const FIntVector LeafPosition = FIntVector(LeafMinXY.X, LeafMinXY.Y, ItLeafMinZ) + DATA_CHUNK_SIZE / 2;
|
||
|
if (!Data.IsInWorld(LeafPosition)) continue;
|
||
|
|
||
|
auto* ItLeaf = FVoxelOctreeUtilities::GetLeaf<EVoxelOctreeLeafQuery::CreateIfNull>(Data.GetOctree(), LeafPosition);
|
||
|
check(ItLeaf);
|
||
|
check(!ItLeaf->GetData<FVoxelValue>().HasData());
|
||
|
|
||
|
ItLeaf->InitForEdit<FVoxelValue>(Data);
|
||
|
ItLeaf->GetData<FVoxelValue>().SetIsDirty(true, Data);
|
||
|
|
||
|
// & Add it to the map
|
||
|
Leaves.Add(ItLeafMinZ, ItLeaf);
|
||
|
|
||
|
// Update min/max as well
|
||
|
MinLeafMinZ = FMath::Min(ItLeafMinZ, MinLeafMinZ);
|
||
|
MaxLeafMinZ = FMath::Max(ItLeafMinZ, MaxLeafMinZ);
|
||
|
}
|
||
|
return NewHeight;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Not found anything, default to old height
|
||
|
return HeightmapHeight;
|
||
|
};
|
||
|
NewHeights[X + DATA_CHUNK_SIZE * Y] = GetNewHeight();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Write the heights
|
||
|
for (int32 X = 0; X < DATA_CHUNK_SIZE; X++)
|
||
|
{
|
||
|
for (int32 Y = 0; Y < DATA_CHUNK_SIZE; Y++)
|
||
|
{
|
||
|
const FIntPoint HeightmapPosition = GetHeightmapPosition(X, Y);
|
||
|
if (!IsInBounds(HeightmapPosition)) continue;
|
||
|
|
||
|
Wrapper.SetHeight(HeightmapPosition.X, HeightmapPosition.Y, NewHeights[X + DATA_CHUNK_SIZE * Y]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template VOXEL_API void UVoxelDataTools::CompressIntoHeightmapImpl<uint16>(FVoxelData& Data, TVoxelHeightmapAssetSamplerWrapper<uint16>& Wrapper, bool bCheckAllLeaves);
|
||
|
template VOXEL_API void UVoxelDataTools::CompressIntoHeightmapImpl<float>(FVoxelData& Data, TVoxelHeightmapAssetSamplerWrapper<float>& Wrapper, bool bCheckAllLeaves);
|
||
|
|
||
|
void UVoxelDataTools::CompressIntoHeightmap(
|
||
|
AVoxelWorld* World,
|
||
|
UVoxelHeightmapAsset* HeightmapAsset,
|
||
|
bool bHeightmapAssetMatchesWorld)
|
||
|
{
|
||
|
VOXEL_FUNCTION_COUNTER();
|
||
|
CHECK_VOXELWORLD_IS_CREATED_VOID();
|
||
|
|
||
|
auto& Data = World->GetData();
|
||
|
FVoxelWriteScopeLock Lock(Data, FVoxelIntBox::Infinite, "");
|
||
|
|
||
|
bool bCheckAllLeaves = false;
|
||
|
if (!HeightmapAsset)
|
||
|
{
|
||
|
if (World->Generator.IsObject()) // We don't want to edit the default object otherwise
|
||
|
{
|
||
|
HeightmapAsset = Cast<UVoxelHeightmapAsset>(World->Generator.Object);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (!bHeightmapAssetMatchesWorld)
|
||
|
{
|
||
|
bCheckAllLeaves = true;
|
||
|
|
||
|
const int32 Size = FVoxelUtilities::GetSizeFromDepth<DATA_CHUNK_SIZE>(Data.Depth);
|
||
|
if (Size > 20000)
|
||
|
{
|
||
|
FVoxelMessages::Error(FUNCTION_ERROR("Heightmap size would be too large!"));
|
||
|
return;
|
||
|
}
|
||
|
if (auto* Heightmap = Cast<UVoxelHeightmapAssetUINT16>(HeightmapAsset))
|
||
|
{
|
||
|
HeightmapAsset->HeightOffset = -Size / 2; // Height = 0 should be bottom of the world since all heights are positive
|
||
|
HeightmapAsset->HeightScale = float(Size) / MAX_uint16; // Distribute the heights
|
||
|
HeightmapAsset->AdditionalThickness = Size; // Just to be safe
|
||
|
|
||
|
// Init the heightmap data
|
||
|
Heightmap->GetData().SetSize(Size, Size, false, {});
|
||
|
Heightmap->GetData().SetAllHeightsTo(0);
|
||
|
}
|
||
|
if (auto* Heightmap = Cast<UVoxelHeightmapAssetFloat>(HeightmapAsset))
|
||
|
{
|
||
|
HeightmapAsset->HeightOffset = 0; // Heights can be negative too here
|
||
|
HeightmapAsset->HeightScale = 1.f; // No need to distribute
|
||
|
HeightmapAsset->AdditionalThickness = Size; // Fill below
|
||
|
|
||
|
// Init the heightmap data
|
||
|
Heightmap->GetData().SetSize(Size, Size, false, {});
|
||
|
Heightmap->GetData().SetAllHeightsTo(0);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (auto* UINT16Heightmap = Cast<UVoxelHeightmapAssetUINT16>(HeightmapAsset))
|
||
|
{
|
||
|
TVoxelHeightmapAssetSamplerWrapper<uint16> Wrapper(UINT16Heightmap);
|
||
|
CompressIntoHeightmapImpl(Data, Wrapper, bCheckAllLeaves);
|
||
|
UINT16Heightmap->Save();
|
||
|
}
|
||
|
else if (auto* FloatHeightmap = Cast<UVoxelHeightmapAssetFloat>(HeightmapAsset))
|
||
|
{
|
||
|
TVoxelHeightmapAssetSamplerWrapper<float> Wrapper(FloatHeightmap);
|
||
|
CompressIntoHeightmapImpl(Data, Wrapper, bCheckAllLeaves);
|
||
|
FloatHeightmap->Save();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
FVoxelMessages::Error(FUNCTION_ERROR("Generator is not an heightmap!"));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void UVoxelDataTools::RoundToGeneratorImpl(FVoxelData& Data, const FVoxelIntBox& Bounds, bool bPreserveNormals)
|
||
|
{
|
||
|
VOXEL_ASYNC_FUNCTION_COUNTER();
|
||
|
|
||
|
int32 NumDirtyLeaves = 0;
|
||
|
FVoxelOctreeUtilities::IterateLeavesInBounds(Data.GetOctree(), Bounds, [&](FVoxelDataOctreeLeaf& Leaf)
|
||
|
{
|
||
|
if (Leaf.GetData<FVoxelValue>().IsDirty())
|
||
|
{
|
||
|
NumDirtyLeaves++;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
FVoxelScopedSlowTask SlowTask(NumDirtyLeaves, VOXEL_LOCTEXT("Round To Generator"));
|
||
|
|
||
|
FVoxelMutableDataAccelerator OctreeAccelerator(Data, Bounds.Extend(2));
|
||
|
FVoxelOctreeUtilities::IterateLeavesInBounds(Data.GetOctree(), Bounds, [&](FVoxelDataOctreeLeaf& Leaf)
|
||
|
{
|
||
|
if (!Leaf.GetData<FVoxelValue>().IsDirty())
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
SlowTask.EnterProgressFrame();
|
||
|
|
||
|
// Do not try to round if single value
|
||
|
if (!Leaf.GetData<FVoxelValue>().IsSingleValue())
|
||
|
{
|
||
|
const FVoxelIntBox LeafBounds = Leaf.GetBounds();
|
||
|
LeafBounds.Iterate([&](int32 X, int32 Y, int32 Z)
|
||
|
{
|
||
|
const FVoxelCellIndex Index = FVoxelDataOctreeUtilities::IndexFromGlobalCoordinates(LeafBounds.Min, X, Y, Z);
|
||
|
const FVoxelValue Value = Leaf.GetData<FVoxelValue>().Get(Index);
|
||
|
const FVoxelValue GeneratorValue = Data.Generator->Get<FVoxelValue>(X, Y, Z, 0, FVoxelItemStack::Empty);
|
||
|
|
||
|
if (Value == GeneratorValue) return;
|
||
|
if (Value.IsEmpty() != GeneratorValue.IsEmpty()) return;
|
||
|
|
||
|
const auto CheckNeighbor = [&](int32 DX, int32 DY, int32 DZ)
|
||
|
{
|
||
|
const FVoxelValue OtherValue = OctreeAccelerator.GetValue(X + DX, Y + DY, Z + DZ, 0);
|
||
|
const FVoxelValue OtherGeneratorValue = Data.Generator->Get<FVoxelValue>(X + DX, Y + DY, Z + DZ, 0, FVoxelItemStack::Empty);
|
||
|
return OtherValue.IsEmpty() == OtherGeneratorValue.IsEmpty();
|
||
|
};
|
||
|
|
||
|
if (bPreserveNormals)
|
||
|
{
|
||
|
for (int32 DX = -1; DX <= 1; DX++)
|
||
|
{
|
||
|
for (int32 DY = -1; DY <= 1; DY++)
|
||
|
{
|
||
|
for (int32 DZ = -1; DZ <= 1; DZ++)
|
||
|
{
|
||
|
if (DX == 0 && DY == 0 && DZ == 0) continue;
|
||
|
if (!CheckNeighbor(DX, DY, DZ)) return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (!CheckNeighbor(-1, 0, 0)) return;
|
||
|
if (!CheckNeighbor(+1, 0, 0)) return;
|
||
|
if (!CheckNeighbor(0, -1, 0)) return;
|
||
|
if (!CheckNeighbor(0, +1, 0)) return;
|
||
|
if (!CheckNeighbor(0, 0, -1)) return;
|
||
|
if (!CheckNeighbor(0, 0, +1)) return;
|
||
|
}
|
||
|
|
||
|
OctreeAccelerator.SetValue(X, Y, Z, GeneratorValue);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// But always check this, as else we get a lot of space used by single values!
|
||
|
FVoxelDataUtilities::CheckIfSameAsGenerator<FVoxelValue>(Data, Leaf);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::RoundToGenerator(AVoxelWorld* World, FVoxelIntBox Bounds, bool bPreserveNormals)
|
||
|
{
|
||
|
VOXEL_TOOL_HELPER(Write, DoNotUpdateRender, NO_PREFIX, RoundToGeneratorImpl(Data, Bounds, bPreserveNormals));
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::RoundToGeneratorAsync(
|
||
|
UObject* WorldContextObject,
|
||
|
FLatentActionInfo LatentInfo,
|
||
|
AVoxelWorld* World,
|
||
|
FVoxelIntBox Bounds,
|
||
|
bool bPreserveNormals,
|
||
|
bool bHideLatentWarnings)
|
||
|
{
|
||
|
VOXEL_TOOL_LATENT_HELPER(Write, DoNotUpdateRender, NO_PREFIX, RoundToGeneratorImpl(Data, Bounds, bPreserveNormals));
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void UVoxelDataTools::CheckIfSameAsGeneratorImpl(FVoxelData& Data, const FVoxelIntBox& Bounds)
|
||
|
{
|
||
|
VOXEL_ASYNC_FUNCTION_COUNTER();
|
||
|
|
||
|
int32 NumLeaves = 0;
|
||
|
FVoxelOctreeUtilities::IterateLeavesInBounds(Data.GetOctree(), Bounds, [&](FVoxelDataOctreeLeaf& Leaf)
|
||
|
{
|
||
|
NumLeaves++;
|
||
|
});
|
||
|
|
||
|
FVoxelScopedSlowTask SlowTask(NumLeaves, VOXEL_LOCTEXT("Check If Same As Generator"));
|
||
|
FVoxelOctreeUtilities::IterateLeavesInBounds(Data.GetOctree(), Bounds, [&](FVoxelDataOctreeLeaf& Leaf)
|
||
|
{
|
||
|
SlowTask.EnterProgressFrame();
|
||
|
ensureThreadSafe(Leaf.IsLockedForWrite());
|
||
|
if (Leaf.GetData<FVoxelValue>().IsDirty())
|
||
|
{
|
||
|
FVoxelDataUtilities::CheckIfSameAsGenerator<FVoxelValue>(Data, Leaf);
|
||
|
}
|
||
|
if (Leaf.GetData<FVoxelMaterial>().IsDirty())
|
||
|
{
|
||
|
FVoxelDataUtilities::CheckIfSameAsGenerator<FVoxelMaterial>(Data, Leaf);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::CheckIfSameAsGenerator(AVoxelWorld* World, FVoxelIntBox Bounds)
|
||
|
{
|
||
|
VOXEL_TOOL_HELPER(Write, DoNotUpdateRender, NO_PREFIX, CheckIfSameAsGeneratorImpl(Data, Bounds));
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::CheckIfSameAsGeneratorAsync(
|
||
|
UObject* WorldContextObject,
|
||
|
FLatentActionInfo LatentInfo,
|
||
|
AVoxelWorld* World,
|
||
|
FVoxelIntBox Bounds,
|
||
|
bool bHideLatentWarnings)
|
||
|
{
|
||
|
VOXEL_TOOL_LATENT_HELPER(Write, DoNotUpdateRender, NO_PREFIX, CheckIfSameAsGeneratorImpl(Data, Bounds));
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
template<typename T>
|
||
|
void UVoxelDataTools::SetBoxAsDirtyImpl(FVoxelData& Data, const FVoxelIntBox& Bounds, bool bCompress)
|
||
|
{
|
||
|
VOXEL_ASYNC_FUNCTION_COUNTER();
|
||
|
|
||
|
const int32 Count = Bounds.Overlap(Data.WorldBounds).MakeMultipleOfRoundUp(DATA_CHUNK_SIZE).Count() / int64(VOXELS_PER_DATA_CHUNK);
|
||
|
FVoxelScopedSlowTask SlowTask(Count, VOXEL_LOCTEXT("Set Box as Dirty"));
|
||
|
FVoxelOctreeUtilities::IterateTreeInBounds(Data.GetOctree(), Bounds, [&](FVoxelDataOctreeBase& Tree)
|
||
|
{
|
||
|
if (Tree.IsLeaf())
|
||
|
{
|
||
|
SlowTask.EnterProgressFrame();
|
||
|
|
||
|
auto& Leaf = Tree.AsLeaf();
|
||
|
ensureThreadSafe(Leaf.IsLockedForWrite());
|
||
|
|
||
|
Leaf.InitForEdit<T>(Data);
|
||
|
if (bCompress)
|
||
|
{
|
||
|
// Else memory usage explodes
|
||
|
Leaf.GetData<T>().Compress(Data);
|
||
|
}
|
||
|
Leaf.GetData<T>().SetIsDirty(true, Data);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
auto& Parent = Tree.AsParent();
|
||
|
if (!Parent.HasChildren())
|
||
|
{
|
||
|
ensureThreadSafe(Parent.IsLockedForWrite());
|
||
|
Parent.CreateChildren();
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
template VOXEL_API void UVoxelDataTools::SetBoxAsDirtyImpl<FVoxelValue>(FVoxelData&, const FVoxelIntBox&, bool);
|
||
|
template VOXEL_API void UVoxelDataTools::SetBoxAsDirtyImpl<FVoxelMaterial>(FVoxelData&, const FVoxelIntBox&, bool);
|
||
|
|
||
|
void UVoxelDataTools::SetBoxAsDirty(
|
||
|
AVoxelWorld* World,
|
||
|
FVoxelIntBox Bounds,
|
||
|
bool bDirtyValues,
|
||
|
bool bDirtyMaterials)
|
||
|
{
|
||
|
VOXEL_TOOL_HELPER(Write, DoNotUpdateRender, NO_PREFIX,
|
||
|
if (bDirtyValues)
|
||
|
{
|
||
|
SetBoxAsDirtyImpl<FVoxelValue>(Data, Bounds, true);
|
||
|
}
|
||
|
if (bDirtyMaterials)
|
||
|
{
|
||
|
SetBoxAsDirtyImpl<FVoxelMaterial>(Data, Bounds, true);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::SetBoxAsDirtyAsync(
|
||
|
UObject* WorldContextObject,
|
||
|
FLatentActionInfo LatentInfo,
|
||
|
AVoxelWorld* World,
|
||
|
FVoxelIntBox Bounds,
|
||
|
bool bDirtyValues,
|
||
|
bool bDirtyMaterials,
|
||
|
bool bHideLatentWarnings)
|
||
|
{
|
||
|
VOXEL_TOOL_LATENT_HELPER(Write, DoNotUpdateRender, NO_PREFIX,
|
||
|
if (bDirtyValues)
|
||
|
{
|
||
|
SetBoxAsDirtyImpl<FVoxelValue>(Data, Bounds, true);
|
||
|
}
|
||
|
if (bDirtyMaterials)
|
||
|
{
|
||
|
SetBoxAsDirtyImpl<FVoxelMaterial>(Data, Bounds, true);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
#define FINDCLOSESTNONEMPTYVOXEL_PREFIX \
|
||
|
const FVoxelVector Position = FVoxelToolHelpers::GetRealTemplate(World, InPosition, bConvertToVoxelSpace); \
|
||
|
const FVoxelIntBox Bounds = FVoxelIntBox(Position, Position + 1);
|
||
|
|
||
|
FVoxelFindClosestNonEmptyVoxelResult UVoxelDataTools::FindClosestNonEmptyVoxelImpl(
|
||
|
FVoxelData& Data,
|
||
|
const FVoxelVector& Position,
|
||
|
bool bReadMaterial)
|
||
|
{
|
||
|
FVoxelFindClosestNonEmptyVoxelResult Result;
|
||
|
|
||
|
const FVoxelConstDataAccelerator Accelerator(Data);
|
||
|
|
||
|
v_flt Distance = MAX_vflt;
|
||
|
for (auto& Neighbor : FVoxelUtilities::GetNeighbors(Position))
|
||
|
{
|
||
|
const FVoxelValue Value = Accelerator.GetValue(Neighbor, 0);
|
||
|
if (!Value.IsEmpty())
|
||
|
{
|
||
|
const v_flt PointDistance = (FVoxelVector(Neighbor) - Position).SizeSquared();
|
||
|
if (PointDistance < Distance)
|
||
|
{
|
||
|
Distance = PointDistance;
|
||
|
Result.bSuccess = true;
|
||
|
Result.Position = Neighbor;
|
||
|
Result.Value = Value.ToFloat();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (Result.bSuccess && bReadMaterial)
|
||
|
{
|
||
|
Result.Material = Accelerator.GetMaterial(Result.Position, 0);
|
||
|
}
|
||
|
|
||
|
return Result;
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::FindClosestNonEmptyVoxel(
|
||
|
FVoxelFindClosestNonEmptyVoxelResult& Result,
|
||
|
AVoxelWorld* World,
|
||
|
FVector InPosition,
|
||
|
bool bReadMaterial,
|
||
|
bool bConvertToVoxelSpace)
|
||
|
{
|
||
|
VOXEL_TOOL_HELPER(Read, DoNotUpdateRender, FINDCLOSESTNONEMPTYVOXEL_PREFIX, Result = FindClosestNonEmptyVoxelImpl(Data, Position, bReadMaterial));
|
||
|
}
|
||
|
|
||
|
void UVoxelDataTools::FindClosestNonEmptyVoxelAsync(
|
||
|
UObject* WorldContextObject,
|
||
|
FLatentActionInfo LatentInfo,
|
||
|
FVoxelFindClosestNonEmptyVoxelResult& Result,
|
||
|
AVoxelWorld* World,
|
||
|
FVector InPosition,
|
||
|
bool bReadMaterial,
|
||
|
bool bConvertToVoxelSpace,
|
||
|
bool bHideLatentWarnings)
|
||
|
{
|
||
|
VOXEL_TOOL_LATENT_HELPER_WITH_VALUE(Result, Read, DoNotUpdateRender, FINDCLOSESTNONEMPTYVOXEL_PREFIX, InResult = FindClosestNonEmptyVoxelImpl(Data, Position, bReadMaterial));
|
||
|
}
|