From ef6972606e6e6d4693c63c1c863fd2e64fee047f Mon Sep 17 00:00:00 2001 From: Timon Roemer <t.roemer@vis.rwth-aachen.de> Date: Wed, 17 Jul 2024 18:15:29 +0200 Subject: [PATCH] Starts implementing MagicWand --- Content/MagicWandMap.umap | Bin 0 -> 43101 bytes Content/MagicWandPawn.uasset | Bin 0 -> 68061 bytes Content/MetaPointMap.umap | Bin 45108 -> 45078 bytes Source/MetaCastBachelor/DensityField.cpp | 36 ++- Source/MetaCastBachelor/DensityField.h | 8 +- Source/MetaCastBachelor/MagicWand.cpp | 315 +++++++++++++++++++++++ Source/MetaCastBachelor/MagicWand.h | 57 ++++ Source/MetaCastBachelor/MetaPoint.cpp | 196 ++++++++------ Source/MetaCastBachelor/MetaPoint.h | 1 + Source/MetaCastBachelor/PointCloud.cpp | 2 +- Source/MetaCastBachelor/PointCloud.h | 2 +- Source/MetaCastBachelor/Utilities.cpp | 4 +- 12 files changed, 524 insertions(+), 97 deletions(-) create mode 100644 Content/MagicWandMap.umap create mode 100644 Content/MagicWandPawn.uasset create mode 100644 Source/MetaCastBachelor/MagicWand.cpp create mode 100644 Source/MetaCastBachelor/MagicWand.h diff --git a/Content/MagicWandMap.umap b/Content/MagicWandMap.umap new file mode 100644 index 0000000000000000000000000000000000000000..432e39ca2127b23880bf35d2220201a73ca86de3 GIT binary patch literal 43101 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�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 JHiQXM4**AMl}!Ku literal 0 HcmV?d00001 diff --git a/Content/MagicWandPawn.uasset b/Content/MagicWandPawn.uasset new file mode 100644 index 0000000000000000000000000000000000000000..9472186a5940e9e6147f87a741f1b214548baeff GIT binary patch literal 68061 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 R7J)3M_G^NQSCAM;JpgmlwgvzI literal 0 HcmV?d00001 diff --git a/Content/MetaPointMap.umap b/Content/MetaPointMap.umap index a90f205a3061d3582938745c1b4c3550588e57fa..e65a8b10eadfdfb9712a93fbbe490b559b6eb5ed 100644 GIT binary patch delta 712 zcmdn;fN9zTrVV!(1y0K>4tlaj(R1<@Gl#pY`En+|VJu*Jw0?3NlN8gl^_#1hE;ut5 zO!jt5XOc^p>=-IF`J9^z(+LPO&skxzpSucU(BxwGc*YBpFT2MxB_vOdah96w=i$QC zn+(>l&clW2FGQ5j(}hVZ1uP2E+vlmm6r2fGf6mi|sUZ_C<L9Nq#G3`?_IbH5ZHF*H zdicCmn6~ADW&FHd7-c3;_Ks(YEd&eX`4~*L^ATZUFNShmnASp>&I*&?`KU0Jmw;vZ ze03&A`ARVDfN@=zUQ~c(z*hLFF#V_iJIl|{g-Nv%EVItfg(;^REXwEa!ZZ`Y%=33) zItF2$^LJtTTn$zWGQ}@Ig-N#-tiCV6g=u{)Sms@T3sYSklxZ-zE>MI?rykC|7bwDX z3&Q0Ka$)LdfXe7ho)sj)B-scy?Oc!xlU@s0MlaZf$*~*E1X;N*ScPfJIxzQLunW@x z7}H>KU5E%%;CiqONMXQSmd%$!qM5kNbq!7R3=H&4Ehn3YFBABA|JkvH@6USA4Y?OC zW_#qq=C|QuY|IJ{s*@ELNlccD6JgAnd^gU3%aZ{LTqozo*{GIe6s4xd7iH$97c+c? z%B+UcI*brTE?A=vgu}q#F*!HRe6n9W8#j{X&3W;)LafHd#`aGqFI*(CnWJbuqX2tP g>`Rw~i+-z|{MLV%{a1RjVzI&GfKaK;f+fl}06Qf4?*IS* delta 738 zcmbRCfN9GErVV!(1zZo@o;}Tp({cJcTj@8ZQ`;xMVJu)`*f2ScNs5VO!{#cc3(kyv zlfB*2nQRg!Gloh{&U03n{LW2<=^2Ep=kCH-FuB-0p7Gt}%kJ?^b;*-soTVoFdAKmG zPKIkZ=b^$RodTBO^K@bINP)}rd8#lKW`bqTdAcyo%LL2ldATrYXMvd@`__4>FkOXk z-+8$(UCD*Z<aw(wT1}qp9nVx<2odx#m~7`G!lYUZ<+?DPhB848_fcV*S^}2o^VOLg z<txE-4aRk0VygtpfNbRRQ(+RT1Ut*m&xOeuBD2oVg{i9=EXwEa!n7U2%=33)dIDjd z^LJqqtbv>27ofuAT?<y<7vRElwiYbo7ich9E>MJNP92o%!sJyCWg1Mr7bwE?1H$DC za$#E50F}|1JS#|o$-EJ4+PNSXCZ86tj9#z{Q+zj=*%$1>bZH%!=@()!SuR9`=^l*h z!j!)rECcf0he<4(FNH)iahd5Fn(CPv=@}VJHV<DWFk{}~;KPZhyf<099zIxiMtbwx za4|M!27A@Xg|j3k%f*Q>R!%+_XTTM~00kkFbK`7Git`c+;++kQ;*AUp;th?=;}i2T za}!H4^Ya+KLKUut(mIR~Mh)1UXb6XaA$)RfocUz?cs42AW^E3LuN7iWOH12)q3|T5 mfOXAjqcd`Wt_`jyr>@#K(|s~yqr{{x$w^(3n?DyT+W-J&p!Mwl diff --git a/Source/MetaCastBachelor/DensityField.cpp b/Source/MetaCastBachelor/DensityField.cpp index 6c6af6b..0af1af8 100644 --- a/Source/MetaCastBachelor/DensityField.cpp +++ b/Source/MetaCastBachelor/DensityField.cpp @@ -5,7 +5,7 @@ // INITIALIZATION FUNCTIONS -FDensityField::FDensityField(): XNum(0), YNum(0), ZNum(0), XStep(0), YStep(0), ZStep(0), VoxelPointLookupTable(nullptr) +FDensityField::FDensityField(): XNum(0), YNum(0), ZNum(0), XStep(0), YStep(0), ZStep(0), MaxDensity(0), MinDensity(0), AvgDensity(0), VoxelPointLookupTable(nullptr) { VoxelList = TArray<FVoxel>(); } @@ -17,8 +17,8 @@ void FDensityField::InitializeDensityField(const APointCloud* PointCloud, const VoxelPointLookupTable = new FVoxelPointLookupTable(); VoxelPointLookupTable->Initialize(PointCloud->PositionVectors, this); - CalculateVoxelDensities_2(PointCloud, PointCloud->InfluenceRadius); - //CalculateVoxelDensitiesByNumber(PointCloud, 4); + //CalculateVoxelDensities_2(PointCloud, PointCloud->InfluenceRadius); + CalculateVoxelDensitiesSimple(PointCloud, PointCloud->InfluenceRadius); CalculateAndStoreGradients(); } @@ -101,7 +101,7 @@ void FDensityField::CalculateVoxelDensities(const APointCloud* PointCloud, const } } } - double MaxDensity = 0.0; + MaxDensity = 0.0; for (const auto& Node : VoxelList) { if (Node.GetVoxelDensity() > MaxDensity) @@ -118,7 +118,7 @@ void FDensityField::CalculateVoxelDensities(const APointCloud* PointCloud, const } -void FDensityField::CalculateVoxelDensitiesByNumber(const APointCloud* PointCloud, const float InfluenceRadius) +void FDensityField::CalculateVoxelDensitiesSimple(const APointCloud* PointCloud, const float InfluenceRadius) { if (!PointCloud) return; @@ -160,7 +160,8 @@ void FDensityField::CalculateVoxelDensitiesByNumber(const APointCloud* PointClou if (Distance < InfluenceRadius) { - VoxelList[Index].AddToVoxelDensity(1.0); // Simply increment the density count + const float Weight = FUtilities::SmoothingKernel(Distance, InfluenceRadius); + VoxelList[Index].AddToVoxelDensity(Weight); // Simply increment the density count } } } @@ -168,15 +169,21 @@ void FDensityField::CalculateVoxelDensitiesByNumber(const APointCloud* PointClou } } - double MaxDensity = 0.0; + MaxDensity = MIN_flt; + MinDensity = MAX_FLT; + AvgDensity = 0; for (const auto& Node : VoxelList) { - if (Node.GetVoxelDensity() > MaxDensity) - { - MaxDensity = Node.GetVoxelDensity(); - } + const float Dens = Node.GetVoxelDensity(); + MaxDensity = FMath::Max(Dens, MaxDensity); + MinDensity = FMath::Min(Dens, MinDensity); + AvgDensity += Dens; } - UE_LOG(LogTemp, Log, TEXT("Maximum density found: %f"), MaxDensity); + AvgDensity /= VoxelList.Num(); + + UE_LOG(LogTemp, Log, TEXT("Maximum density found: %f"), MaxDensity); + UE_LOG(LogTemp, Log, TEXT("Minimum density found: %f"), MinDensity); + UE_LOG(LogTemp, Log, TEXT("Average density found: %f"), AvgDensity); const double EndTime = FPlatformTime::Seconds(); // End timing const double ElapsedTime = EndTime - StartTime; // Calculate elapsed time @@ -412,14 +419,15 @@ void FDensityField::CalculateAndStoreGradients() // DEBUG FUNCTIONS -void FDensityField::DrawDebugVoxelDensity(const UWorld* World, const AActor* Actor, const float Duration, const float Scale, const float DensityThreshold) const +void FDensityField::DrawDebugVoxelDensity(const UWorld* World, const AActor* Actor, const float Duration, const float Scale, const float DensityThreshold) { if (!World || !Actor) { return; } - double MinDensity = DBL_MAX, MaxDensity = 0; + MinDensity = DBL_MAX; + MaxDensity = 0; for (const FVoxel& Node : VoxelList) { const double LogDensity = log10(Node.GetVoxelDensity() + 1); // Avoiding log(0) diff --git a/Source/MetaCastBachelor/DensityField.h b/Source/MetaCastBachelor/DensityField.h index 5a3cd26..1e86417 100644 --- a/Source/MetaCastBachelor/DensityField.h +++ b/Source/MetaCastBachelor/DensityField.h @@ -42,12 +42,18 @@ class FDensityField float XStep, YStep, ZStep; FVector GridOrigin; + + void CalculateVoxelDensities(const APointCloud* PointCloud, float InfluenceRadius, float Sigma); - void CalculateVoxelDensitiesByNumber(const APointCloud* PointCloud, float InfluenceRadius); + void CalculateVoxelDensitiesSimple(const APointCloud* PointCloud, float InfluenceRadius); void CalculateAndStoreGradients(); + void DrawDebugVoxelDensity(const UWorld* World, const AActor* Actor, float Duration, float Scale, float DensityThreshold); void InitializeDataStructures(const FVector& MinBounds, const FVector& MaxBounds, int32 XAxisNum, int32 YAxisNum, int32 ZAxisNum); public: + double MaxDensity; + double MinDensity; + double AvgDensity; FVoxelPointLookupTable* VoxelPointLookupTable; mutable FCriticalSection DataGuard; // CONSTRUCTOR diff --git a/Source/MetaCastBachelor/MagicWand.cpp b/Source/MetaCastBachelor/MagicWand.cpp new file mode 100644 index 0000000..360602a --- /dev/null +++ b/Source/MetaCastBachelor/MagicWand.cpp @@ -0,0 +1,315 @@ +#include "MagicWand.h" +#include "Utilities.h" +#include "Generators/MarchingCubes.h" + +UMagicWand::UMagicWand() : World(nullptr) +{ + // Create the procedural mesh component + ProceduralMesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("GeneratedMesh")); + SelectedClusterIndices.Reserve(100000); +} + +void UMagicWand::BeginPlay() +{ + Super::BeginPlay(); + + World = GetWorld(); + + if (!World) { + UE_LOG(LogTemp, Warning, TEXT("Invalid world provided.")); + } + + ProceduralMesh->AttachToComponent(MyPointCloud->PointCloudVisualizer, FAttachmentTransformRules::SnapToTargetIncludingScale); + ProceduralMesh->SetMaterial(0, SelectionVolumeMat); + ProceduralMesh->SetMaterial(1, SelectionVolumeMat); + ProceduralMesh->SetMaterial(2, SelectionVolumeMat); + ProceduralMesh->bCastDynamicShadow = false; + ProceduralMesh->bRenderCustomDepth = true; + ProceduralMesh->SetCastShadow(false); + ProceduralMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); + ProceduralMesh->SetMobility(EComponentMobility::Movable); + ProceduralMesh->SetVisibility(true); + +} + +void UMagicWand::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + AccumulatedTime += DeltaTime; + + ProceduralMesh->SetVisibility(Select); + + if (Select) + { + PerformMagicWandSelection(SelectionObject->GetComponentLocation()); + } +} + +void UMagicWand::PerformMagicWandSelection(const FVector& InputPosition) +{ + if (MyPointCloud->SelectionFlags.Num() != MyPointCloud->PositionVectors.Num()) + { + UE_LOG(LogTemp, Warning, TEXT("PerformMagicWandSelection: Positions and SelectionFlags array sizes do not match.")); + return; + } + + // Find the closest point to the input position as the seed + int32 SeedIndex = INDEX_NONE; + float MinDistance = FLT_MAX; + + for (int32 i = 0; i < MyPointCloud->PositionVectors.Num(); i++) + { + FVector CurrentPoint = MyPointCloud->PositionVectors[i]; + CurrentPoint = MyPointCloud->PointCloudVisualizer->GetComponentTransform().TransformPosition(CurrentPoint); + + const float Distance = FVector::Dist(InputPosition, CurrentPoint); + if (Distance < MinDistance) + { + MinDistance = Distance; + SeedIndex = i; + } + } + + if (SeedIndex != INDEX_NONE) + { + // Clear previous selection + SelectedClusterIndices.Empty(); + for (bool& Flag : MyPointCloud->SelectionFlags) + { + Flag = false; + } + + // Expand selection from the seed + ExpandSelection(SeedIndex); + + for (int32 i = 0; i < SelectedClusterIndices.Num(); i++) + { + const int Index = SelectedClusterIndices[i]; + MyPointCloud->SelectionFlags[Index] = true; + } + } +} + +void UMagicWand::ExpandSelection(const int32 SeedIndex) +{ + TQueue<int32> ToProcess; + ToProcess.Enqueue(SeedIndex); + SelectedClusterIndices.Add(SeedIndex); + MyPointCloud->SelectionFlags[SeedIndex] = true; + + while (!ToProcess.IsEmpty()) + { + int32 CurrentIndex; + ToProcess.Dequeue(CurrentIndex); + FVector CurrentPoint = MyPointCloud->PositionVectors[CurrentIndex]; + CurrentPoint = MyPointCloud->PointCloudVisualizer->GetComponentTransform().TransformPosition(CurrentPoint); + + for (int32 i = 0; i < MyPointCloud->PositionVectors.Num(); i++) + { + if (!MyPointCloud->SelectionFlags[i]) + { + FVector Point = MyPointCloud->PositionVectors[i]; + Point = MyPointCloud->PointCloudVisualizer->GetComponentTransform().TransformPosition(Point); + + if (FVector::Dist(CurrentPoint, Point) <= ProximityThreshold) + { + ToProcess.Enqueue(i); + SelectedClusterIndices.Add(i); + MyPointCloud->SelectionFlags[i] = true; + } + } + } + } +} + + +void UMagicWand::EraseParticles(const FVector& InputPosition) +{ + +} + +void UMagicWand::HandleMetaSelectReleased(const FInputActionInstance& Instance) +{ + Super::HandleMetaSelectReleased(Instance); + + //ProceduralMesh->ClearAllMeshSections(); + //ProceduralMesh->SetVisibility(false); + + + AsyncTask(ENamedThreads::Type::AnyBackgroundHiPriTask, [this]() + { + MyPointCloud->ColorPointsInVoxels(FloodedIndices); + }); +} + +void UMagicWand::HandleMetaSelectPressed(const FInputActionInstance& Instance) +{ + Super::HandleMetaSelectPressed(Instance); + + UE_LOG(LogTemp, Warning, TEXT("PerformMagicWandSelection")); + InitMagicWandSelection(); +} + +void UMagicWand::InitMagicWandSelection() +{ + const FVector SelectionWorldPosition = SelectionObject->GetComponentLocation(); + MyDensityField = MyPointCloud->MyDensityField; + + // Convert the world position of the selection object to the local position relative to the point cloud + const FVector SelectionLocalPosition = MyPointCloud->PointCloudVisualizer->GetComponentTransform().InverseTransformPosition(SelectionWorldPosition); + + ProceduralMesh->ClearAllMeshSections(); + World = GetWorld(); + + AbortMarchingCubes = true; + AccumulatedTime = 0.0f; +} + +void UMagicWand::SelectParticles(const FVector& InputPosition) +{ + if (AccumulatedTime >= 1 / EvaluationsPerSecond) + { + AccumulatedTime = -10000; + + AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this]() + { + const FVector SelectionWorldPosition = SelectionObject->GetComponentLocation(); + + + }); + } +} + +void UMagicWand::GenerateVoxelMeshWithCubes(const TArray<int32> Voxels) const +{ + TArray<FVector> Vertices; + TArray<int32> Triangles; + TArray<FColor> VertexColors; + + // Generate cube vertices and triangles for each voxel + for (const int32 VoxelIndex : Voxels) + { + if(!MyDensityField->IsValidIndex(VoxelIndex)) + { + continue; + } + + TArray<FVector> VoxelCorners = MyDensityField->IndexToVoxelCornersWorld(VoxelIndex); + + // Add vertices for the voxel + const int32 BaseIndex = Vertices.Num(); + Vertices.Append(VoxelCorners); + + // Define the triangles (12 triangles for 6 faces of the cube) + // Each face of the cube has 2 triangles (6 faces * 2 triangles per face = 12 triangles) + Triangles.Append({ + BaseIndex + 0, BaseIndex + 1, BaseIndex + 2, BaseIndex + 0, BaseIndex + 2, BaseIndex + 3, // Bottom face + BaseIndex + 4, BaseIndex + 6, BaseIndex + 5, BaseIndex + 4, BaseIndex + 7, BaseIndex + 6, // Top face + BaseIndex + 0, BaseIndex + 4, BaseIndex + 1, BaseIndex + 1, BaseIndex + 4, BaseIndex + 5, // Front face + BaseIndex + 1, BaseIndex + 5, BaseIndex + 2, BaseIndex + 2, BaseIndex + 5, BaseIndex + 6, // Right face + BaseIndex + 2, BaseIndex + 6, BaseIndex + 3, BaseIndex + 3, BaseIndex + 6, BaseIndex + 7, // Back face + BaseIndex + 3, BaseIndex + 7, BaseIndex + 0, BaseIndex + 0, BaseIndex + 7, BaseIndex + 4 // Left face + }); + + // Add red color for each corner vertex + for (int32 i = 0; i < 8; ++i) + { + VertexColors.Add(FColor::Green); + } + } + + // Create the mesh section + AsyncTask(ENamedThreads::Type::GameThread, [this, Vertices, Triangles, VertexColors]() + { + //CreateMeshSections(Vertices, Triangles, VertexColors, 1000); + ProceduralMesh->CreateMeshSection(0, Vertices, Triangles, TArray<FVector>(), TArray<FVector2D>(), VertexColors, TArray<FProcMeshTangent>(), false); + ProceduralMesh->SetVisibility(true); + }); +} + +void UMagicWand::GenerateVoxelMeshSmooth(const TArray<int32> Voxels) +{ + if(IsMarchingCubesRunning) + return; + + IsMarchingCubesRunning = true; + + FScopeLock Lock(&ProceduralMeshGuard); + AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this, Voxels]() + { + FVector Min(FLT_MAX, FLT_MAX, FLT_MAX); + FVector Max(-FLT_MAX, -FLT_MAX, -FLT_MAX); + + for(const int FloodedIndex : Voxels) + { + Min = FVector::Min(Min, MyDensityField->IndexToVoxelPosition(FloodedIndex)); + Max = FVector::Max(Max, MyDensityField->IndexToVoxelPosition(FloodedIndex)); + } + + UE::Geometry::FMarchingCubes MarchingCubes; + MarchingCubes.Bounds = UE::Geometry::TAxisAlignedBox3(Min, Max); + MarchingCubes.CubeSize = MarchingCubeSize == 0 ? MyDensityField->GetStep().X : MarchingCubeSize; + MarchingCubes.CancelF = [this]() { + return AbortMarchingCubes; + }; + MarchingCubes.IsoValue = 0; + + AbortMarchingCubes = false; + // Define the implicit function + MarchingCubes.Implicit = [this, Voxels](const FVector3d& Pos) { + const FVector PosConverted = FVector(Pos.X, Pos.Y, Pos.Z); + const int Index = MyDensityField->WorldPositionToIndex(PosConverted); + return Voxels.Contains(Index) ? 1 : -1; + }; + + MarchingCubes.bParallelCompute = true; + const UE::Geometry::FMeshShapeGenerator& Generator = MarchingCubes.Generate(); + TArray<FVector3d> Vertices3d = Generator.Vertices; + TArray<UE::Geometry::FIndex3i> Triangles = Generator.Triangles; + TArray<FVector3f> Normals3F = Generator.Normals; + + // Convert FVector3d to FVector + TArray<FVector> Vertices; + Vertices.Reserve(Generator.Vertices.Num()); + for (const FVector3d& Vertex : Vertices3d) + { + Vertices.Add(FVector(Vertex.X, Vertex.Y, Vertex.Z)); + } + + // Convert FIndex3i to int32 + TArray<int32> Indices; + Indices.Reserve(Generator.Triangles.Num() * 3); + for (const UE::Geometry::FIndex3i& Triangle : Triangles) + { + Indices.Add(Triangle.A); + Indices.Add(Triangle.B); + Indices.Add(Triangle.C); + } + + // Convert FVector3f to FVector + TArray<FVector> Normals; + Normals.Reserve(Generator.Normals.Num()); + for (const FVector3f& Normal : Normals3F) + { + Normals.Add(FVector(Normal.X, Normal.Y, Normal.Z)); + } + + TArray<FColor> VertexColors; + VertexColors.Reserve(Vertices.Num()); + for (int32 i = 0; i < Vertices.Num(); ++i) + { + VertexColors.Add(FColor::Green); + } + + + 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); + AccumulatedTime = 0.0f; + IsMarchingCubesRunning = false; + }); + }); +} \ No newline at end of file diff --git a/Source/MetaCastBachelor/MagicWand.h b/Source/MetaCastBachelor/MagicWand.h new file mode 100644 index 0000000..f20239e --- /dev/null +++ b/Source/MetaCastBachelor/MagicWand.h @@ -0,0 +1,57 @@ + +#pragma once + +#include "CoreMinimal.h" +#include "ProceduralMeshComponent.h" +#include "MetaCastBaseline.h" +#include "Generators/MarchingCubes.h" +#include "MagicWand.generated.h" + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class UMagicWand : public UMetaCastBaseline +{ + GENERATED_BODY() + + FDensityField* MyDensityField; + UPROPERTY() + UWorld* World; + bool IsMarchingCubesRunning = false; + bool AbortMarchingCubes = false; + + // Critical section to protect shared variables + mutable FCriticalSection ProceduralMeshGuard; + + UPROPERTY() + UProceduralMeshComponent* ProceduralMesh; + UPROPERTY(EditAnywhere) + UMaterialInterface* SelectionVolumeMat; + TArray<int32> FloodedIndices; + UPROPERTY(EditAnywhere) + float MarchingCubeSize = 0; + UPROPERTY(EditAnywhere) + int EvaluationsPerSecond = 10; + float AccumulatedTime = 0.0; + UPROPERTY(EditAnywhere) + float ProximityThreshold = 0.1f; + TArray<int32> SelectedClusterIndices; + + +public: + UMagicWand(); + + virtual void BeginPlay() override; + + virtual void SelectParticles(const FVector& InputPosition) override; + virtual void EraseParticles(const FVector& InputPosition) override; + + virtual void HandleMetaSelectReleased(const FInputActionInstance& Instance) override; + virtual void HandleMetaSelectPressed(const FInputActionInstance& Instance) override; + void InitMagicWandSelection(); + + void GenerateVoxelMeshWithCubes(const TArray<int32> Voxels) const; + void GenerateVoxelMeshSmooth(const TArray<int32> Voxels); + + virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + void PerformMagicWandSelection(const FVector& InputPosition); + void ExpandSelection(int32 SeedIndex); +}; \ No newline at end of file diff --git a/Source/MetaCastBachelor/MetaPoint.cpp b/Source/MetaCastBachelor/MetaPoint.cpp index 6b45ec3..2e908f0 100644 --- a/Source/MetaCastBachelor/MetaPoint.cpp +++ b/Source/MetaCastBachelor/MetaPoint.cpp @@ -1,6 +1,7 @@ #include "MetaPoint.h" #include "Utilities.h" #include "Generators/MarchingCubes.h" +#include "UObject/GCObjectScopeGuard.h" UMetaPoint::UMetaPoint() : LocalMaximumIndex(0), MyDensityField(nullptr), MetaPointThreshold(0), World(nullptr) { @@ -52,14 +53,13 @@ void UMetaPoint::HandleMetaSelectReleased(const FInputActionInstance& Instance) { Super::HandleMetaSelectReleased(Instance); - //ProceduralMesh->ClearAllMeshSections(); - //ProceduralMesh->SetVisibility(false); + ProceduralMesh->ClearAllMeshSections(); + ProceduralMesh->SetVisibility(false); - MyPointCloud->ColorPointsInVoxels(FloodedIndices); AsyncTask(ENamedThreads::Type::AnyBackgroundHiPriTask, [this]() { - + MyPointCloud->ColorPointsInVoxels(FloodedIndices); }); } @@ -70,6 +70,17 @@ void UMetaPoint::HandleMetaSelectPressed(const FInputActionInstance& Instance) InitMetaPointSelection(); } +void UMetaPoint::HandleMetaEraseReleased(const FInputActionInstance& Instance) +{ + Super::HandleMetaEraseReleased(Instance); + + //deselect all particles + for (int32 i = 0; i < MyPointCloud->SelectionFlags.Num(); ++i) + { + MyPointCloud->SelectionFlags[i] = false; + } +} + void UMetaPoint::InitMetaPointSelection() { const FVector SelectionWorldPosition = SelectionObject->GetComponentLocation(); @@ -114,7 +125,7 @@ void UMetaPoint::SelectParticles(const FVector& InputPosition) AccumulatedTime = -10000; FScopeLock Lock(&FloodFillingGuard); - AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this]() + AsyncTask(ENamedThreads::Type::AnyBackgroundHiPriTask, [this]() { const FVector SelectionWorldPosition = SelectionObject->GetComponentLocation(); @@ -133,13 +144,19 @@ void UMetaPoint::SelectParticles(const FVector& InputPosition) LocalMaximum = FUtilities::FollowGradientToMaximum(MyDensityField, SelectionLocalPosition); // Convert the local maximum back to world coordinates - //LocalMaximumIndex = MyDensityField->WorldPositionToIndex(LocalMaximum); + LocalMaximumIndex = MyDensityField->WorldPositionToIndex(LocalMaximum); - MetaPointThreshold = FUtilities::InterpolateDensityAtPosition(MyDensityField, SelectionLocalPosition); + const float Dist = FVector::Distance(SelectStartPosition, SelectionLocalPosition); + UE_LOG(LogTemp, Warning, TEXT("Dist: %f"), Dist); + float InitThreshold = FUtilities::InterpolateDensityAtPosition(MyDensityField, SelectStartPosition); + float Thr = MyDensityField->MaxDensity - (Dist / 100.0f) * (MyDensityField->MaxDensity - MyDensityField->MinDensity) - InitThreshold; - const float MaxDistance = FVector::Distance(SelectStartPosition, SelectionLocalPosition); - FloodedIndices = FUtilities::FloodFilling_2(MyDensityField, LocalMaximumIndex, MetaPointThreshold, MaxDistance); + MetaPointThreshold = FUtilities::InterpolateDensityAtPosition(MyDensityField, SelectionLocalPosition); + const float MaxDistance = FVector::Distance(SelectStartPosition, SelectionLocalPosition); + //FloodedIndices = FUtilities::FloodFilling_2(MyDensityField, LocalMaximumIndex, MetaPointThreshold, MaxDistance); + FloodedIndices = FUtilities::FloodFilling(MyDensityField, LocalMaximumIndex, MetaPointThreshold); + GenerateVoxelMeshSmooth(FloodedIndices); }); } @@ -197,83 +214,106 @@ void UMetaPoint::GenerateVoxelMeshSmooth(const TArray<int32> Voxels) if(IsMarchingCubesRunning) return; + if(Voxels.IsEmpty()) + { + AccumulatedTime = 0; + ProceduralMesh->ClearAllMeshSections(); + ProceduralMesh->SetVisibility(false); + return; + } + IsMarchingCubesRunning = true; - FScopeLock Lock(&ProceduralMeshGuard); - AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this, Voxels]() + FVector Min(FLT_MAX, FLT_MAX, FLT_MAX); + FVector Max(-FLT_MAX, -FLT_MAX, -FLT_MAX); + + for(const int FloodedIndex : Voxels) { - FVector Min(FLT_MAX, FLT_MAX, FLT_MAX); - FVector Max(-FLT_MAX, -FLT_MAX, -FLT_MAX); - - for(const int FloodedIndex : Voxels) - { - Min = FVector::Min(Min, MyDensityField->IndexToVoxelPosition(FloodedIndex)); - Max = FVector::Max(Max, MyDensityField->IndexToVoxelPosition(FloodedIndex)); - } + Min = FVector::Min(Min, MyDensityField->IndexToVoxelPosition(FloodedIndex)); + Max = FVector::Max(Max, MyDensityField->IndexToVoxelPosition(FloodedIndex)); + } + + UE::Geometry::FMarchingCubes MarchingCubes; + MarchingCubes.Bounds = UE::Geometry::TAxisAlignedBox3(Min, Max); + //MarchingCubes.CubeSize = MarchingCubeSize == 0 ? MyDensityField->GetStep().X : MarchingCubeSize; - UE::Geometry::FMarchingCubes MarchingCubes; - MarchingCubes.Bounds = UE::Geometry::TAxisAlignedBox3(Min, Max); - MarchingCubes.CubeSize = MarchingCubeSize == 0 ? MyDensityField->GetStep().X : MarchingCubeSize; - MarchingCubes.CancelF = [this]() { - return AbortMarchingCubes; - }; - MarchingCubes.IsoValue = 0; - - AbortMarchingCubes = false; - // Define the implicit function - MarchingCubes.Implicit = [this, Voxels](const FVector3d& Pos) { - const FVector PosConverted = FVector(Pos.X, Pos.Y, Pos.Z); - const int Index = MyDensityField->WorldPositionToIndex(PosConverted); - return Voxels.Contains(Index) ? 1 : -1; - }; - - MarchingCubes.bParallelCompute = true; - const UE::Geometry::FMeshShapeGenerator& Generator = MarchingCubes.Generate(); - TArray<FVector3d> Vertices3d = Generator.Vertices; - TArray<UE::Geometry::FIndex3i> Triangles = Generator.Triangles; - TArray<FVector3f> Normals3F = Generator.Normals; - - // Convert FVector3d to FVector - TArray<FVector> Vertices; - Vertices.Reserve(Generator.Vertices.Num()); - for (const FVector3d& Vertex : Vertices3d) - { - Vertices.Add(FVector(Vertex.X, Vertex.Y, Vertex.Z)); - } + const float Volume = MarchingCubes.Bounds.Volume(); - // Convert FIndex3i to int32 - TArray<int32> Indices; - Indices.Reserve(Generator.Triangles.Num() * 3); - for (const UE::Geometry::FIndex3i& Triangle : Triangles) - { - Indices.Add(Triangle.A); - Indices.Add(Triangle.B); - Indices.Add(Triangle.C); - } + // Logarithmic scaling of the cube size based on volume + if(Volume > 50000) { + MarchingCubes.CubeSize = (3.0f * log10((Volume + 500) / 1000) - 3); + }else if(Volume > 500000) + { + MarchingCubes.CubeSize = 9; + }else { + MarchingCubes.CubeSize = MyDensityField->GetStep().X; + } - // Convert FVector3f to FVector - TArray<FVector> Normals; - Normals.Reserve(Generator.Normals.Num()); - for (const FVector3f& Normal : Normals3F) - { - Normals.Add(FVector(Normal.X, Normal.Y, Normal.Z)); - } + MarchingCubes.CubeSize = FMath::Clamp(MarchingCubes.CubeSize, MyDensityField->GetStep().X, 10.0f); - TArray<FColor> VertexColors; - VertexColors.Reserve(Vertices.Num()); - for (int32 i = 0; i < Vertices.Num(); ++i) - { - VertexColors.Add(FColor::Green); - } + MarchingCubes.CancelF = [this]() { + return AbortMarchingCubes; + }; + MarchingCubes.IsoValue = 0; + MarchingCubes.bEnableValueCaching = true; + AbortMarchingCubes = false; + // Define the implicit function + MarchingCubes.Implicit = [this, Voxels](const FVector3d& Pos) { + const FVector PosConverted = FVector(Pos.X, Pos.Y, Pos.Z); + const int Index = MyDensityField->WorldPositionToIndex(PosConverted); + return Voxels.Contains(Index) ? 1 : -1; + }; - 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); - AccumulatedTime = 0.0f; - IsMarchingCubesRunning = false; - }); + MarchingCubes.bParallelCompute = true; + MarchingCubes.RootMode = UE::Geometry::ERootfindingModes::Bisection; + + const UE::Geometry::FMeshShapeGenerator& Generator = MarchingCubes.Generate(); + + TArray<FVector3d> Vertices3d = Generator.Vertices; + TArray<UE::Geometry::FIndex3i> Triangles = Generator.Triangles; + TArray<FVector3f> Normals3F = Generator.Normals; + + // Convert FVector3d to FVector + TArray<FVector> Vertices; + Vertices.Reserve(Generator.Vertices.Num()); + for (const FVector3d& Vertex : Vertices3d) + { + Vertices.Add(FVector(Vertex.X, Vertex.Y, Vertex.Z)); + } + + // Convert FIndex3i to int32 + TArray<int32> Indices; + Indices.Reserve(Generator.Triangles.Num() * 3); + for (const UE::Geometry::FIndex3i& Triangle : Triangles) + { + Indices.Add(Triangle.A); + Indices.Add(Triangle.B); + Indices.Add(Triangle.C); + } + + // Convert FVector3f to FVector + TArray<FVector> Normals; + Normals.Reserve(Generator.Normals.Num()); + for (const FVector3f& Normal : Normals3F) + { + Normals.Add(FVector(Normal.X, Normal.Y, Normal.Z)); + } + + TArray<FColor> VertexColors; + VertexColors.Reserve(Vertices.Num()); + for (int32 i = 0; i < Vertices.Num(); ++i) + { + VertexColors.Add(FColor::Green); + } + + + 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); + AccumulatedTime = 0.0f; + IsMarchingCubesRunning = false; }); } \ No newline at end of file diff --git a/Source/MetaCastBachelor/MetaPoint.h b/Source/MetaCastBachelor/MetaPoint.h index 20cd30e..8a8591c 100644 --- a/Source/MetaCastBachelor/MetaPoint.h +++ b/Source/MetaCastBachelor/MetaPoint.h @@ -58,6 +58,7 @@ public: virtual void HandleMetaSelectReleased(const FInputActionInstance& Instance) override; virtual void HandleMetaSelectPressed(const FInputActionInstance& Instance) override; + virtual void HandleMetaEraseReleased(const FInputActionInstance& Instance) override; void InitMetaPointSelection(); void GenerateVoxelMeshWithCubes(const TArray<int32> Voxels) const; diff --git a/Source/MetaCastBachelor/PointCloud.cpp b/Source/MetaCastBachelor/PointCloud.cpp index aad4992..d319ce9 100644 --- a/Source/MetaCastBachelor/PointCloud.cpp +++ b/Source/MetaCastBachelor/PointCloud.cpp @@ -134,7 +134,7 @@ void APointCloud::DrawVoxel(const int Index, const float Time) const } -void APointCloud::ColorPointsInVoxels(const TArray<int32>& VoxelIndices) +void APointCloud::ColorPointsInVoxels(const TArray<int32> VoxelIndices) { FScopeLock Lock(&DataGuard); if (!MyDensityField) diff --git a/Source/MetaCastBachelor/PointCloud.h b/Source/MetaCastBachelor/PointCloud.h index 90e89d2..9de2a62 100644 --- a/Source/MetaCastBachelor/PointCloud.h +++ b/Source/MetaCastBachelor/PointCloud.h @@ -67,7 +67,7 @@ public: void UpdateSelection(); void DrawVoxel(const int Index, float Time) const; - void ColorPointsInVoxels(const TArray<int32>& VoxelIndices); + void ColorPointsInVoxels(const TArray<int32> VoxelIndices); UFUNCTION(BlueprintCallable) void ReadPointCloudFromFile(FFilePath FileNamePoints, FFilePath FileNameFlags); diff --git a/Source/MetaCastBachelor/Utilities.cpp b/Source/MetaCastBachelor/Utilities.cpp index 76b42e3..c3581b9 100644 --- a/Source/MetaCastBachelor/Utilities.cpp +++ b/Source/MetaCastBachelor/Utilities.cpp @@ -106,7 +106,7 @@ float FUtilities::GaussianKernel(const float Distance, const float Sigma) { float FUtilities::SmoothingKernel(const float Distance, const float Radius) { const float Value = FMath::Max(0.0f, Radius * Radius - Distance * Distance); - return Value * Value * Value; + return (Value * Value) / 5; } FVector FUtilities::FollowGradientToMaximum(const FDensityField* DensityField, const FVector& StartPosition) @@ -116,7 +116,7 @@ FVector FUtilities::FollowGradientToMaximum(const FDensityField* DensityField, c int Iterations = 0; constexpr int MaxIterations = 100; // Prevent infinite loops - constexpr float StepSize = 0.05f; // Define a reasonable step size + constexpr float StepSize = 0.05f; // Define step size while (Iterations < MaxIterations) { -- GitLab