532 lines
No EOL
14 KiB
C++
532 lines
No EOL
14 KiB
C++
// Copyright 2020 Phyronnaz
|
|
|
|
#pragma once
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "VoxelData/VoxelData.h"
|
|
#include "VoxelData/VoxelDataOctree.h"
|
|
#include "VoxelUtilities/VoxelOctreeUtilities.h"
|
|
#include "VoxelGenerators/VoxelGeneratorInstance.inl"
|
|
|
|
#include "Async/ParallelFor.h"
|
|
|
|
FORCEINLINE FVoxelDataOctreeBase& FVoxelData::GetOctree() const
|
|
{
|
|
return *Octree;
|
|
}
|
|
|
|
template<typename T>
|
|
void FVoxelData::CacheBounds(const FVoxelIntBox& Bounds, bool bMultiThreaded)
|
|
{
|
|
VOXEL_ASYNC_FUNCTION_COUNTER();
|
|
|
|
TArray<FVoxelDataOctreeLeaf*> Leaves;
|
|
FVoxelOctreeUtilities::IterateTreeInBounds(GetOctree(), Bounds, [&](FVoxelDataOctreeBase& Chunk)
|
|
{
|
|
if (Chunk.IsLeaf())
|
|
{
|
|
ensureThreadSafe(Chunk.IsLockedForWrite());
|
|
|
|
auto& Leaf = Chunk.AsLeaf();
|
|
auto& DataHolder = Leaf.GetData<T>();
|
|
if (!DataHolder.HasData())
|
|
{
|
|
Leaves.Add(&Leaf);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto& Parent = Chunk.AsParent();
|
|
if (!Parent.HasChildren())
|
|
{
|
|
ensureThreadSafe(Chunk.IsLockedForWrite());
|
|
Parent.CreateChildren();
|
|
}
|
|
}
|
|
});
|
|
|
|
ParallelFor(Leaves.Num(), [&](int32 Index)
|
|
{
|
|
FVoxelDataOctreeLeaf& Leaf = *Leaves[Index];
|
|
|
|
auto& DataHolder = Leaf.GetData<T>();
|
|
DataHolder.CreateData(*this, [&](T* RESTRICT DataPtr)
|
|
{
|
|
TVoxelQueryZone<T> QueryZone(Leaf.GetBounds(), DataPtr);
|
|
Leaf.GetFromGeneratorAndAssets(*Generator, QueryZone, 0);
|
|
});
|
|
|
|
}, !bMultiThreaded);
|
|
}
|
|
|
|
template<typename T>
|
|
void FVoxelData::ClearCacheInBounds(const FVoxelIntBox& Bounds)
|
|
{
|
|
VOXEL_ASYNC_FUNCTION_COUNTER();
|
|
|
|
FVoxelOctreeUtilities::IterateLeavesInBounds(GetOctree(), Bounds, [&](FVoxelDataOctreeLeaf& Leaf)
|
|
{
|
|
ensureThreadSafe(Leaf.IsLockedForWrite());
|
|
|
|
auto& DataHolder = Leaf.GetData<T>();
|
|
if (DataHolder.HasAllocation() && !DataHolder.IsDirty())
|
|
{
|
|
DataHolder.ClearData(*this);
|
|
}
|
|
});
|
|
}
|
|
|
|
template<typename T>
|
|
TArray<T> FVoxelData::Get(const FVoxelIntBox& Bounds) const
|
|
{
|
|
VOXEL_ASYNC_FUNCTION_COUNTER();
|
|
|
|
TArray<T> Result;
|
|
Result.Empty(Bounds.Count());
|
|
Result.SetNumUninitialized(Bounds.Count());
|
|
TVoxelQueryZone<T> QueryZone(Bounds, Result);
|
|
Get(QueryZone, 0);
|
|
return Result;
|
|
}
|
|
|
|
template<typename T>
|
|
TArray<T> FVoxelData::ParallelGet(const FVoxelIntBox& Bounds, bool bForceSingleThread) const
|
|
{
|
|
VOXEL_ASYNC_FUNCTION_COUNTER();
|
|
|
|
TArray<T> Result;
|
|
Result.SetNumUninitialized(Bounds.Count());
|
|
TVoxelQueryZone<T> QueryZone(Bounds, Result);
|
|
|
|
Bounds.ParallelSplit([&](const FVoxelIntBox& LocalBounds)
|
|
{
|
|
auto LocalQueryZone = QueryZone.ShrinkTo(LocalBounds);
|
|
Get(LocalQueryZone, 0);
|
|
}, bForceSingleThread);
|
|
|
|
return Result;
|
|
}
|
|
|
|
FORCEINLINE bool FVoxelData::IsEmpty(const FVoxelIntBox& Bounds, int32 LOD) const
|
|
{
|
|
const auto Range = GetValueRange(Bounds, LOD);
|
|
return Range.Min.IsEmpty() == Range.Max.IsEmpty();
|
|
}
|
|
|
|
template<typename T>
|
|
FORCEINLINE T FVoxelData::GetCustomOutput(T DefaultValue, FName Name, v_flt X, v_flt Y, v_flt Z, int32 LOD) const
|
|
{
|
|
// Clamp to world, to avoid un-editable border
|
|
ClampToWorld(X, Y, Z);
|
|
|
|
auto& Node = FVoxelOctreeUtilities::GetBottomNode(GetOctree(), int32(X), int32(Y), int32(Z));
|
|
return Node.GetCustomOutput<T>(*Generator, DefaultValue, Name, X, Y, Z, LOD);
|
|
}
|
|
|
|
template<typename ... TArgs, typename F>
|
|
void FVoxelData::Set(const FVoxelIntBox& Bounds, F Apply)
|
|
{
|
|
if (!ensure(Bounds.IsValid())) return;
|
|
FVoxelOctreeUtilities::IterateTreeInBounds(GetOctree(), Bounds, [&](FVoxelDataOctreeBase& Tree)
|
|
{
|
|
if (Tree.IsLeaf())
|
|
{
|
|
auto& Leaf = Tree.AsLeaf();
|
|
ensureThreadSafe(Leaf.IsLockedForWrite());
|
|
FVoxelDataOctreeSetter::Set<TArgs...>(*this, Leaf, [&](auto Lambda)
|
|
{
|
|
Leaf.GetBounds().Overlap(Bounds).Iterate(Lambda);
|
|
}, Apply);
|
|
}
|
|
else
|
|
{
|
|
auto& Parent = Tree.AsParent();
|
|
if (!Parent.HasChildren())
|
|
{
|
|
ensureThreadSafe(Parent.IsLockedForWrite());
|
|
Parent.CreateChildren();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
template<typename ... TArgs, typename F>
|
|
void FVoxelData::ParallelSet(const FVoxelIntBox& Bounds, F Apply, bool bForceSingleThread)
|
|
{
|
|
if (!ensure(Bounds.IsValid())) return;
|
|
|
|
TArray<FVoxelDataOctreeLeaf*> Leaves;
|
|
FVoxelOctreeUtilities::IterateTreeInBounds(GetOctree(), Bounds, [&](FVoxelDataOctreeBase& Tree)
|
|
{
|
|
if (Tree.IsLeaf())
|
|
{
|
|
auto& Leaf = Tree.AsLeaf();
|
|
ensureThreadSafe(Leaf.IsLockedForWrite());
|
|
Leaves.Add(&Leaf);
|
|
}
|
|
else
|
|
{
|
|
auto& Parent = Tree.AsParent();
|
|
if (!Parent.HasChildren())
|
|
{
|
|
ensureThreadSafe(Parent.IsLockedForWrite());
|
|
Parent.CreateChildren();
|
|
}
|
|
}
|
|
});
|
|
|
|
ParallelFor(Leaves.Num(), [&](int32 Index)
|
|
{
|
|
auto& Leaf = *Leaves[Index];
|
|
FVoxelDataOctreeSetter::Set<TArgs...>(*this, Leaf, [&](auto Lambda)
|
|
{
|
|
Leaf.GetBounds().Overlap(Bounds).Iterate(Lambda);
|
|
}, Apply);
|
|
}, bForceSingleThread);
|
|
}
|
|
|
|
template<typename T>
|
|
FORCEINLINE void FVoxelData::Set(int32 X, int32 Y, int32 Z, const T& Value)
|
|
{
|
|
if (IsInWorld(X, Y, Z))
|
|
{
|
|
auto Iterate = [&](auto Lambda) { Lambda(X, Y, Z); };
|
|
auto Apply = [&](int32, int32, int32, T& InValue) { InValue = Value; };
|
|
auto& Leaf = *FVoxelOctreeUtilities::GetLeaf<EVoxelOctreeLeafQuery::CreateIfNull>(GetOctree(), X, Y, Z);
|
|
FVoxelDataOctreeSetter::Set<T>(*this, Leaf, Iterate, Apply);
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
FORCEINLINE T FVoxelData::Get(int32 X, int32 Y, int32 Z, int32 LOD) const
|
|
{
|
|
// Clamp to world, to avoid un-editable border
|
|
ClampToWorld(X, Y, Z);
|
|
|
|
auto& Node = FVoxelOctreeUtilities::GetBottomNode(GetOctree(), int32(X), int32(Y), int32(Z));
|
|
return Node.Get<T>(*Generator, X, Y, Z, LOD);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace FVoxelDataItemsUtilities
|
|
{
|
|
// This will NOT add the item to the item holder
|
|
template<typename T>
|
|
void AddItemToLeafData(
|
|
const FVoxelData& Data,
|
|
FVoxelDataOctreeLeaf& Leaf,
|
|
const T& Item)
|
|
{
|
|
}
|
|
// This will NOT remove the item from the item holder, but will assume it has already been removed
|
|
template<typename T>
|
|
void RemoveItemFromLeafData(
|
|
const FVoxelData& Data,
|
|
FVoxelDataOctreeLeaf& Leaf,
|
|
const T& Item)
|
|
{
|
|
if (!TIsSame<T, FVoxelAssetItem>::Value && !TIsSame<T, FVoxelDataItem>::Value)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Flush cache if possible
|
|
if (!Leaf.Values.IsDirty())
|
|
{
|
|
Leaf.Values.ClearData(Data);
|
|
}
|
|
if (!Leaf.Materials.IsDirty())
|
|
{
|
|
Leaf.Materials.ClearData(Data);
|
|
}
|
|
|
|
// If something is still dirty, remove manually
|
|
if (Leaf.Values.IsDirty())
|
|
{
|
|
FVoxelDataUtilities::RemoveItemFromLeafData<FVoxelValue>(Data, Leaf, Item);
|
|
}
|
|
if (Leaf.Materials.IsDirty())
|
|
{
|
|
FVoxelDataUtilities::RemoveItemFromLeafData<FVoxelMaterial>(Data, Leaf, Item);
|
|
}
|
|
}
|
|
}
|
|
|
|
template<>
|
|
inline void FVoxelDataItemsUtilities::AddItemToLeafData<FVoxelAssetItem>(
|
|
const FVoxelData& Data,
|
|
FVoxelDataOctreeLeaf& Leaf,
|
|
const FVoxelAssetItem& Item)
|
|
{
|
|
// Flush cache if possible
|
|
if (!Leaf.Values.IsDirty())
|
|
{
|
|
Leaf.Values.ClearData(Data);
|
|
}
|
|
if (!Leaf.Materials.IsDirty())
|
|
{
|
|
Leaf.Materials.ClearData(Data);
|
|
}
|
|
|
|
// If something is still dirty, merge manually
|
|
if (Leaf.Values.IsDirty() || Leaf.Materials.IsDirty())
|
|
{
|
|
FVoxelDataUtilities::AddAssetItemToLeafData(Data, Leaf, *Item.Generator, Item.Bounds, Item.LocalToWorld, Leaf.Values.IsDirty(), Leaf.Materials.IsDirty());
|
|
}
|
|
}
|
|
|
|
template<>
|
|
inline void FVoxelDataItemsUtilities::AddItemToLeafData<FVoxelDataItem>(
|
|
const FVoxelData& Data,
|
|
FVoxelDataOctreeLeaf& Leaf,
|
|
const FVoxelDataItem& Item)
|
|
{
|
|
// Flush cache if possible
|
|
if (!Leaf.Values.IsDirty())
|
|
{
|
|
Leaf.Values.ClearData(Data);
|
|
}
|
|
if (!Leaf.Materials.IsDirty())
|
|
{
|
|
Leaf.Materials.ClearData(Data);
|
|
}
|
|
|
|
// If something is still dirty, merge manually
|
|
if (Leaf.Values.IsDirty())
|
|
{
|
|
FVoxelDataUtilities::AddItemToLeafData<FVoxelValue>(Data, Leaf, Item);
|
|
}
|
|
if (Leaf.Materials.IsDirty())
|
|
{
|
|
FVoxelDataUtilities::AddItemToLeafData<FVoxelMaterial>(Data, Leaf, Item);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
template<typename T, bool bDoNotModifyExistingDataChunks, typename... TArgs>
|
|
TVoxelWeakPtr<const TVoxelDataItemWrapper<T>> FVoxelData::AddItem(TArgs&&... Args)
|
|
{
|
|
VOXEL_ASYNC_FUNCTION_COUNTER();
|
|
|
|
const auto ItemWrapper = MakeVoxelShared<TVoxelDataItemWrapper<T>>();
|
|
ItemWrapper->Item = T{ Forward<TArgs>(Args)... };
|
|
ItemWrapper->Data = AsShared();
|
|
|
|
const int32 MaxPlaceableItemsPerOctree = CVarMaxPlaceableItemsPerOctree.GetValueOnAnyThread();
|
|
FVoxelOctreeUtilities::IterateTreeInBounds(GetOctree(), ItemWrapper->Item.Bounds, [&](FVoxelDataOctreeBase& Tree)
|
|
{
|
|
if (Tree.IsLeaf())
|
|
{
|
|
ensureThreadSafe(Tree.IsLockedForWrite());
|
|
|
|
Tree.GetItemHolder().AddItem(ItemWrapper->Item);
|
|
|
|
if (!bDoNotModifyExistingDataChunks)
|
|
{
|
|
FVoxelDataItemsUtilities::AddItemToLeafData(*this, Tree.AsLeaf(), ItemWrapper->Item);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto& Parent = Tree.AsParent();
|
|
if (!Parent.HasChildren())
|
|
{
|
|
ensureThreadSafe(Parent.IsLockedForWrite());
|
|
// -1: since we're adding a new one
|
|
if (Tree.GetItemHolder().NeedToSubdivide(MaxPlaceableItemsPerOctree - 1))
|
|
{
|
|
Parent.CreateChildren();
|
|
}
|
|
else
|
|
{
|
|
Tree.GetItemHolder().AddItem(ItemWrapper->Item);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
if (TIsSame<T, FVoxelAssetItem>::Value) { INC_DWORD_STAT(STAT_NumVoxelAssetItems); }
|
|
if (TIsSame<T, FVoxelDisableEditsBoxItem>::Value) { INC_DWORD_STAT(STAT_NumVoxelDisableEditsItems); }
|
|
if (TIsSame<T, FVoxelDataItem>::Value) { INC_DWORD_STAT(STAT_NumVoxelDataItems); }
|
|
|
|
TItemData<T>& ItemsData = GetItemsData<T>();
|
|
|
|
FScopeLock Lock(&ItemsData.Section);
|
|
ItemWrapper->Index = ItemsData.Items.Add(ItemWrapper);
|
|
return ItemWrapper;
|
|
}
|
|
|
|
template<typename T>
|
|
bool FVoxelData::RemoveItem(TVoxelWeakPtr<const TVoxelDataItemWrapper<T>>& InItem, FString& OutError)
|
|
{
|
|
VOXEL_ASYNC_FUNCTION_COUNTER();
|
|
|
|
const auto Item = InItem.Pin();
|
|
if (!Item.IsValid() || Item->Index == -1)
|
|
{
|
|
OutError = TEXT("Invalid item, or the item was already deleted");
|
|
return false;
|
|
}
|
|
if (Item->Data != AsShared())
|
|
{
|
|
OutError = TEXT("Item doesn't belong to this data!");
|
|
return false;
|
|
}
|
|
|
|
TItemData<T>& ItemsData = GetItemsData<T>();
|
|
|
|
{
|
|
FScopeLock Lock(&ItemsData.Section);
|
|
if (!ensure(ItemsData.Items.IsValidIndex(Item->Index)))
|
|
{
|
|
return false;
|
|
}
|
|
if (!ensure(ItemsData.Items[Item->Index] == Item))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
FVoxelOctreeUtilities::IterateTreeInBounds(GetOctree(), Item->Item.Bounds, [&](FVoxelDataOctreeBase& Tree)
|
|
{
|
|
if (Tree.IsLeafOrHasNoChildren())
|
|
{
|
|
ensureThreadSafe(Tree.IsLockedForWrite());
|
|
|
|
Tree.GetItemHolder().RemoveItem(Item->Item);
|
|
|
|
if (Tree.IsLeaf())
|
|
{
|
|
FVoxelDataItemsUtilities::RemoveItemFromLeafData(*this, Tree.AsLeaf(), Item->Item);
|
|
}
|
|
}
|
|
});
|
|
|
|
if (TIsSame<T, FVoxelAssetItem>::Value) { DEC_DWORD_STAT(STAT_NumVoxelAssetItems); }
|
|
if (TIsSame<T, FVoxelDisableEditsBoxItem>::Value) { DEC_DWORD_STAT(STAT_NumVoxelDisableEditsItems); }
|
|
if (TIsSame<T, FVoxelDataItem>::Value) { DEC_DWORD_STAT(STAT_NumVoxelDataItems); }
|
|
|
|
FScopeLock Lock(&ItemsData.Section);
|
|
// Make sure our item is the last one
|
|
ItemsData.Items.Swap(Item->Index, ItemsData.Items.Num() - 1);
|
|
// Fixup the one we swapped (could be us, but that's fine)
|
|
ItemsData.Items[Item->Index]->Index = Item->Index;
|
|
// Pop the item
|
|
ensure(ItemsData.Items.Pop(false) == Item);
|
|
// Clear the index, in case we try to remove the item twice
|
|
Item->Index = -1;
|
|
|
|
return true;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
template<>
|
|
inline FVoxelData::TItemData<FVoxelAssetItem>& FVoxelData::GetItemsData<FVoxelAssetItem>()
|
|
{
|
|
return AssetItemsData;
|
|
}
|
|
|
|
template<>
|
|
inline FVoxelData::TItemData<FVoxelDisableEditsBoxItem>& FVoxelData::GetItemsData<FVoxelDisableEditsBoxItem>()
|
|
{
|
|
return DisableEditsItemsData;
|
|
}
|
|
|
|
template<>
|
|
inline FVoxelData::TItemData<FVoxelDataItem>& FVoxelData::GetItemsData<FVoxelDataItem>()
|
|
{
|
|
return DataItemsData;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
template<typename T, typename TApplyOld, typename TApplyNew>
|
|
void FVoxelDataUtilities::MigrateLeafDataToNewGenerator(
|
|
const FVoxelData& Data,
|
|
FVoxelDataOctreeLeaf& Leaf,
|
|
const FVoxelIntBox& BoundsToMigrate,
|
|
TApplyOld ApplyOldGenerator,
|
|
TApplyNew ApplyNewGenerator)
|
|
{
|
|
TVoxelDataOctreeLeafData<T>& DataHolder = Leaf.GetData<T>();
|
|
if (!ensure(DataHolder.IsDirty()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FVoxelIntBox Bounds = Leaf.GetBounds().Overlap(BoundsToMigrate);
|
|
const FIntVector Size = Bounds.Size();
|
|
|
|
// Revert to the old generator to query the old data
|
|
ApplyOldGenerator();
|
|
|
|
TArray<T> OldGeneratorData;
|
|
OldGeneratorData.SetNumUninitialized(Bounds.Count());
|
|
{
|
|
TVoxelQueryZone<T> QueryZone(Bounds, OldGeneratorData.GetData());
|
|
Leaf.GetFromGeneratorAndAssets<T>(*Data.Generator, QueryZone, 0);
|
|
}
|
|
|
|
// Switch back to the new generator, and query the new data
|
|
ApplyNewGenerator();
|
|
|
|
TArray<T> NewGeneratorData;
|
|
NewGeneratorData.SetNumUninitialized(Bounds.Count());
|
|
{
|
|
TVoxelQueryZone<T> QueryZone(Bounds, NewGeneratorData.GetData());
|
|
Leaf.GetFromGeneratorAndAssets<T>(*Data.Generator, QueryZone, 0);
|
|
}
|
|
|
|
// Update all the data that was the same as the old generator to the new generator
|
|
FVoxelDataOctreeSetter::Set<T>(Data, Leaf, [&](auto Lambda) { Bounds.Iterate(Lambda); },
|
|
[&](int32 X, int32 Y, int32 Z, T& Value)
|
|
{
|
|
const int32 Index = FVoxelUtilities::Get3DIndex(Size, X, Y, Z, Bounds.Min);
|
|
|
|
if (Value == FVoxelUtilities::Get(OldGeneratorData, Index))
|
|
{
|
|
// Switch the edited value to the new value if it wasn't edited
|
|
Value = FVoxelUtilities::Get(NewGeneratorData, Index);
|
|
}
|
|
});
|
|
}
|
|
|
|
template<typename T, typename TItem>
|
|
void FVoxelDataUtilities::AddItemToLeafData(
|
|
const FVoxelData& Data,
|
|
FVoxelDataOctreeLeaf& Leaf,
|
|
const TItem& Item)
|
|
{
|
|
MigrateLeafDataToNewGenerator<T>(
|
|
Data,
|
|
Leaf,
|
|
Item.Bounds,
|
|
[&]() { ensure(Leaf.GetItemHolder().RemoveItem(Item)); },
|
|
[&]() { Leaf.GetItemHolder().AddItem(Item); });
|
|
}
|
|
|
|
template<typename T, typename TItem>
|
|
void FVoxelDataUtilities::RemoveItemFromLeafData(
|
|
const FVoxelData& Data,
|
|
FVoxelDataOctreeLeaf& Leaf,
|
|
const TItem& Item)
|
|
{
|
|
ensureVoxelSlowNoSideEffects(!Leaf.GetItemHolder().RemoveItem(Item));
|
|
MigrateLeafDataToNewGenerator<T>(
|
|
Data,
|
|
Leaf,
|
|
Item.Bounds,
|
|
[&]() { Leaf.GetItemHolder().AddItem(Item); },
|
|
[&]() { ensure(Leaf.GetItemHolder().RemoveItem(Item)); });
|
|
} |