From 8897d99363ed503ae5083c76291b5c2cc1d46160 Mon Sep 17 00:00:00 2001 From: Timon Roemer <t.roemer@vis.rwth-aachen.de> Date: Fri, 12 Jul 2024 18:20:41 +0200 Subject: [PATCH] Completes MetaCast --- Content/MetaPointMap.umap | Bin 45708 -> 44870 bytes Content/SelectionVolume.uasset | Bin 8780 -> 8925 bytes Source/MetaCastBachelor/DensityField.cpp | 83 +++++++++++ Source/MetaCastBachelor/DensityField.h | 4 + Source/MetaCastBachelor/MetaCastBaseline.cpp | 1 + Source/MetaCastBachelor/MetaPoint.cpp | 144 +++++++++++-------- Source/MetaCastBachelor/MetaPoint.h | 17 +-- Source/MetaCastBachelor/PointCloud.cpp | 79 ++++++++-- Source/MetaCastBachelor/PointCloud.h | 4 +- Source/MetaCastBachelor/Utilities.cpp | 68 +++------ 10 files changed, 275 insertions(+), 125 deletions(-) diff --git a/Content/MetaPointMap.umap b/Content/MetaPointMap.umap index 651dc1767d85ce66c1085a001440e676d242a400..f44a6fa3bd8788f0813c8f4c4351e9c5bf22240f 100644 GIT binary patch literal 44870 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 K3F~Ep)B^x+hXsfL literal 45708 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 W01M5sdlrE#r}k@tHi>}5K<WV|$z3x5 diff --git a/Content/SelectionVolume.uasset b/Content/SelectionVolume.uasset index 73d06d7c6830ac1fc883baf71e935610588d84e9..b4a7c48878aadac755046e2d70262c2c42fa0168 100644 GIT binary patch delta 4700 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 delta 4613 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 WZaAoaj_eX-Hpobjy#C}`r33)9$dNGs diff --git a/Source/MetaCastBachelor/DensityField.cpp b/Source/MetaCastBachelor/DensityField.cpp index 3aa6e9b..f952939 100644 --- a/Source/MetaCastBachelor/DensityField.cpp +++ b/Source/MetaCastBachelor/DensityField.cpp @@ -104,6 +104,72 @@ void FDensityField::CalculateVoxelDensities(const APointCloud* PointCloud, const } +void FDensityField::CalculateVoxelDensitiesByNumber(const APointCloud* PointCloud, const float InfluenceRadius) +{ + if (!PointCloud) + return; + + const double StartTime = FPlatformTime::Seconds(); // Start timing + UE_LOG(LogTemp, Log, TEXT("Starting density calculation.")); + + // Clear existing densities + for (auto& Node : VoxelList) + { + Node.SetVoxelDensity(0.0); // Reset the density counter + } + + UE_LOG(LogTemp, Log, TEXT("Cleared previous densities.")); + + // Iterate over each particle + for (const FVector& ParticlePosition : PointCloud->PositionVectors) + { + // Calculate which voxels the particle influences + const int32 StartX = FMath::Max(0, static_cast<int32>((ParticlePosition.X - InfluenceRadius - GridOrigin.X) / XStep)); + const int32 EndX = FMath::Min(XNum - 1, static_cast<int32>((ParticlePosition.X + InfluenceRadius - GridOrigin.X) / XStep)); + const int32 StartY = FMath::Max(0, static_cast<int32>((ParticlePosition.Y - InfluenceRadius - GridOrigin.Y) / YStep)); + const int32 EndY = FMath::Min(YNum - 1, static_cast<int32>((ParticlePosition.Y + InfluenceRadius - GridOrigin.Y) / YStep)); + const int32 StartZ = FMath::Max(0, static_cast<int32>((ParticlePosition.Z - InfluenceRadius - GridOrigin.Z) / ZStep)); + const int32 EndZ = FMath::Min(ZNum - 1, static_cast<int32>((ParticlePosition.Z + InfluenceRadius - GridOrigin.Z) / ZStep)); + + // Update densities within the influence radius + for (int32 Z = StartZ; Z <= EndZ; ++Z) + { + for (int32 Y = StartY; Y <= EndY; ++Y) + { + for (int32 X = StartX; X <= EndX; ++X) + { + const int32 Index = GridPositionToIndex(X, Y, Z); + if (IsValidIndex(Index)) + { + FVector VoxelPosition = IndexToVoxelPosition(Index); + const float Distance = FVector::Dist(VoxelPosition, ParticlePosition); + + if (Distance < InfluenceRadius) + { + VoxelList[Index].AddToVoxelDensity(1.0); // Simply increment the density count + } + } + } + } + } + } + + double MaxDensity = 0.0; + for (const auto& Node : VoxelList) + { + if (Node.GetVoxelDensity() > MaxDensity) + { + MaxDensity = Node.GetVoxelDensity(); + } + } + UE_LOG(LogTemp, Log, TEXT("Maximum density found: %f"), MaxDensity); + + const double EndTime = FPlatformTime::Seconds(); // End timing + const double ElapsedTime = EndTime - StartTime; // Calculate elapsed time + + UE_LOG(LogTemp, Log, TEXT("Density calculation completed in %f seconds."), ElapsedTime); +} + void FDensityField::CalculateAndStoreGradients() { UE_LOG(LogTemp, Log, TEXT("Starting gradient calculation.")); @@ -182,6 +248,8 @@ void FDensityField::DrawDebugVoxelDensity(const UWorld* World, const AActor* Act UE_LOG(LogTemp, Log, TEXT("DensityField drawn with gradient colors using logarithmic scaling!")); } + + // WORLD POSITION CONVERSION FUNCTIONS int32 FDensityField::WorldPositionToIndex(const FVector &Position) const { @@ -354,6 +422,21 @@ double FDensityField::IndexToVoxelDensity(const int32 Index) const return 0.0; } +void FDensityField::IndexToVoxelBounds(const int32 Index, FVector& OutMinBounds, FVector& OutMaxBounds) const +{ + if (!IsValidIndex(Index)) { + UE_LOG(LogTemp, Warning, TEXT("Invalid voxel index provided: %d"), Index); + return; + } + + // Calculate the bounds + const FVector VoxelCenter = IndexToVoxelPosition(Index); + OutMinBounds = VoxelCenter - GetStep() * 0.5f; + OutMaxBounds = VoxelCenter + GetStep() * 0.5f; + + //UE_LOG(LogTemp, Log, TEXT("Voxel %d Bounds: Min(%s), Max(%s)"), Index, *OutMinBounds.ToString(), *OutMaxBounds.ToString()); +} + // VALIDITY FUNCTIONS bool FDensityField::IsValidIndex(const int32 Index) const { diff --git a/Source/MetaCastBachelor/DensityField.h b/Source/MetaCastBachelor/DensityField.h index 7477fd5..3f4ff10 100644 --- a/Source/MetaCastBachelor/DensityField.h +++ b/Source/MetaCastBachelor/DensityField.h @@ -60,12 +60,14 @@ class FDensityField FVector GridOrigin; public: + mutable FCriticalSection DataGuard; // CONSTRUCTOR FDensityField(); // INITIALIZATION FUNCTIONS void InitializeDensityField(const FVector& MinBounds, const FVector& MaxBounds, const int32 XAxisNum, const int32 YAxisNum, const int32 ZAxisNum); void CalculateVoxelDensities(const APointCloud* PointCloud, float InfluenceRadius, float Sigma); + void CalculateVoxelDensitiesByNumber(const APointCloud* PointCloud, float InfluenceRadius); void CalculateAndStoreGradients(); // DEBUG FUNCTIONS @@ -85,6 +87,7 @@ public: FIntVector3 IndexToGridPosition(int32 Index) const; int32 GetVoxelNumber() const; double IndexToVoxelDensity(int32 Index) const; + void IndexToVoxelBounds(int32 Index, FVector& OutMinBounds, FVector& OutMaxBounds) const; double GridPositionToVoxelDensity(int32 XBin, int32 YBin, int32 ZBin) const; FVector GridPositionToVoxelGradient(int32 XBin, int32 YBin, int32 ZBin) const; FVector GetStep() const; @@ -94,6 +97,7 @@ public: bool IsValidWorldPosition(const FVector& Position) const; TArray<int32> IndexToVoxelNeighbors(const int32 Index) const; bool IsValidIndex(int32 Index) const; + bool IsValid(); void SetVoxelDensityByIndex(int32 Index, double Density); void SetVoxelGradientByIndex(int32 Index, const FVector& Gradient); }; diff --git a/Source/MetaCastBachelor/MetaCastBaseline.cpp b/Source/MetaCastBachelor/MetaCastBaseline.cpp index 7d19389..f6bd224 100644 --- a/Source/MetaCastBachelor/MetaCastBaseline.cpp +++ b/Source/MetaCastBachelor/MetaCastBaseline.cpp @@ -99,6 +99,7 @@ void UMetaCastBaseline::InitInputBindings() EnhancedInputComponent->BindAction(MetaSelectAction, ETriggerEvent::Started, this, &UMetaCastBaseline::HandleMetaSelectPressed); EnhancedInputComponent->BindAction(MetaSelectAction, ETriggerEvent::Completed, this, &UMetaCastBaseline::HandleMetaSelectReleased); + EnhancedInputComponent->BindAction(MetaSelectAction, ETriggerEvent::Canceled, this, &UMetaCastBaseline::HandleMetaSelectReleased); EnhancedInputComponent->BindAction(MetaEraseAction, ETriggerEvent::Started, this, &UMetaCastBaseline::HandleMetaErasePressed); EnhancedInputComponent->BindAction(MetaEraseAction, ETriggerEvent::Completed, this, &UMetaCastBaseline::HandleMetaEraseReleased); diff --git a/Source/MetaCastBachelor/MetaPoint.cpp b/Source/MetaCastBachelor/MetaPoint.cpp index dd70f5d..4a71e32 100644 --- a/Source/MetaCastBachelor/MetaPoint.cpp +++ b/Source/MetaCastBachelor/MetaPoint.cpp @@ -1,7 +1,7 @@ #include "MetaPoint.h" #include "Utilities.h" -UMetaPoint::UMetaPoint() : Index(0), MyDensityField(nullptr), Threshold(0), World(nullptr), MyProceduralMesh(nullptr) +UMetaPoint::UMetaPoint() : LocalMaximumIndex(0), MyDensityField(nullptr), MetaPointThreshold(0), World(nullptr) { // Initialize the Marching Cubes algorithm MyMarchingCubes = new MarchingCubes(); @@ -26,6 +26,13 @@ void UMetaPoint::BeginPlay() ProceduralMesh->SetMaterial(2, SelectionVolumeMat); } +void UMetaPoint::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + AccumulatedTime += DeltaTime; +} + void UMetaPoint::EraseParticles(const FVector& InputPosition) { @@ -34,92 +41,83 @@ void UMetaPoint::EraseParticles(const FVector& InputPosition) void UMetaPoint::HandleMetaSelectReleased(const FInputActionInstance& Instance) { Super::HandleMetaSelectReleased(Instance); + + ProceduralMesh->ClearAllMeshSections(); + ProceduralMesh->SetVisibility(false); + + AsyncTask(ENamedThreads::Type::AnyBackgroundHiPriTask, [this]() + { + MyPointCloud->ColorPointsInVoxels(FloodedIndices); + }); } void UMetaPoint::HandleMetaSelectPressed(const FInputActionInstance& Instance) { Super::HandleMetaSelectPressed(Instance); - FindAndMarkLocalMaximum(); + InitMetaPointSelection(); } -// Method to perform gradient ascent to find the local maximum density starting from a given position. -void UMetaPoint::FindAndMarkLocalMaximum() +void UMetaPoint::InitMetaPointSelection() { - const FVector WorldPosition = SelectionObject->GetComponentLocation(); + 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 StartPosition = MyPointCloud->PointCloudVisualizer->GetComponentTransform().InverseTransformPosition(WorldPosition); + const FVector SelectionLocalPosition = MyPointCloud->PointCloudVisualizer->GetComponentTransform().InverseTransformPosition(SelectionWorldPosition); // Perform gradient ascent to find the local maximum starting from the converted local position - LocalMaximum = FUtilities::FollowGradientToMaximum(MyDensityField, StartPosition); + LocalMaximum = FUtilities::FollowGradientToMaximum(MyDensityField, SelectionLocalPosition); // Convert the local maximum back to world coordinates - Index = MyDensityField->WorldPositionToIndex(LocalMaximum); + LocalMaximumIndex = MyDensityField->WorldPositionToIndex(LocalMaximum); - Threshold = FUtilities::InterpolateDensityAtPosition(MyDensityField, LocalMaximum); - TestingThresholdFactor = 1; + MetaPointThreshold = FUtilities::InterpolateDensityAtPosition(MyDensityField, SelectionLocalPosition); // Initialize Visited array to false for all voxels Visited.Init(false, MyDensityField->GetVoxelNumber()); // Initialize IndicesToVisit with the starting index IndicesToVisit.Empty(); - IndicesToVisit.Add(Index); + IndicesToVisit.Add(LocalMaximumIndex); // Clear FloodedIndices FloodedIndices.Empty(); - - World = GetWorld(); -} - -void UMetaPoint::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) -{ - Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + RevisitIndices.Empty(); - //TestingThresholdFactor -= DeltaTime * 0.1f; - - AccumulatedTime += DeltaTime; - - constexpr float DecayRate = 0.1f; // Adjust this value to control the rate of decay - TestingThresholdFactor *= FMath::Exp(-DecayRate * DeltaTime); - - // Ensure TestingThresholdFactor never goes below a small threshold to prevent it from becoming zero - constexpr float MinThreshold = 0.001f; // Adjust as needed - if (TestingThresholdFactor < MinThreshold) - { - TestingThresholdFactor = MinThreshold; - } + ProceduralMesh->ClearAllMeshSections(); + World = GetWorld(); } void UMetaPoint::SelectParticles(const FVector& InputPosition) { - //WorldMaximum = MyPointCloud->PointCloudVisualizer->GetComponentTransform().TransformPosition(LocalMaximum); - // Draw a debug sphere at the location of the local maximum in the world - //DrawDebugSphere(World, WorldMaximum, SelectionRadius / 2, 32, FColor::Red, false, 0); - - if (AccumulatedTime >= 0.1f) + if (AccumulatedTime >= 1 / MetaCastPerSecond) { - AccumulatedTime = -10.0f; - + AccumulatedTime = -1000; + AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this]() { - //TArray<int32> Voxels = FUtilities::FloodFilling(MyDensityField, Index, Threshold * TestingThresholdFactor); - FUtilities::FloodFilling_2(MyDensityField, Threshold * TestingThresholdFactor, Visited, IndicesToVisit, FloodedIndices, IndicesToVisit); - - auto Voxels = FloodedIndices; - AsyncTask(ENamedThreads::GameThread, [this, Voxels]() - { - GenerateVoxelMesh(FloodedIndices); - - AccumulatedTime = 0.0f; - }); + const FVector SelectionWorldPosition = SelectionObject->GetComponentLocation(); + // 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); + + // Perform gradient ascent to find the local maximum starting from the converted local position + LocalMaximum = FUtilities::FollowGradientToMaximum(MyDensityField, SelectionLocalPosition); + + // Convert the local maximum back to world coordinates + LocalMaximumIndex = MyDensityField->WorldPositionToIndex(LocalMaximum); + + MetaPointThreshold = FUtilities::InterpolateDensityAtPosition(MyDensityField, SelectionLocalPosition); + FloodedIndices = FUtilities::FloodFilling(MyDensityField, LocalMaximumIndex, MetaPointThreshold); + + GenerateVoxelMesh(FloodedIndices); + + AccumulatedTime = 0.0f; }); } } -void UMetaPoint::GenerateVoxelMesh(const TArray<int32>& Voxels) const +void UMetaPoint::GenerateVoxelMesh(const TArray<int32> Voxels) const { TArray<FVector> Vertices; TArray<int32> Triangles; @@ -135,12 +133,6 @@ void UMetaPoint::GenerateVoxelMesh(const TArray<int32>& Voxels) const TArray<FVector> VoxelCorners = MyDensityField->IndexToVoxelCornersWorld(VoxelIndex); - // Convert corners to world space - for (FVector& Corner : VoxelCorners) - { - //Corner = MyPointCloud->PointCloudVisualizer->GetComponentTransform().TransformPosition(Corner); - } - // Add vertices for the voxel const int32 BaseIndex = Vertices.Num(); Vertices.Append(VoxelCorners); @@ -159,10 +151,48 @@ void UMetaPoint::GenerateVoxelMesh(const TArray<int32>& Voxels) const // Add red color for each corner vertex for (int32 i = 0; i < 8; ++i) { - VertexColors.Add(FColor::Red); + VertexColors.Add(FColor::Green); } } // Create the mesh section - ProceduralMesh->CreateMeshSection(0, Vertices, Triangles, TArray<FVector>(), TArray<FVector2D>(), VertexColors, TArray<FProcMeshTangent>(), true); + 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 UMetaPoint::CreateMeshSections(const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FColor>& VertexColors, const int32 MaxVerticesPerSection) const +{ + const int32 NumVertices = Vertices.Num(); + const int32 NumTriangles = Triangles.Num() / 3; + + for (int32 SectionIndex = 0, VertexIndex = 0, TriangleIndex = 0; VertexIndex < NumVertices && TriangleIndex < NumTriangles; + ++SectionIndex, VertexIndex += MaxVerticesPerSection, TriangleIndex += MaxVerticesPerSection / 3) + { + const int32 NumVerticesInSection = FMath::Min(MaxVerticesPerSection, NumVertices - VertexIndex); + const int32 NumTrianglesInSection = FMath::Min(MaxVerticesPerSection / 3, NumTriangles - TriangleIndex); + + TArray<FVector> SectionVertices; + TArray<FColor> SectionVertexColors; + SectionVertices.Reserve(NumVerticesInSection); + SectionVertexColors.Reserve(NumVerticesInSection); + for (int32 i = 0; i < NumVerticesInSection; ++i) + { + SectionVertices.Add(Vertices[VertexIndex + i]); + SectionVertexColors.Add(VertexColors[VertexIndex + i]); + } + + TArray<int32> SectionTriangles; + SectionTriangles.Reserve(NumTrianglesInSection * 3); + for (int32 i = 0; i < NumTrianglesInSection * 3; ++i) + { + SectionTriangles.Add(Triangles[TriangleIndex * 3 + i] - VertexIndex); + } + + // Create or update the mesh section + ProceduralMesh->CreateMeshSection(SectionIndex, SectionVertices, SectionTriangles, TArray<FVector>(), TArray<FVector2D>(), SectionVertexColors, TArray<FProcMeshTangent>(), false); + } +} \ No newline at end of file diff --git a/Source/MetaCastBachelor/MetaPoint.h b/Source/MetaCastBachelor/MetaPoint.h index ce3e88b..e221d59 100644 --- a/Source/MetaCastBachelor/MetaPoint.h +++ b/Source/MetaCastBachelor/MetaPoint.h @@ -14,10 +14,10 @@ class UMetaPoint : public UMetaCastBaseline MarchingCubes* MyMarchingCubes; FVector WorldMaximum; - int32 Index; + int32 LocalMaximumIndex; FVector LocalMaximum; FDensityField* MyDensityField; - float Threshold; + float MetaPointThreshold; UPROPERTY() UWorld* World; float AccumulatedTime = 0.0; @@ -32,15 +32,11 @@ class UMetaPoint : public UMetaCastBaseline TArray<int32> IndicesToVisit; TArray<int32> FloodedIndices; TArray<int32> RevisitIndices; - -public: - - UPROPERTY(EditAnywhere, BlueprintReadWrite) - UProceduralMeshComponent* MyProceduralMesh; UPROPERTY(EditAnywhere) - float TestingThresholdFactor = 2.0; + int MetaCastPerSecond = 10; +public: UMetaPoint(); virtual void BeginPlay() override; @@ -50,9 +46,10 @@ public: virtual void HandleMetaSelectReleased(const FInputActionInstance& Instance) override; virtual void HandleMetaSelectPressed(const FInputActionInstance& Instance) override; - void FindAndMarkLocalMaximum(); + void InitMetaPointSelection(); - void GenerateVoxelMesh(const TArray<int32>& Voxels) const; + void GenerateVoxelMesh(const TArray<int32> Voxels) const; + void CreateMeshSections(const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FColor>& VertexColors, int32 MaxVerticesPerSection) const; virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; }; diff --git a/Source/MetaCastBachelor/PointCloud.cpp b/Source/MetaCastBachelor/PointCloud.cpp index d6482df..747d186 100644 --- a/Source/MetaCastBachelor/PointCloud.cpp +++ b/Source/MetaCastBachelor/PointCloud.cpp @@ -22,7 +22,7 @@ void APointCloud::BeginPlay() void APointCloud::ReadPointCloudFromFile(const FFilePath FileNamePoints, const FFilePath FileNameFlags) { - TArray<FVector> LoadedPointCloud = PointCloudDataReader::LoadPointCloudData(FileNamePoints.FilePath); + const TArray<FVector> LoadedPointCloud = PointCloudDataReader::LoadPointCloudData(FileNamePoints.FilePath); TArray<int32> LoadedPointCloudFlags = PointCloudDataReader::LoadFlags(FileNameFlags.FilePath); // Initialize the boolean array for flags with all elements set to false @@ -30,7 +30,7 @@ void APointCloud::ReadPointCloudFromFile(const FFilePath FileNamePoints, const F BooleanFlags.Init(false, LoadedPointCloud.Num()); // Set true for indices that are included in the flags array - for (int32 FlagIndex : LoadedPointCloudFlags) + for (const int32 FlagIndex : LoadedPointCloudFlags) { if (FlagIndex < BooleanFlags.Num()) { @@ -91,10 +91,10 @@ void APointCloud::SetupDensityFieldFromPointCloud() const constexpr int32 ZAxisNum = 100; // Number of divisions in the grid along the Z-axis MyDensityField->InitializeDensityField(MinBounds, MaxBounds, XAxisNum, YAxisNum, ZAxisNum); - constexpr float InfluenceRadius = 5.0f; // Half the size of a voxel - constexpr float Sigma = 3.0f; // Standard deviation for the Gaussian kernel - MyDensityField->CalculateVoxelDensities(this, InfluenceRadius, Sigma); - + constexpr float InfluenceRadius = 2.0f; // Half the size of a voxel + constexpr float Sigma = 0.5f; // Standard deviation for the Gaussian kernel + + MyDensityField->CalculateVoxelDensitiesByNumber(this, InfluenceRadius); MyDensityField->CalculateAndStoreGradients(); if (const UWorld* World = GetWorld()) @@ -107,12 +107,24 @@ void APointCloud::UpdateSelection() { for (int32 i = 0; i < PositionVectors.Num(); i++) { - if (SelectionFlags[i]) + if(DefaultFlags[i]) { - PointColors[i] = FColor::Green; + if (SelectionFlags[i]) + { + PointColors[i] = FColor::Red; + }else + { + PointColors[i] = FColor::Orange; + } }else { - PointColors[i] = FColor::Blue; + if (SelectionFlags[i]) + { + PointColors[i] = FColor::Green; + }else + { + PointColors[i] = FColor::Blue; + } } } @@ -135,10 +147,59 @@ void APointCloud::DrawVoxel(const int Index, const float Time) const } +void APointCloud::ColorPointsInVoxels(const TArray<int32> VoxelIndices) +{ + if (!MyDensityField) + { + UE_LOG(LogTemp, Warning, TEXT("DensityField is not initialized.")); + return; + } + + TArray<FVector> MyPositionVectors = PositionVectors; + + FScopeLock Lock(&DataGuard); + FScopeLock Lock2(&MyDensityField->DataGuard); + // Iterate through each index in VoxelIndices + for (const int32 VoxelIndex : VoxelIndices) + { + // Ensure the voxel index is valid + if (!MyDensityField || !MyDensityField->IsValidIndex(VoxelIndex)) + { + UE_LOG(LogTemp, Warning, TEXT("Invalid voxel index: %d"), VoxelIndex); + continue; + } + + // Get the bounds of the voxel + FVector VoxelMin, VoxelMax; + MyDensityField->IndexToVoxelBounds(VoxelIndex, VoxelMin, VoxelMax); + + // Iterate over all points to check if they are within this voxel + for (int32 i = 0; i < MyPositionVectors.Num(); ++i) + { + if(!MyPositionVectors.IsValidIndex(i)) + { + continue; + } + const FVector Point = MyPositionVectors[i]; + + // Check if the point is within the voxel bounds + if (Point.X >= VoxelMin.X && Point.X <= VoxelMax.X && + Point.Y >= VoxelMin.Y && Point.Y <= VoxelMax.Y && + Point.Z >= VoxelMin.Z && Point.Z <= VoxelMax.Z) + { + SelectionFlags[i] = true; + } + } + } + + //UpdateSelection(); +} + // Called every frame void APointCloud::Tick(float DeltaTime) { Super::Tick(DeltaTime); + UpdateSelection(); } diff --git a/Source/MetaCastBachelor/PointCloud.h b/Source/MetaCastBachelor/PointCloud.h index e22fd46..4770f1b 100644 --- a/Source/MetaCastBachelor/PointCloud.h +++ b/Source/MetaCastBachelor/PointCloud.h @@ -17,6 +17,7 @@ public: TArray<bool> DefaultFlags; TArray<bool> SelectionFlags; TArray<FColor> PointColors; + mutable FCriticalSection DataGuard; UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Bounds") FVector MinBounds; @@ -53,12 +54,13 @@ protected: virtual void BeginPlay() override; -public: +public: // Called every frame virtual void Tick(float DeltaTime) override; void UpdateSelection(); void DrawVoxel(const int Index, float Time) const; + 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 343a2ee..2362648 100644 --- a/Source/MetaCastBachelor/Utilities.cpp +++ b/Source/MetaCastBachelor/Utilities.cpp @@ -138,7 +138,7 @@ FVector FUtilities::FollowGradientToMaximum(const FDensityField* DensityField, c //UE_LOG(LogTemp, Log, TEXT("Iteration %d: CurrentPos = %s, Gradient = %s, NextPos = %s, NewDensity = %f, CurrentDensity = %f"), Iterations, *CurrentPosition.ToString(), *Gradient.ToString(), *NextPosition.ToString(), NewDensity, CurrentDensity); if (NewDensity <= CurrentDensity) { // Check if density has increased - UE_LOG(LogTemp, Log, TEXT("Density did not increase; stopping at position %s with density %f"), *CurrentPosition.ToString(), CurrentDensity); + //UE_LOG(LogTemp, Log, TEXT("Density did not increase; stopping at position %s with density %f"), *CurrentPosition.ToString(), CurrentDensity); break; // No improvement in density, stop iteration } @@ -203,38 +203,6 @@ TArray<int32> FUtilities::FloodFilling(const FDensityField* DensityField, const return VisitedIndices; } -/* -TArray<int32> FUtilities::FloodFilling_2(const FDensityField* DensityField, const float Threshold, TArray<bool>& Visited, TArray<int32>& IndicesToVisit, TArray<int32>& FloodedIndices) -{ - if (!DensityField) { - UE_LOG(LogTemp, Warning, TEXT("DensityField is null.")); - return FloodedIndices; - } - - while (IndicesToVisit.Num() > 0) - { - int32 CurrentIndex = IndicesToVisit.Pop(); - if (!Visited[CurrentIndex]) - { - Visited[CurrentIndex] = true; - FloodedIndices.Add(CurrentIndex); - - TArray<int32> Neighbors = DensityField->IndexToVoxelNeighbors(CurrentIndex); - - for (int32 NeighborIndex : Neighbors) - { - const double NeighborDensity = DensityField->IndexToVoxelDensity(NeighborIndex); - - if (NeighborDensity > Threshold && !Visited[NeighborIndex]) { - IndicesToVisit.Push(NeighborIndex); - } - } - } - } - - return FloodedIndices; -}*/ - void FUtilities::FloodFilling_2(const FDensityField* DensityField, const float Threshold, TArray<bool>& VisitedIndices, TArray<int32>& IndicesToVisit, TArray<int32>& FloodedIndices, TArray<int32>& PotentialRevisit) { if (!DensityField) { @@ -244,10 +212,13 @@ void FUtilities::FloodFilling_2(const FDensityField* DensityField, const float T TArray<int32> CurrentPotentialRevisit; - constexpr int MaxIterations = 100; + + constexpr int MaxIterations = 2000; int IterationCount = 0; + while (IndicesToVisit.Num() > 0) { + IterationCount++; if(IterationCount > MaxIterations) { @@ -258,28 +229,29 @@ void FUtilities::FloodFilling_2(const FDensityField* DensityField, const float T } int32 CurrentIndex = IndicesToVisit.Pop(); - VisitedIndices[CurrentIndex] = true; - const double CurrentDensity = DensityField->IndexToVoxelDensity(CurrentIndex); - if (CurrentDensity >= Threshold) { - FloodedIndices.Add(CurrentIndex); + if(DensityField->IsValidIndex(CurrentIndex)) + { + VisitedIndices[CurrentIndex] = true; + const double CurrentDensity = DensityField->IndexToVoxelDensity(CurrentIndex); - TArray<int32> Neighbors = DensityField->IndexToVoxelNeighbors(CurrentIndex); - for (int32 NeighborIndex : Neighbors) - { - if (!VisitedIndices[NeighborIndex]) + if (CurrentDensity >= Threshold) { + FloodedIndices.Add(CurrentIndex); + + TArray<int32> Neighbors = DensityField->IndexToVoxelNeighbors(CurrentIndex); + for (int32 NeighborIndex : Neighbors) { - IndicesToVisit.Push(NeighborIndex); + if (!VisitedIndices[NeighborIndex]) + { + IndicesToVisit.Push(NeighborIndex); + } } + } else { + CurrentPotentialRevisit.Add(CurrentIndex); } - } else { - CurrentPotentialRevisit.Add(CurrentIndex); } } // Update PotentialRevisit with the new list from this run PotentialRevisit = CurrentPotentialRevisit; } - - - -- GitLab