Skip to content
Snippets Groups Projects
Commit 47a868cf authored by Timon Römer's avatar Timon Römer
Browse files

Adds full MagicWand support with visuals

parent 91c6b1eb
Branches
No related tags found
No related merge requests found
No preview for this file type
......@@ -9,7 +9,7 @@
class FMagicWandSelectionTask;
UMagicWand::UMagicWand() : World(nullptr)
UMagicWand::UMagicWand() : CurrentSelection(), World(nullptr)
{
// Create the procedural mesh component
ProceduralMesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("GeneratedMesh"));
......@@ -47,7 +47,7 @@ void UMagicWand::InitProceduralMesh() const
MarchingCubeMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
MarchingCubeMesh->SetMobility(EComponentMobility::Movable);
MarchingCubeMesh->SetVisibility(true);
MarchingCubeMesh->AttachToComponent(MyPointCloud->PointCloudVisualizer, FAttachmentTransformRules::SnapToTargetIncludingScale);
MarchingCubeMesh->AttachToComponent(MyPointCloud->GetRootComponent(), FAttachmentTransformRules::SnapToTargetIncludingScale);
}
void UMagicWand::InitReferences()
......@@ -120,9 +120,9 @@ void UMagicWand::GenerateVoxelMeshWithCubes(const TArray<int32> Voxels) const
});
}
void UMagicWand::GenerateVoxelMeshSmooth(const TSet<int32> Voxels)
void UMagicWand::GenerateVoxelMeshSmooth(const TSet<int32>& Voxels)
{
if(IsMarchingCubesRunning)
if(IsMarchingCubesRunning || Voxels.IsEmpty())
return;
IsMarchingCubesRunning = true;
......@@ -141,7 +141,21 @@ void UMagicWand::GenerateVoxelMeshSmooth(const TSet<int32> Voxels)
UE::Geometry::FMarchingCubes MarchingCubes;
MarchingCubes.Bounds = UE::Geometry::TAxisAlignedBox3(Min, Max);
MarchingCubes.CubeSize = MarchingCubeSize == 0 ? MyDensityField->GetStep().X : MarchingCubeSize;
//MarchingCubes.CubeSize = MarchingCubeSize == 0 ? MyDensityField->GetStep().X : MarchingCubeSize;
const float Volume = MarchingCubes.Bounds.Volume();
// Logarithmic scaling of the cube size based on volume
if(Volume > 50000) {
MarchingCubes.CubeSize = (3.0f * log10((Volume + 500) / 1000) - 4);
}else if(Volume > 500000)
{
MarchingCubes.CubeSize = 4.3;
}else {
MarchingCubes.CubeSize = MyDensityField->GetStep().X;
}
MarchingCubes.CubeSize = FMath::Clamp(MarchingCubes.CubeSize, MyDensityField->GetStep().X, 10.0f);
MarchingCubes.CancelF = [this]() {
return AbortMarchingCubes;
};
......@@ -298,6 +312,8 @@ void UMagicWand::HandleMetaSelectPressed(const FInputActionInstance& Instance)
const FVector SelectionStartPositionLocal = MyPointCloud->PointCloudVisualizer->GetComponentTransform().InverseTransformPosition(SelectionStartPositionWorld);
FindSeed(SelectionStartPositionLocal);
if(SeedPointIndex == INDEX_NONE) return;
CountSelectionTime = 1 / MagicWandUpdatesPerSecond;
IsMagicWandInitialized = true;
}
......@@ -315,31 +331,36 @@ void UMagicWand::HandleMetaSelectReleased(const FInputActionInstance& Instance)
{
UGameplayStatics::PlaySound2D(World, SelectionEndSound);
}
if(!CurrentSelection.IsValid()) return;
TArray<int32> SelectionArray = CurrentSelection.GetPointIndexList();
for(const int Index : SelectionArray)
TArray<FAtomicBool> SelectionArray = CurrentSelection.Get()->GetSelectionFlags();
for(int i = 0; i < SelectionArray.Num(); i++)
{
if(MyPointCloud && MyPointCloud->SelectionFlags.IsValidIndex(Index))
if(SelectionArray[i].Value.load())
{
MyPointCloud->SelectionFlags[Index] = true;
MyPointCloud->SelectionFlags[i] = true;
}
}
CurrentSelection = FSelectionManager(CurrentSelection.GetSelectionFlags().Num());
//CurrentSelection.Reset();
}
void UMagicWand::HandleMetaEraseReleased(const FInputActionInstance& Instance)
{
Super::HandleMetaEraseReleased(Instance);
if(NumberThreads.GetValue() > 0 || CurrentSelection.SelectedCount() == 0)
if(NumberThreads.GetValue() > 0)
{
return;
}
//AbortMagicWand.Store(true);
CurrentSelection = FSelectionManager(CurrentSelection.GetSelectionFlags().Num());
MyPointCloud->SelectionFlags = CurrentSelection.GetSelectionFlags();
CurrentSelection = MakeShared<FSelectionManager>(MyPointCloud->SelectionFlags.Num(), MyDensityField->GetVoxelNumber());
for(int i = 0; i < MyPointCloud->SelectionFlags.Num(); i++)
{
MyPointCloud->SelectionFlags[i] = false;
}
}
// MAGIC WAND SELECTION
......@@ -359,28 +380,24 @@ void UMagicWand::PerformMagicWandSelection(const float ProximityThreshold)
return;
}
if (SeedPointIndex != INDEX_NONE)
{
if(SelectionStartSound)
{
UGameplayStatics::PlaySound2D(World, SelectionStartSound);
}
if (SeedPointIndex == INDEX_NONE) return;
if(SelectionStartSound) UGameplayStatics::PlaySound2D(World, SelectionStartSound);
CurrentSelection = GetSelectionResultsFromProximity(ProximityThreshold);
CurrentSelection.SelectIndex(SeedPointIndex);
CurrentSelection = GetSelectionCacheResultCopy(ProximityThreshold);
CurrentSelection.Get()->SelectIndex(SeedPointIndex);
TQueue<int32>* ProcessQueue = new TQueue<int32>{};
for (int32 Index : CurrentSelection.GetPointIndexList())
for (int32 Index : CurrentSelection.Get()->GetPointsAsIndexList())
{
ProcessQueue->Enqueue(Index);
}
FMagicWandSelectionTask* Task = new FMagicWandSelectionTask(this, ProcessQueue, ProximityThreshold, CurrentSelection.SelectedCount());
FMagicWandSelectionTask* Task = new FMagicWandSelectionTask(this, ProcessQueue, ProximityThreshold, CurrentSelection.Get()->SelectedCount());
NumberThreads.Increment();
Task->Start();
}
}
void UMagicWand::FindSeed(const FVector& InputPosition)
{
......@@ -435,9 +452,7 @@ void UMagicWand::ExpandFromAllPointsInQueue(TQueue<int32>* ProcessQueue, const f
{
//UE_LOG(LogTemp, Warning, TEXT("Opened New Thread! Number of Threads now: %d"), NumberThreads.GetValue());
TSet<int32> MyFullVoxelList;
MyFullVoxelList.Reserve(1000000);
FVoxelPointLookupTable MyVoxelPointLookupTable = *MyDensityField->VoxelPointLookupTable;
const FVoxelPointLookupTable MyVoxelPointLookupTable = *MyDensityField->VoxelPointLookupTable;
while (!ProcessQueue->IsEmpty())
{
......@@ -450,7 +465,7 @@ void UMagicWand::ExpandFromAllPointsInQueue(TQueue<int32>* ProcessQueue, const f
{
const FVector CurrentQueuePointPosition = MyPointCloud->PositionVectors[CurrentQueuePointIndex];
ExpandFromPoint(ProcessQueue, ProximityThreshold, ThreadLoad, CurrentQueuePointPosition, &MyFullVoxelList, &MyVoxelPointLookupTable);
ExpandFromPoint(ProcessQueue, ProximityThreshold, ThreadLoad, CurrentQueuePointPosition, &MyVoxelPointLookupTable);
}
ThreadLoad--;
......@@ -459,7 +474,7 @@ void UMagicWand::ExpandFromAllPointsInQueue(TQueue<int32>* ProcessQueue, const f
FinishSelectionThread(ProximityThreshold);
}
void UMagicWand::ExpandFromPoint(TQueue<int32>* ProcessQueue, const float ProximityThreshold, int& ThreadLoad, const FVector& ExpansionPoint, TSet<int32>* MyFullVoxelList, FVoxelPointLookupTable* MyVoxelPointLookupTable)
void UMagicWand::ExpandFromPoint(TQueue<int32>* ProcessQueue, const float ProximityThreshold, int& ThreadLoad, const FVector& ExpansionPoint, const FVoxelPointLookupTable* MyVoxelPointLookupTable) const
{
const FVector Step = MyDensityField->GetStep();
const int32 StartX = FMath::Max(0, FMath::FloorToInt((ExpansionPoint.X - ProximityThreshold - MyPointCloud->MinBounds.X) / Step.X));
......@@ -479,7 +494,8 @@ void UMagicWand::ExpandFromPoint(TQueue<int32>* ProcessQueue, const float Proxim
{
const int32 CurrentVoxelComparisonIndex = MyDensityField->GridPositionToIndex(X, Y, Z);
if(MyFullVoxelList->Contains(CurrentVoxelComparisonIndex))
if(CurrentSelection.Get()->IsFullVoxel(CurrentVoxelComparisonIndex))
{
continue;
}
......@@ -489,7 +505,7 @@ void UMagicWand::ExpandFromPoint(TQueue<int32>* ProcessQueue, const float Proxim
int Selected = 0;
for(const int32 CurrentPointComparisonIndex : PointIndices)
{
if(CurrentSelection.IsSelected(CurrentPointComparisonIndex))
if(CurrentSelection.Get()->IsSelected(CurrentPointComparisonIndex))
{
Selected++;
continue;
......@@ -504,13 +520,13 @@ void UMagicWand::ExpandFromPoint(TQueue<int32>* ProcessQueue, const float Proxim
ThreadLoad++;
Selected++;
CurrentSelection.SelectIndex(CurrentPointComparisonIndex);
CurrentSelection.Get()->SelectIndex(CurrentPointComparisonIndex);
}
}
if(Selected == PointIndices.Num())
{
MyFullVoxelList->Add(CurrentVoxelComparisonIndex);
CurrentSelection.Get()->AddFullVoxel(CurrentVoxelComparisonIndex);
}
}
}
......@@ -526,7 +542,27 @@ void UMagicWand::FinishSelectionThread(const float ProximityThreshold)
{
CacheSelectionResults(ProximityThreshold, CurrentSelection);
//UE_LOG(LogTemp, Error, TEXT("Finished!"));
FScopeLock Lock(&SelectionCacheLock);
if(CurrentSelection.IsValid())
{
TSet<int32> Voxels;
TArray<int32> PointIndices = CurrentSelection.Get()->GetPointsAsIndexList();
for (int32 i = 0; i < PointIndices.Num(); i++)
{
const int Index = PointIndices[i];
if(MyPointCloud->PositionVectors.IsValidIndex(Index))
{
FVector Point = MyPointCloud->PositionVectors[Index];
int32 VoxelFromIndex = MyDensityField->WorldPositionToIndex(Point);
Voxels.Add(VoxelFromIndex);
}
}
GenerateVoxelMeshSmooth(Voxels);
}
//CurrentSelection.Reset();
}else if(NumberThreads.GetValue() < 0)
{
UE_LOG(LogTemp, Error, TEXT("More Threads closed than opened!"));
......@@ -551,7 +587,7 @@ void UMagicWand::CreateNewExpansionThread(TQueue<int32>* ProcessQueue, int& Numb
Task->Start();
}
void UMagicWand::CacheSelectionResults(const float ProximityThreshold, const FSelectionManager& SelectionResults)
void UMagicWand::CacheSelectionResults(const float ProximityThreshold, const TSharedPtr<FSelectionManager>& SelectionResults)
{
FScopeLock Lock(&SelectionCacheLock);
// Check if the proximity range is already present
......@@ -562,27 +598,30 @@ void UMagicWand::CacheSelectionResults(const float ProximityThreshold, const FSe
SortedProximityRanges.Sort();
// Update the cache with new indices for a given proximity range
SelectionCache.Add(ProximityThreshold, SelectionResults);
const TSharedRef<FSelectionManager> CacheCopy = MakeShared<FSelectionManager>(*SelectionResults.Get()); // This will return a shared pointer
SelectionCache.Add(ProximityThreshold, CacheCopy);
}
}
FSelectionManager UMagicWand::GetSelectionResultsFromProximity(const float ProximityThreshold)
{
TSharedPtr<FSelectionManager> UMagicWand::GetSelectionCacheResultCopy(const float ProximityThreshold) {
FScopeLock Lock(&SelectionCacheLock);
for(int i = SortedProximityRanges.Num() - 1; i >= 0; --i)
{
const float CurrentRange = SortedProximityRanges[i];
for (int i = SortedProximityRanges.Num() - 1; i >= 0; --i) {
if(CurrentRange <= ProximityThreshold)
{
//UE_LOG(LogTemp, Error, TEXT("Cached selection found for proximity range %f, closest smaller proximity was %f"), ProximityThreshold, CurrentRange);
return SelectionCache[CurrentRange];
if (SortedProximityRanges[i] <= ProximityThreshold) {
FSelectionManager* CopiedManager(SelectionCache[SortedProximityRanges[i]].Get());
return MakeShared<FSelectionManager>(*CopiedManager); // This will return a shared pointer
}
}
//UE_LOG(LogTemp, Error, TEXT("No cached selection found for proximity range %f"), ProximityThreshold);
return FSelectionManager(MyPointCloud->SelectionFlags.Num());
// If no valid selection manager is found, create a new one and add it to the map
TSharedPtr<FSelectionManager> NewManager = MakeShared<FSelectionManager>(MyPointCloud->SelectionFlags.Num(), MyDensityField->GetVoxelNumber());
SelectionCache.Add(ProximityThreshold, NewManager);
SortedProximityRanges.Add(ProximityThreshold);
SortedProximityRanges.Sort();
return NewManager; // Return the newly created manager
}
// TICK
......@@ -619,24 +658,6 @@ void UMagicWand::SelectParticles(const FVector& InputPosition)
if(CountSelectionTime >= 1.0f / MagicWandUpdatesPerSecond && NumberThreads.GetValue() == 0)
{
CountSelectionTime = 0;
//AbortMagicWand.Store(false);
PerformMagicWandSelection(ProximityThreshold);
FScopeLock Lock(&SelectionCacheLock);
TSet<int32> Voxels;
TArray<int32> PointIndices = CurrentSelection.GetPointIndexList();
for (int32 i = 0; i < PointIndices.Num(); i++)
{
const int Index = PointIndices[i];
if(MyPointCloud->PositionVectors.IsValidIndex(Index))
{
FVector Point = MyPointCloud->PositionVectors[Index];
int32 VoxelFromIndex = MyDensityField->WorldPositionToIndex(Point);
Voxels.Add(VoxelFromIndex);
}
}
GenerateVoxelMeshSmooth(Voxels);
}
}
\ No newline at end of file
......@@ -16,8 +16,8 @@ class UMagicWand : public UMetaCastBaseline
// MAGIC WAND
TAtomic<bool> AbortMagicWand = false;
FSelectionManager CurrentSelection;
TMap<float, FSelectionManager> SelectionCache;
TSharedPtr<FSelectionManager> CurrentSelection;
TMap<float, TSharedPtr<FSelectionManager>> SelectionCache;
TArray<float> SortedProximityRanges;
FVector SeedPointPositionLocal;
FDensityField* MyDensityField;
......@@ -100,7 +100,7 @@ public:
// VISUALIZATION
void GenerateVoxelMeshWithCubes(const TArray<int32> Voxels) const;
void GenerateVoxelMeshSmooth(const TSet<int32> Voxels);
void GenerateVoxelMeshSmooth(const TSet<int32>& Voxels);
// MAGIC WAND SELECTION
......@@ -108,11 +108,11 @@ public:
void PerformMagicWandSelection(const float ProximityThreshold);
void FindSeed(const FVector& InputPosition);
void ExpandFromAllPointsInQueue(TQueue<int32>* ProcessQueue, const float ProximityThreshold, int ThreadLoad);
void ExpandFromPoint(TQueue<int32>* ProcessQueue, const float ProximityThreshold, int& ThreadLoad, const FVector& ExpansionPoint, TSet<int32>* MyFullVoxelList, FVoxelPointLookupTable* MyVoxelPointLookupTable);
void ExpandFromPoint(TQueue<int32>* ProcessQueue, const float ProximityThreshold, int& ThreadLoad, const FVector& ExpansionPoint, const FVoxelPointLookupTable* MyVoxelPointLookupTable) const;
void FinishSelectionThread(const float ProximityThreshold);
void CreateNewExpansionThread(TQueue<int32>* ProcessQueue, int& NumberToProcess, const float ProximityThreshold);
void CacheSelectionResults(const float ProximityThreshold, const FSelectionManager& SelectionResults);
FSelectionManager GetSelectionResultsFromProximity(float ProximityThreshold);
void CacheSelectionResults(const float ProximityThreshold, const TSharedPtr<FSelectionManager>& SelectionResults);
TSharedPtr<FSelectionManager> GetSelectionCacheResultCopy(float ProximityThreshold);
// TICK
......
#pragma once
#include "CoreMinimal.h"
class FSelectionManager
class FAtomicBool
{
TArray<int32> PointIndexList; // List of selected indices
TArray<bool> SelectionFlags; // Flags to indicate if an index is selected
int SelectionCount; // Counter for selected indices
TSet<int32> FullVoxelList;
public:
explicit FSelectionManager(const int32 NumElements) : SelectionCount(0)
FAtomicBool() : Value(false) {}
explicit FAtomicBool(const bool InValue) : Value(InValue) {}
// Copy constructor
FAtomicBool(const FAtomicBool& Other)
{
SelectionFlags.Init(false, NumElements);
PointIndexList.Reserve(NumElements); // Preallocate space to optimize appending
Value.store(Other.Value.load());
}
explicit FSelectionManager(const TArray<bool>& InSelectionFlags) : SelectionFlags(InSelectionFlags)
// Assignment operator
FAtomicBool& operator=(const FAtomicBool& Other)
{
SelectionCount = 0;
for (int i = 0; i < InSelectionFlags.Num(); i++)
if (this != &Other)
{
if (InSelectionFlags[i])
{
PointIndexList.Add(i);
SelectionCount++;
Value.store(Other.Value.load());
}
return *this;
}
// Move constructor
FAtomicBool(FAtomicBool&& Other) noexcept : Value(Other.Value.load()) {}
// Move assignment operator
FAtomicBool& operator=(FAtomicBool&& Other) noexcept
{
if (this != &Other)
{
Value.store(Other.Value.load());
}
return *this;
}
FSelectionManager(): SelectionCount(0) {}
std::atomic<bool> Value;
};
class FSelectionManager
{
TArray<FAtomicBool> SelectionFlags; // Flags to indicate if an index is selected
TArray<FAtomicBool> FullVoxelFlags; // Flags to indicate if a voxel is full
FThreadSafeCounter SelectionPointCount; // Counter for selected indices
public:
explicit FSelectionManager(const int32 NumberOfPoints, const int32 NumberOfVoxels) : SelectionPointCount(0)
{
SelectionFlags.Init(FAtomicBool(false), NumberOfPoints);
FullVoxelFlags.Init(FAtomicBool(false), NumberOfVoxels);
}
// Copy constructor
FSelectionManager(const FSelectionManager& Other)
: SelectionFlags(Other.SelectionFlags)
, FullVoxelFlags(Other.FullVoxelFlags)
, SelectionPointCount(Other.SelectionPointCount)
{}
/** Adds an index to the selection if it isn't already selected */
void SelectIndex(const int32 Index)
{
if (!SelectionFlags[Index])
if (IsValidIndex(Index) && !SelectionFlags[Index].Value.load())
{
PointIndexList.Add(Index);
SelectionFlags[Index] = true;
SelectionCount++;
//PointIndexList.Add(Index);
SelectionFlags[Index].Value.store(true);
SelectionPointCount.Increment();
}
}
/** Removes a specific index from the selection */
bool UnselectIndex(const int32 IndexToRemove)
{
if (IsValidIndex(IndexToRemove) && SelectionFlags[IndexToRemove])
if (IsValidIndex(IndexToRemove) && SelectionFlags[IndexToRemove].Value.load())
{
const int32 RemoveAt = PointIndexList.IndexOfByKey(IndexToRemove);
if (RemoveAt != INDEX_NONE)
//const int32 RemoveAt = PointIndexList.IndexOfByKey(IndexToRemove);
if (true/*RemoveAt != INDEX_NONE*/)
{
PointIndexList.RemoveAt(RemoveAt);
SelectionFlags[IndexToRemove] = false;
SelectionCount--;
//PointIndexList.RemoveAt(RemoveAt);
SelectionFlags[IndexToRemove].Value.store(false);
SelectionPointCount.Decrement();
return true;
}
}
......@@ -62,27 +90,19 @@ public:
/** Returns true if any indices are selected */
bool IsAnythingSelected() const
{
return SelectionCount > 0;
return SelectionPointCount.GetValue() > 0;
}
/** Checks if a specific index is selected */
bool IsSelected(const int32 Index) const
{
return IsValidIndex(Index) && SelectionFlags[Index];
}
/** Resets the selection status of all indices */
void Reset()
{
SelectionFlags.Init(false, SelectionFlags.Num());
PointIndexList.Empty();
SelectionCount = 0;
return IsValidIndex(Index) && SelectionFlags[Index].Value.load();
}
/** Returns the number of selected indices */
int32 SelectedCount() const
{
return SelectionCount;
return SelectionPointCount.GetValue();
}
/** Checks if the given index is within the valid range */
......@@ -91,19 +111,55 @@ public:
return Index >= 0 && Index < SelectionFlags.Num();
}
/** Returns the number of indices in the selection list (for debugging) */
int32 QueueSize() const
const TArray<FAtomicBool>& GetSelectionFlags() const
{
return PointIndexList.Num();
return SelectionFlags;
}
const TArray<int32>& GetPointIndexList() const
// Thread-safe method to add a full voxel
void AddFullVoxel(const int32 VoxelIndex)
{
return PointIndexList;
if(FullVoxelFlags.IsValidIndex(VoxelIndex))
{
FullVoxelFlags[VoxelIndex].Value.store(true);
}else
{
UE_LOG(LogTemp, Error, TEXT("Voxel index %d is out of bounds when writing."), VoxelIndex);
}
}
const TArray<bool>& GetSelectionFlags() const
// Thread-safe method to check if a voxel is full
bool IsFullVoxel(const int32 VoxelIndex) const
{
return SelectionFlags;
if(FullVoxelFlags.IsValidIndex(VoxelIndex))
{
return FullVoxelFlags[VoxelIndex].Value.load();
}
UE_LOG(LogTemp, Error, TEXT("Voxel index %d is out of bounds when reading."), VoxelIndex);
return false;
}
// Batch update method to add multiple full voxels
void BatchAddFullVoxels(const TArray<int32>& VoxelIndices)
{
for (const int32 VoxelIndex : VoxelIndices)
{
FullVoxelFlags[VoxelIndex].Value.store(true);
}
}
TArray<int> GetPointsAsIndexList()
{
TArray<int> PointIndexList;
PointIndexList.Reserve(SelectionPointCount.GetValue());
for (int32 Index = 0; Index < SelectionFlags.Num(); ++Index)
{
if (SelectionFlags[Index].Value.load())
{
PointIndexList.Add(Index);
}
}
return PointIndexList;
}
};
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment