diff --git a/Content/LineMaterialOnTop.uasset b/Content/LineMaterialOnTop.uasset new file mode 100644 index 0000000000000000000000000000000000000000..dc22cab6810dd4bb4d641ea9a133b9f921fb125f Binary files /dev/null and b/Content/LineMaterialOnTop.uasset differ diff --git a/Content/MagicWandMap.umap b/Content/MagicWandMap.umap index 4e7456218f05c4b5c26de3ca4d0b7adcdecfd53e..ea280d4c79087af91cda2dfbfa349f9795314ad5 100644 Binary files a/Content/MagicWandMap.umap and b/Content/MagicWandMap.umap differ diff --git a/Content/SelectionEnd.uasset b/Content/SelectionEnd.uasset new file mode 100644 index 0000000000000000000000000000000000000000..5123c422a95b9f183074275e78f1bd61b41d527e Binary files /dev/null and b/Content/SelectionEnd.uasset differ diff --git a/Content/SelectionEndSound.uasset b/Content/SelectionEndSound.uasset new file mode 100644 index 0000000000000000000000000000000000000000..f1d9214254da15bc2a58ec928f77ddd4b3a28a4c Binary files /dev/null and b/Content/SelectionEndSound.uasset differ diff --git a/Content/SelectionStart.uasset b/Content/SelectionStart.uasset new file mode 100644 index 0000000000000000000000000000000000000000..de70358be6a09c33bec1f285cb456d42024ca72b Binary files /dev/null and b/Content/SelectionStart.uasset differ diff --git a/Content/SelectionStartSound.uasset b/Content/SelectionStartSound.uasset new file mode 100644 index 0000000000000000000000000000000000000000..d75674c56db89afff2b1889918a640201d84c70c Binary files /dev/null and b/Content/SelectionStartSound.uasset differ diff --git a/Content/SelectionVolume.uasset b/Content/SelectionVolume.uasset index f5d2c569e5837f227551627058837d7703770703..ee9355b4be96b567ea71a244e4fd54a1255459b8 100644 Binary files a/Content/SelectionVolume.uasset and b/Content/SelectionVolume.uasset differ diff --git a/Source/MetaCastBachelor/FMagicWandSelectionTask.h b/Source/MetaCastBachelor/FMagicWandSelectionTask.h new file mode 100644 index 0000000000000000000000000000000000000000..4fd03de9e4e7ac1c5980b4c19870114ad1c17c8a --- /dev/null +++ b/Source/MetaCastBachelor/FMagicWandSelectionTask.h @@ -0,0 +1,80 @@ +#pragma once + +#include "CoreMinimal.h" +#include "MagicWand.h" + +DECLARE_STATS_GROUP(TEXT("MagicWandTasks"), STATGROUP_MagicWandTasks, STATCAT_Advanced); + +class FMagicWandSelectionTask : public FRunnable +{ + UMagicWand* MagicWand; + TQueue<int32>* ProcessQueue; + float ProximityThreshold; + int ThreadLoad; + FRunnableThread* Thread = nullptr; + +public: + FMagicWandSelectionTask(UMagicWand* InMagicWand, TQueue<int32>* InProcessQueue, const float InProximityThreshold, const int ThreadLoad) + : MagicWand(InMagicWand), ProcessQueue(InProcessQueue), ProximityThreshold(InProximityThreshold), ThreadLoad(ThreadLoad) + { + + } + + virtual bool Init() override + { + return true; + } + + void Start() + { + if (!Thread) + { + Thread = FRunnableThread::Create(this, TEXT("FMagicWandSelectionTask"), 0, TPri_Highest); + } + } + + virtual uint32 Run() override + { + MagicWand->ExpandFromAllPointsInQueue(ProcessQueue, ProximityThreshold, ThreadLoad); + delete ProcessQueue; // Ensure resources are freed properly + ProcessQueue = nullptr; + return 0; + } + + void DoWork() + { + MagicWand->ExpandFromAllPointsInQueue(ProcessQueue, ProximityThreshold, ThreadLoad); + delete ProcessQueue; // Ensure resources are freed properly + ProcessQueue = nullptr; + } + + // Destructor to clean up the queue + virtual ~FMagicWandSelectionTask() override + { + if (ProcessQueue) + { + delete ProcessQueue; + ProcessQueue = nullptr; + } + if (Thread) + { + Thread->Kill(true); + delete Thread; + } + } + + void ForceStop() + { + if (ProcessQueue) + { + delete ProcessQueue; + ProcessQueue = nullptr; + } + if (Thread) + { + Thread->Kill(true); // Forcefully terminates the thread + delete Thread; + Thread = nullptr; + } + } +}; \ No newline at end of file diff --git a/Source/MetaCastBachelor/MagicWand.cpp b/Source/MetaCastBachelor/MagicWand.cpp index 39877f5160954d8f4647d7eddf17670a1049b871..e5404af1f9dab6e9e20eac7ff95472b847b2b5c2 100644 --- a/Source/MetaCastBachelor/MagicWand.cpp +++ b/Source/MetaCastBachelor/MagicWand.cpp @@ -1,14 +1,19 @@ #include "MagicWand.h" #include "Utilities.h" +#include "FMagicWandSelectionTask.h" #include "Components/LineBatchComponent.h" #include "Generators/MarchingCubes.h" +#include "Kismet/GameplayStatics.h" // INITIALIZATION +class FMagicWandSelectionTask; + UMagicWand::UMagicWand() : World(nullptr) { // Create the procedural mesh component ProceduralMesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("GeneratedMesh")); + MarchingCubeMesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("GeneratedMesh2")); MyLineBatchComponent = CreateDefaultSubobject<ULineBatchComponent>(TEXT("LineDrawer")); } @@ -32,6 +37,17 @@ void UMagicWand::InitProceduralMesh() const ProceduralMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); ProceduralMesh->SetMobility(EComponentMobility::Movable); ProceduralMesh->SetVisibility(true); + + MarchingCubeMesh->SetMaterial(0, SelectionVolumeMat); + MarchingCubeMesh->SetMaterial(1, SelectionVolumeMat); + MarchingCubeMesh->SetMaterial(2, SelectionVolumeMat); + MarchingCubeMesh->bCastDynamicShadow = false; + MarchingCubeMesh->bRenderCustomDepth = true; + MarchingCubeMesh->SetCastShadow(false); + MarchingCubeMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); + MarchingCubeMesh->SetMobility(EComponentMobility::Movable); + MarchingCubeMesh->SetVisibility(true); + MarchingCubeMesh->AttachToComponent(MyPointCloud->PointCloudVisualizer, FAttachmentTransformRules::SnapToTargetIncludingScale); } void UMagicWand::InitReferences() @@ -104,7 +120,7 @@ void UMagicWand::GenerateVoxelMeshWithCubes(const TArray<int32> Voxels) const }); } -void UMagicWand::GenerateVoxelMeshSmooth(const TArray<int32> Voxels) +void UMagicWand::GenerateVoxelMeshSmooth(const TSet<int32> Voxels) { if(IsMarchingCubesRunning) return; @@ -134,7 +150,7 @@ void UMagicWand::GenerateVoxelMeshSmooth(const TArray<int32> Voxels) AbortMarchingCubes = false; // Define the implicit function MarchingCubes.Implicit = [this, Voxels](const FVector3d& Pos) { - const FVector PosConverted = FVector(Pos.X, Pos.Y, Pos.Z); + const FVector PosConverted(Pos.X, Pos.Y, Pos.Z); const int Index = MyDensityField->WorldPositionToIndex(PosConverted); return Voxels.Contains(Index) ? 1 : -1; }; @@ -182,8 +198,8 @@ void UMagicWand::GenerateVoxelMeshSmooth(const TArray<int32> Voxels) AsyncTask(ENamedThreads::Type::GameThread, [this, Vertices, Indices, Normals, VertexColors]() { FScopeLock MeshLock(&ProceduralMeshGuard); - ProceduralMesh->CreateMeshSection(0, Vertices, Indices, Normals, TArray<FVector2D>(), VertexColors, TArray<FProcMeshTangent>(), false); - ProceduralMesh->SetVisibility(true); + MarchingCubeMesh->CreateMeshSection(0, Vertices, Indices, Normals, TArray<FVector2D>(), VertexColors, TArray<FProcMeshTangent>(), false); + MarchingCubeMesh->SetVisibility(true); IsMarchingCubesRunning = false; }); }); @@ -269,40 +285,61 @@ void UMagicWand::GenerateCylinderBetweenPoints(const FVector& Start, const FVect void UMagicWand::HandleMetaSelectPressed(const FInputActionInstance& Instance) { Super::HandleMetaSelectPressed(Instance); + + if(NumberThreads.GetValue() > 0) return; + + ProceduralMesh->ClearAllMeshSections(); + FScopeLock Lock(&SelectionCacheLock); + SelectionCache.Empty(); + SortedProximityRanges.Empty(); + SeedPointIndex = INDEX_NONE; const FVector SelectionStartPositionWorld = SelectionObject->GetComponentLocation(); const FVector SelectionStartPositionLocal = MyPointCloud->PointCloudVisualizer->GetComponentTransform().InverseTransformPosition(SelectionStartPositionWorld); FindSeed(SelectionStartPositionLocal); + CountSelectionTime = 1 / MagicWandUpdatesPerSecond; + IsMagicWandInitialized = true; } void UMagicWand::HandleMetaSelectReleased(const FInputActionInstance& Instance) { Super::HandleMetaSelectReleased(Instance); - const FVector SelectionWorldPosition = SelectionObject->GetComponentLocation(); - const FVector SelectionLocalPosition = MyPointCloud->PointCloudVisualizer->GetComponentTransform().InverseTransformPosition(SelectionWorldPosition); - - InitMagicWandSelection(); + IsMagicWandInitialized = false; + ProceduralMesh->ClearAllMeshSections(); + MarchingCubeMesh->SetVisibility(false); + FScopeLock Lock(&SelectionCacheLock); + + if(SelectionEndSound) + { + UGameplayStatics::PlaySound2D(World, SelectionEndSound); + } - AsyncTask(ENamedThreads::Type::AnyBackgroundHiPriTask, [this, &SelectionLocalPosition]() + TArray<int32> SelectionArray = CurrentSelection.GetPointIndexList(); + for(const int Index : SelectionArray) { - PerformMagicWandSelection(); - }); + if(MyPointCloud && MyPointCloud->SelectionFlags.IsValidIndex(Index)) + { + MyPointCloud->SelectionFlags[Index] = true; + } + } + + CurrentSelection = FSelectionManager(CurrentSelection.GetSelectionFlags().Num()); } void UMagicWand::HandleMetaEraseReleased(const FInputActionInstance& Instance) { Super::HandleMetaEraseReleased(Instance); - - FScopeLock Lock(&ThreadNumberLock); - AbortMagicWand.Store(true); - //deselect all particles - for (int32 i = 0; i < MyPointCloud->SelectionFlags.Num(); ++i) + if(NumberThreads.GetValue() > 0 || CurrentSelection.SelectedCount() == 0) { - MyPointCloud->SelectionFlags[i] = false; + return; } + + //AbortMagicWand.Store(true); + CurrentSelection = FSelectionManager(CurrentSelection.GetSelectionFlags().Num()); + MyPointCloud->SelectionFlags = CurrentSelection.GetSelectionFlags(); } // MAGIC WAND SELECTION @@ -311,10 +348,10 @@ void UMagicWand::InitMagicWandSelection() { ProceduralMesh->ClearAllMeshSections(); AbortMarchingCubes = true; - AbortMagicWand.Store(false); + //AbortMagicWand.Store(false); } -void UMagicWand::PerformMagicWandSelection() +void UMagicWand::PerformMagicWandSelection(const float ProximityThreshold) { if (MyPointCloud->SelectionFlags.Num() != MyPointCloud->PositionVectors.Num()) { @@ -322,13 +359,26 @@ void UMagicWand::PerformMagicWandSelection() return; } - //UE_LOG(LogTemp, Warning, TEXT("Starting MagicWand Selection!")); - - // Find the closest point to the input position as the seed - if (SeedPointIndex != INDEX_NONE) { - ExpandSelectionFromPointIndex(SeedPointIndex); + if(SelectionStartSound) + { + UGameplayStatics::PlaySound2D(World, SelectionStartSound); + } + + CurrentSelection = GetSelectionResultsFromProximity(ProximityThreshold); + CurrentSelection.SelectIndex(SeedPointIndex); + + TQueue<int32>* ProcessQueue = new TQueue<int32>{}; + + for (int32 Index : CurrentSelection.GetPointIndexList()) + { + ProcessQueue->Enqueue(Index); + } + + FMagicWandSelectionTask* Task = new FMagicWandSelectionTask(this, ProcessQueue, ProximityThreshold, CurrentSelection.SelectedCount()); + NumberThreads.Increment(); + Task->Start(); } } @@ -381,127 +431,111 @@ void UMagicWand::FindSeed(const FVector& InputPosition) } } -void UMagicWand::ExpandSelectionFromPointIndex(const int32 PointIndex) +void UMagicWand::ExpandFromAllPointsInQueue(TQueue<int32>* ProcessQueue, const float ProximityThreshold, int ThreadLoad) { - { - FScopeLock Lock(&ThreadNumberLock); - NumberThreads.Increment(); - } - - TQueue<int32>* ProcessQueue = new TQueue<int32>{}; - ProcessQueue->Enqueue(PointIndex); - MyPointCloud->SelectionFlags[PointIndex] = true; - - const float SquaredProximityThreshold = ProximityThreshold * ProximityThreshold; - int NumberToProcess = 0; - - while (!ProcessQueue->IsEmpty() && !AbortMagicWand.Load()) - { - if(NumberToProcess > 1000 && NumberThreads.GetValue() < MaxThreadCount) CreateNewExpansionThread(ProcessQueue, NumberToProcess); - - int32 CurrentQueuePointIndex; - ProcessQueue->Dequeue(CurrentQueuePointIndex); - const FVector CurrentQueuePointPosition = MyPointCloud->PositionVectors[CurrentQueuePointIndex]; - - ExpandThroughNeighborsInRange(ProcessQueue, SquaredProximityThreshold, NumberToProcess, CurrentQueuePointPosition); - NumberToProcess--; - } - - AbortSelection(); -} - -void UMagicWand::ExpandSelectionFromPointIndexQueue(TQueue<int32>* ProcessQueue) -{ - { - FScopeLock Lock(&ThreadNumberLock); - NumberThreads.Increment(); - } //UE_LOG(LogTemp, Warning, TEXT("Opened New Thread! Number of Threads now: %d"), NumberThreads.GetValue()); - const float SquaredProximityThreshold = ProximityThreshold * ProximityThreshold; - int NumberToProcess = 0; - - while (!ProcessQueue->IsEmpty() && !AbortMagicWand.Load()) + + TSet<int32> MyFullVoxelList; + MyFullVoxelList.Reserve(1000000); + FVoxelPointLookupTable MyVoxelPointLookupTable = *MyDensityField->VoxelPointLookupTable; + + while (!ProcessQueue->IsEmpty()) { - if(NumberToProcess > 1000 && NumberThreads.GetValue() < MaxThreadCount) CreateNewExpansionThread(ProcessQueue, NumberToProcess); + if(ThreadLoad > 1000 && NumberThreads.GetValue() < MaxThreadCount) CreateNewExpansionThread(ProcessQueue, ThreadLoad, ProximityThreshold); int32 CurrentQueuePointIndex; ProcessQueue->Dequeue(CurrentQueuePointIndex); - const FVector CurrentQueuePointPosition = MyPointCloud->PositionVectors[CurrentQueuePointIndex]; + + if(MyPointCloud && MyPointCloud->PositionVectors.IsValidIndex(CurrentQueuePointIndex)) + { + const FVector CurrentQueuePointPosition = MyPointCloud->PositionVectors[CurrentQueuePointIndex]; - ExpandThroughNeighborsInRange(ProcessQueue, SquaredProximityThreshold, NumberToProcess, CurrentQueuePointPosition); - NumberToProcess--; + ExpandFromPoint(ProcessQueue, ProximityThreshold, ThreadLoad, CurrentQueuePointPosition, &MyFullVoxelList, &MyVoxelPointLookupTable); + } + + ThreadLoad--; } - AbortSelection(); + FinishSelectionThread(ProximityThreshold); } -void UMagicWand::ExpandThroughNeighborsInRange(TQueue<int32>* ProcessQueue, const float SquaredProximityThreshold, int& NumberToProcess, const FVector& CurrentQueuePointPosition) const +void UMagicWand::ExpandFromPoint(TQueue<int32>* ProcessQueue, const float ProximityThreshold, int& ThreadLoad, const FVector& ExpansionPoint, TSet<int32>* MyFullVoxelList, FVoxelPointLookupTable* MyVoxelPointLookupTable) { - const int32 StartX = FMath::Max(0, FMath::FloorToInt((CurrentQueuePointPosition.X - ProximityThreshold - MyPointCloud->MinBounds.X) / MyDensityField->GetStep().X)); - const int32 EndX = FMath::Min(MyDensityField->GetXNum() - 1, FMath::FloorToInt((CurrentQueuePointPosition.X + ProximityThreshold - MyPointCloud->MinBounds.X) / MyDensityField->GetStep().X)); - const int32 StartY = FMath::Max(0, FMath::FloorToInt((CurrentQueuePointPosition.Y - ProximityThreshold - MyPointCloud->MinBounds.Y) / MyDensityField->GetStep().Y)); - const int32 EndY = FMath::Min(MyDensityField->GetYNum() - 1, FMath::FloorToInt((CurrentQueuePointPosition.Y + ProximityThreshold - MyPointCloud->MinBounds.Y) / MyDensityField->GetStep().Y)); - const int32 StartZ = FMath::Max(0, FMath::FloorToInt((CurrentQueuePointPosition.Z - ProximityThreshold - MyPointCloud->MinBounds.Z) / MyDensityField->GetStep().Z)); - const int32 EndZ = FMath::Min(MyDensityField->GetZNum() - 1, FMath::FloorToInt((CurrentQueuePointPosition.Z + ProximityThreshold - MyPointCloud->MinBounds.Z) / MyDensityField->GetStep().Z)); - + const FVector Step = MyDensityField->GetStep(); + const int32 StartX = FMath::Max(0, FMath::FloorToInt((ExpansionPoint.X - ProximityThreshold - MyPointCloud->MinBounds.X) / Step.X)); + const int32 EndX = FMath::Min(MyDensityField->GetXNum() - 1, FMath::FloorToInt((ExpansionPoint.X + ProximityThreshold - MyPointCloud->MinBounds.X) / Step.X)); + const int32 StartY = FMath::Max(0, FMath::FloorToInt((ExpansionPoint.Y - ProximityThreshold - MyPointCloud->MinBounds.Y) / Step.Y)); + const int32 EndY = FMath::Min(MyDensityField->GetYNum() - 1, FMath::FloorToInt((ExpansionPoint.Y + ProximityThreshold - MyPointCloud->MinBounds.Y) / Step.Y)); + const int32 StartZ = FMath::Max(0, FMath::FloorToInt((ExpansionPoint.Z - ProximityThreshold - MyPointCloud->MinBounds.Z) / Step.Z)); + const int32 EndZ = FMath::Min(MyDensityField->GetZNum() - 1, FMath::FloorToInt((ExpansionPoint.Z + ProximityThreshold - MyPointCloud->MinBounds.Z) / Step.Z)); + + const float SquaredProximityThreshold = ProximityThreshold * ProximityThreshold; + for (int32 Z = StartZ; Z <= EndZ; ++Z) { for (int32 Y = StartY; Y <= EndY; ++Y) { for (int32 X = StartX; X <= EndX; ++X) { - if(AbortMagicWand.Load()) return; const int32 CurrentVoxelComparisonIndex = MyDensityField->GridPositionToIndex(X, Y, Z); - - if(!MyDensityField->IsValidIndex(CurrentVoxelComparisonIndex)) + + if(MyFullVoxelList->Contains(CurrentVoxelComparisonIndex)) { continue; } + + TArray<int32> PointIndices = MyVoxelPointLookupTable->GetPointsInVoxel(CurrentVoxelComparisonIndex); - TArray<int32> PointIndices = MyDensityField->VoxelPointLookupTable->GetPointsInVoxel(CurrentVoxelComparisonIndex); - + int Selected = 0; for(const int32 CurrentPointComparisonIndex : PointIndices) { - if(MyPointCloud->SelectionFlags[CurrentPointComparisonIndex]) + if(CurrentSelection.IsSelected(CurrentPointComparisonIndex)) { + Selected++; continue; } FVector CurrentComparisonPosition = MyPointCloud->PositionVectors[CurrentPointComparisonIndex]; - const double SquaredDistance = FVector::DistSquared(CurrentQueuePointPosition, CurrentComparisonPosition); + const double SquaredDistance = FVector::DistSquared(ExpansionPoint, CurrentComparisonPosition); if (SquaredDistance <= SquaredProximityThreshold) { ProcessQueue->Enqueue(CurrentPointComparisonIndex); - NumberToProcess++; + ThreadLoad++; + Selected++; - if(AbortMagicWand.Load()) return; - - MyPointCloud->SelectionFlags[CurrentPointComparisonIndex] = true; + CurrentSelection.SelectIndex(CurrentPointComparisonIndex); } } + + if(Selected == PointIndices.Num()) + { + MyFullVoxelList->Add(CurrentVoxelComparisonIndex); + } } } } } -void UMagicWand::AbortSelection() +void UMagicWand::FinishSelectionThread(const float ProximityThreshold) { NumberThreads.Decrement(); //UE_LOG(LogTemp, Warning, TEXT("Thread Closed! Number of Threads now: %d"), NumberThreads.GetValue()); if(NumberThreads.GetValue() == 0) { - //AbortMagicWand.Store(false); - //UE_LOG(LogTemp, Warning, TEXT("Aborted!")); + CacheSelectionResults(ProximityThreshold, CurrentSelection); + + //UE_LOG(LogTemp, Error, TEXT("Finished!")); }else if(NumberThreads.GetValue() < 0) { UE_LOG(LogTemp, Error, TEXT("More Threads closed than opened!")); } } -void UMagicWand::CreateNewExpansionThread(TQueue<int32>* ProcessQueue, int& NumberToProcess) +void UMagicWand::CreateNewExpansionThread(TQueue<int32>* ProcessQueue, int& NumberToProcess, const float ProximityThreshold) { + NumberThreads.Increment(); TQueue<int32>* TempQueue = new TQueue<int32>{}; const int NumberRemove = NumberToProcess / 2; @@ -513,10 +547,42 @@ void UMagicWand::CreateNewExpansionThread(TQueue<int32>* ProcessQueue, int& Numb TempQueue->Enqueue(CurrentQueuePointIndex); } - AsyncTask(ENamedThreads::Type::AnyBackgroundHiPriTask, [this, TempQueue]() + FMagicWandSelectionTask* Task = new FMagicWandSelectionTask(this, TempQueue, ProximityThreshold, NumberRemove); + Task->Start(); +} + +void UMagicWand::CacheSelectionResults(const float ProximityThreshold, const FSelectionManager& SelectionResults) +{ + FScopeLock Lock(&SelectionCacheLock); + // Check if the proximity range is already present + if (!SortedProximityRanges.Contains(ProximityThreshold)) { - ExpandSelectionFromPointIndexQueue(TempQueue); - }); + // Add new range to the array and sort it + SortedProximityRanges.Add(ProximityThreshold); + SortedProximityRanges.Sort(); + + // Update the cache with new indices for a given proximity range + SelectionCache.Add(ProximityThreshold, SelectionResults); + } +} + +FSelectionManager UMagicWand::GetSelectionResultsFromProximity(const float ProximityThreshold) +{ + FScopeLock Lock(&SelectionCacheLock); + + for(int i = SortedProximityRanges.Num() - 1; i >= 0; --i) + { + const float CurrentRange = SortedProximityRanges[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]; + } + } + + //UE_LOG(LogTemp, Error, TEXT("No cached selection found for proximity range %f"), ProximityThreshold); + return FSelectionManager(MyPointCloud->SelectionFlags.Num()); } // TICK @@ -525,16 +591,21 @@ void UMagicWand::TickComponent(const float DeltaTime, const ELevelTick TickType, { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); - /* - AccumulatedTime.Store(AccumulatedTime.Load() + DeltaTime); - ProceduralMesh->SetVisibility(Select);*/ + CountSelectionTime += DeltaTime; + + if(!Select) + { + MarchingCubeMesh->SetVisibility(false); + } } void UMagicWand::SelectParticles(const FVector& InputPosition) { + if(!IsMagicWandInitialized) return; + const FVector CurrentSelectionPositionWorld = SelectionObject->GetComponentLocation(); const FVector CurrentSelectionPositionLocal = MyPointCloud->PointCloudVisualizer->GetComponentTransform().InverseTransformPosition(CurrentSelectionPositionWorld); - ProximityThreshold = FMath::Clamp(FVector::Dist(SeedPointPositionLocal, CurrentSelectionPositionLocal) / ThresholdDistanceScaling, MinThreshold, MaxThreshold); + const float ProximityThreshold = FMath::Clamp(FVector::Dist(SeedPointPositionLocal, CurrentSelectionPositionLocal) / ThresholdDistanceScaling, MinThreshold, MaxThreshold); const FVector SelectionDirectionLocal = CurrentSelectionPositionLocal - SeedPointPositionLocal; const FVector ProximityEndPointLocal = SeedPointPositionLocal + (SelectionDirectionLocal.GetSafeNormal() * ProximityThreshold); @@ -544,4 +615,28 @@ void UMagicWand::SelectParticles(const FVector& InputPosition) GenerateCylinderBetweenPoints(SelectionStartPositionWorld, ProximityEndPointWorld, 0.05, 8, FColor::Red, 1); GenerateCylinderBetweenPoints((CurrentSelectionPositionWorld - ProximityEndPointWorld).GetSafeNormal() * 0.05 + ProximityEndPointWorld, CurrentSelectionPositionWorld, 0.05, 8, FColor::Green, 0); + + 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 diff --git a/Source/MetaCastBachelor/MagicWand.h b/Source/MetaCastBachelor/MagicWand.h index 683796acda160ae94acbef6a60ae2811326ea7ba..5137d0fc493030e5985dc89b12e7462112ff733b 100644 --- a/Source/MetaCastBachelor/MagicWand.h +++ b/Source/MetaCastBachelor/MagicWand.h @@ -1,6 +1,7 @@ #pragma once #include "CoreMinimal.h" +#include "MagicWandSelectionManager.h" #include "ProceduralMeshComponent.h" #include "MetaCastBaseline.h" #include "Generators/MarchingCubes.h" @@ -12,28 +13,49 @@ class UMagicWand : public UMetaCastBaseline { GENERATED_BODY() - // INTERNAL + // MAGIC WAND TAtomic<bool> AbortMagicWand = false; + FSelectionManager CurrentSelection; + TMap<float, FSelectionManager> SelectionCache; + TArray<float> SortedProximityRanges; + FVector SeedPointPositionLocal; FDensityField* MyDensityField; - bool IsMarchingCubesRunning = false; - bool AbortMarchingCubes = false; - FThreadSafeCounter NumberThreads = 0; + float CountSelectionTime = 0; + bool IsMagicWandInitialized = false; + + // THREADING + mutable FCriticalSection ProceduralMeshGuard; + mutable FCriticalSection SelectionCacheLock; mutable FCriticalSection ThreadNumberLock; - UPROPERTY() - UProceduralMeshComponent* ProceduralMesh; + mutable FThreadSafeCounter NumberThreads = 0; + + // VISUALIZATION + + bool IsMarchingCubesRunning = false; + bool AbortMarchingCubes = false; + float MarchingCubeSize = 0; + + // REFERENCES + UPROPERTY() UWorld* World; - FVector SeedPointPositionLocal; + UPROPERTY() ULineBatchComponent* MyLineBatchComponent; - float ProximityThreshold = 0.1f; - float MarchingCubeSize = 0; - int32 SeedPointIndex; + + UPROPERTY() + UProceduralMeshComponent* ProceduralMesh; - // USER EXPORTED + UPROPERTY() + UProceduralMeshComponent* MarchingCubeMesh; + + // USER EDITABLE PROPERTIES + UPROPERTY(EditAnywhere) + float MagicWandUpdatesPerSecond = 3; + UPROPERTY(EditAnywhere) float ThresholdDistanceScaling = 20; @@ -48,8 +70,19 @@ class UMagicWand : public UMetaCastBaseline UPROPERTY(EditAnywhere) int MaxThreadCount = 100; - + + UPROPERTY(EditAnywhere) + bool UpdateMagicWandDuringDrag = false; + + UPROPERTY(EditAnywhere) + USoundWave* SelectionEndSound; + + UPROPERTY(EditAnywhere) + USoundWave* SelectionStartSound; + public: + int32 SeedPointIndex; + // INITIALIZATION UMagicWand(); @@ -67,19 +100,19 @@ public: // VISUALIZATION void GenerateVoxelMeshWithCubes(const TArray<int32> Voxels) const; - void GenerateVoxelMeshSmooth(const TArray<int32> Voxels); + void GenerateVoxelMeshSmooth(const TSet<int32> Voxels); // MAGIC WAND SELECTION void InitMagicWandSelection(); - void FindSeedIndex(const FVector& InputPosition, int32& SeedIndex) const; - void PerformMagicWandSelection(); + void PerformMagicWandSelection(const float ProximityThreshold); void FindSeed(const FVector& InputPosition); - void ExpandSelectionFromPointIndex(int32 PointIndex); - void ExpandSelectionFromPointIndexQueue(TQueue<int32>* ProcessQueue); - void ExpandThroughNeighborsInRange(TQueue<int32>* ProcessQueue, const float SquaredProximityThreshold, int& NumberToProcess, const FVector& CurrentQueuePointPosition) const; - void AbortSelection(); - void CreateNewExpansionThread(TQueue<int32>* ProcessQueue, int& NumberToProcess); + 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 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); // TICK diff --git a/Source/MetaCastBachelor/MagicWandSelectionManager.h b/Source/MetaCastBachelor/MagicWandSelectionManager.h new file mode 100644 index 0000000000000000000000000000000000000000..2c88a4234b84f48eb83bdcc9e0e1e8dbbd7db286 --- /dev/null +++ b/Source/MetaCastBachelor/MagicWandSelectionManager.h @@ -0,0 +1,109 @@ +#pragma once +#include "CoreMinimal.h" + +class FSelectionManager +{ + 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) + { + SelectionFlags.Init(false, NumElements); + PointIndexList.Reserve(NumElements); // Preallocate space to optimize appending + } + + explicit FSelectionManager(const TArray<bool>& InSelectionFlags) : SelectionFlags(InSelectionFlags) + { + SelectionCount = 0; + for (int i = 0; i < InSelectionFlags.Num(); i++) + { + if (InSelectionFlags[i]) + { + PointIndexList.Add(i); + SelectionCount++; + } + } + } + + FSelectionManager(): SelectionCount(0) {} + + + /** Adds an index to the selection if it isn't already selected */ + void SelectIndex(const int32 Index) + { + if (!SelectionFlags[Index]) + { + PointIndexList.Add(Index); + SelectionFlags[Index] = true; + SelectionCount++; + } + } + + /** Removes a specific index from the selection */ + bool UnselectIndex(const int32 IndexToRemove) + { + if (IsValidIndex(IndexToRemove) && SelectionFlags[IndexToRemove]) + { + const int32 RemoveAt = PointIndexList.IndexOfByKey(IndexToRemove); + if (RemoveAt != INDEX_NONE) + { + PointIndexList.RemoveAt(RemoveAt); + SelectionFlags[IndexToRemove] = false; + SelectionCount--; + return true; + } + } + return false; + } + + /** Returns true if any indices are selected */ + bool IsAnythingSelected() const + { + return SelectionCount > 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; + } + + /** Returns the number of selected indices */ + int32 SelectedCount() const + { + return SelectionCount; + } + + /** Checks if the given index is within the valid range */ + bool IsValidIndex(const int32 Index) const + { + return Index >= 0 && Index < SelectionFlags.Num(); + } + + /** Returns the number of indices in the selection list (for debugging) */ + int32 QueueSize() const + { + return PointIndexList.Num(); + } + + const TArray<int32>& GetPointIndexList() const + { + return PointIndexList; + } + + const TArray<bool>& GetSelectionFlags() const + { + return SelectionFlags; + } +}; \ No newline at end of file diff --git a/Source/MetaCastBachelor/PointCloud.cpp b/Source/MetaCastBachelor/PointCloud.cpp index a5f6813ffe3a9127732fc9946fb34915cc576035..0eb4c858e7b6153576a83ac6fc77c9203387fbae 100644 --- a/Source/MetaCastBachelor/PointCloud.cpp +++ b/Source/MetaCastBachelor/PointCloud.cpp @@ -20,9 +20,9 @@ void APointCloud::BeginPlay() InitPointCloudVisualizer(); SetupDensityFieldFromPointCloud(); - UKdtreeBPLibrary::BuildKdtree(MyKdTree, PositionVectors); + //UKdtreeBPLibrary::BuildKdtree(MyKdTree, PositionVectors); //UKdtreeBPLibrary::DumpKdtreeToConsole(MyKdTree); - UKdtreeBPLibrary::ValidateKdtree(MyKdTree); + //UKdtreeBPLibrary::ValidateKdtree(MyKdTree); } void APointCloud::ReadPointCloudFromFile(const FFilePath FileNamePoints, const FFilePath FileNameFlags) diff --git a/Source/MetaCastBachelor/VoxelPointLookupTable.h b/Source/MetaCastBachelor/VoxelPointLookupTable.h index 52076a383f10db918ba8b2d2209b593999648435..43553c3fbae6f1c5893d918ae51f3e84c045ea3b 100644 --- a/Source/MetaCastBachelor/VoxelPointLookupTable.h +++ b/Source/MetaCastBachelor/VoxelPointLookupTable.h @@ -8,6 +8,19 @@ class FVoxelPointLookupTable public: FVoxelPointLookupTable(); + + FVoxelPointLookupTable(const FVoxelPointLookupTable& Other) + : VoxelToPointIndicesMap(Other.VoxelToPointIndicesMap), LinkedDensityField(Other.LinkedDensityField) + {} + + FVoxelPointLookupTable& operator=(const FVoxelPointLookupTable& Other) + { + if (this != &Other) { + VoxelToPointIndicesMap = Other.VoxelToPointIndicesMap; + LinkedDensityField = Other.LinkedDensityField; + } + return *this; + } virtual ~FVoxelPointLookupTable();