// 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 void FVoxelData::CacheBounds(const FVoxelIntBox& Bounds, bool bMultiThreaded) { VOXEL_ASYNC_FUNCTION_COUNTER(); TArray Leaves; FVoxelOctreeUtilities::IterateTreeInBounds(GetOctree(), Bounds, [&](FVoxelDataOctreeBase& Chunk) { if (Chunk.IsLeaf()) { ensureThreadSafe(Chunk.IsLockedForWrite()); auto& Leaf = Chunk.AsLeaf(); auto& DataHolder = Leaf.GetData(); 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(); DataHolder.CreateData(*this, [&](T* RESTRICT DataPtr) { TVoxelQueryZone QueryZone(Leaf.GetBounds(), DataPtr); Leaf.GetFromGeneratorAndAssets(*Generator, QueryZone, 0); }); }, !bMultiThreaded); } template void FVoxelData::ClearCacheInBounds(const FVoxelIntBox& Bounds) { VOXEL_ASYNC_FUNCTION_COUNTER(); FVoxelOctreeUtilities::IterateLeavesInBounds(GetOctree(), Bounds, [&](FVoxelDataOctreeLeaf& Leaf) { ensureThreadSafe(Leaf.IsLockedForWrite()); auto& DataHolder = Leaf.GetData(); if (DataHolder.HasAllocation() && !DataHolder.IsDirty()) { DataHolder.ClearData(*this); } }); } template TArray FVoxelData::Get(const FVoxelIntBox& Bounds) const { VOXEL_ASYNC_FUNCTION_COUNTER(); TArray Result; Result.Empty(Bounds.Count()); Result.SetNumUninitialized(Bounds.Count()); TVoxelQueryZone QueryZone(Bounds, Result); Get(QueryZone, 0); return Result; } template TArray FVoxelData::ParallelGet(const FVoxelIntBox& Bounds, bool bForceSingleThread) const { VOXEL_ASYNC_FUNCTION_COUNTER(); TArray Result; Result.SetNumUninitialized(Bounds.Count()); TVoxelQueryZone 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 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(*Generator, DefaultValue, Name, X, Y, Z, LOD); } template 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(*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 void FVoxelData::ParallelSet(const FVoxelIntBox& Bounds, F Apply, bool bForceSingleThread) { if (!ensure(Bounds.IsValid())) return; TArray 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(*this, Leaf, [&](auto Lambda) { Leaf.GetBounds().Overlap(Bounds).Iterate(Lambda); }, Apply); }, bForceSingleThread); } template 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(GetOctree(), X, Y, Z); FVoxelDataOctreeSetter::Set(*this, Leaf, Iterate, Apply); } } template 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(*Generator, X, Y, Z, LOD); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// namespace FVoxelDataItemsUtilities { // This will NOT add the item to the item holder template 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 void RemoveItemFromLeafData( const FVoxelData& Data, FVoxelDataOctreeLeaf& Leaf, const T& Item) { if (!TIsSame::Value && !TIsSame::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(Data, Leaf, Item); } if (Leaf.Materials.IsDirty()) { FVoxelDataUtilities::RemoveItemFromLeafData(Data, Leaf, Item); } } } template<> inline void FVoxelDataItemsUtilities::AddItemToLeafData( 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( 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(Data, Leaf, Item); } if (Leaf.Materials.IsDirty()) { FVoxelDataUtilities::AddItemToLeafData(Data, Leaf, Item); } } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// template TVoxelWeakPtr> FVoxelData::AddItem(TArgs&&... Args) { VOXEL_ASYNC_FUNCTION_COUNTER(); const auto ItemWrapper = MakeVoxelShared>(); ItemWrapper->Item = T{ Forward(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::Value) { INC_DWORD_STAT(STAT_NumVoxelAssetItems); } if (TIsSame::Value) { INC_DWORD_STAT(STAT_NumVoxelDisableEditsItems); } if (TIsSame::Value) { INC_DWORD_STAT(STAT_NumVoxelDataItems); } TItemData& ItemsData = GetItemsData(); FScopeLock Lock(&ItemsData.Section); ItemWrapper->Index = ItemsData.Items.Add(ItemWrapper); return ItemWrapper; } template bool FVoxelData::RemoveItem(TVoxelWeakPtr>& 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& ItemsData = GetItemsData(); { 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::Value) { DEC_DWORD_STAT(STAT_NumVoxelAssetItems); } if (TIsSame::Value) { DEC_DWORD_STAT(STAT_NumVoxelDisableEditsItems); } if (TIsSame::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& FVoxelData::GetItemsData() { return AssetItemsData; } template<> inline FVoxelData::TItemData& FVoxelData::GetItemsData() { return DisableEditsItemsData; } template<> inline FVoxelData::TItemData& FVoxelData::GetItemsData() { return DataItemsData; } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// template void FVoxelDataUtilities::MigrateLeafDataToNewGenerator( const FVoxelData& Data, FVoxelDataOctreeLeaf& Leaf, const FVoxelIntBox& BoundsToMigrate, TApplyOld ApplyOldGenerator, TApplyNew ApplyNewGenerator) { TVoxelDataOctreeLeafData& DataHolder = Leaf.GetData(); 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 OldGeneratorData; OldGeneratorData.SetNumUninitialized(Bounds.Count()); { TVoxelQueryZone QueryZone(Bounds, OldGeneratorData.GetData()); Leaf.GetFromGeneratorAndAssets(*Data.Generator, QueryZone, 0); } // Switch back to the new generator, and query the new data ApplyNewGenerator(); TArray NewGeneratorData; NewGeneratorData.SetNumUninitialized(Bounds.Count()); { TVoxelQueryZone QueryZone(Bounds, NewGeneratorData.GetData()); Leaf.GetFromGeneratorAndAssets(*Data.Generator, QueryZone, 0); } // Update all the data that was the same as the old generator to the new generator FVoxelDataOctreeSetter::Set(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 void FVoxelDataUtilities::AddItemToLeafData( const FVoxelData& Data, FVoxelDataOctreeLeaf& Leaf, const TItem& Item) { MigrateLeafDataToNewGenerator( Data, Leaf, Item.Bounds, [&]() { ensure(Leaf.GetItemHolder().RemoveItem(Item)); }, [&]() { Leaf.GetItemHolder().AddItem(Item); }); } template void FVoxelDataUtilities::RemoveItemFromLeafData( const FVoxelData& Data, FVoxelDataOctreeLeaf& Leaf, const TItem& Item) { ensureVoxelSlowNoSideEffects(!Leaf.GetItemHolder().RemoveItem(Item)); MigrateLeafDataToNewGenerator( Data, Leaf, Item.Bounds, [&]() { Leaf.GetItemHolder().AddItem(Item); }, [&]() { ensure(Leaf.GetItemHolder().RemoveItem(Item)); }); }