From 074f176b6695b2923a9f7730fa9f15862eff00bd Mon Sep 17 00:00:00 2001 From: Timon Roemer <t.roemer@vis.rwth-aachen.de> Date: Thu, 11 Jul 2024 18:27:36 +0200 Subject: [PATCH] Adds FloodFilling on different threads --- Config/DefaultEngine.ini | 6 +- Content/MetaPointMap.umap | Bin 43192 -> 45708 bytes Content/MetaPointMap_BuiltData.uasset | Bin 42891 -> 42891 bytes Content/SelectionVolume.uasset | Bin 0 -> 8780 bytes Source/MetaCastBachelor/DensityField.cpp | 247 +++++++++++++---------- Source/MetaCastBachelor/DensityField.h | 25 +-- Source/MetaCastBachelor/MetaPoint.cpp | 126 +++++++++--- Source/MetaCastBachelor/MetaPoint.h | 16 ++ Source/MetaCastBachelor/PointCloud.cpp | 10 +- Source/MetaCastBachelor/Utilities.cpp | 129 ++++++++++-- Source/MetaCastBachelor/Utilities.h | 1 + 11 files changed, 386 insertions(+), 174 deletions(-) create mode 100644 Content/SelectionVolume.uasset diff --git a/Config/DefaultEngine.ini b/Config/DefaultEngine.ini index 19b59fc..05ba680 100644 --- a/Config/DefaultEngine.ini +++ b/Config/DefaultEngine.ini @@ -125,7 +125,11 @@ r.Shadow.Virtual.Enable = 1 r.Mobile.AntiAliasing=3 r.AntiAliasingMethod=3 vr.InstancedStereo=True -r.MobileHDR=True +r.MobileHDR=False +r.MSAACount=4 +r.Mobile.ShadingPath=1 +r.ForwardShading=True +r.CustomDepth=1 [/Script/HardwareTargeting.HardwareTargetingSettings] TargetedHardwareClass = Mobile diff --git a/Content/MetaPointMap.umap b/Content/MetaPointMap.umap index add3f06500b1b576fa486e1ede28b8e2d591a298..651dc1767d85ce66c1085a001440e676d242a400 100644 GIT binary patch literal 45708 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 W01M5sdlrE#r}k@tHi>}5K<WV|$z3x5 literal 43192 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 ZA%a2=gv;((1hSmkuL&C)IM}Q;4*<CJk4pdm diff --git a/Content/MetaPointMap_BuiltData.uasset b/Content/MetaPointMap_BuiltData.uasset index 1bf02fa1814c30bc66b1c21252e01906ddb1ac28..24285dcf828b76f9af8653034e84014cb1f8e362 100644 GIT binary patch delta 179 zcmeA^&(wXMX@e1?z=x;Pl(KwZIknvQC=nTAp19eGF^z@IfI&>zjC=ArRxLIIhC|%k z(>C8@jbUMA+-%L2%Pim!bM5YC*$}VQBCbW^2i!_F@8FGN5=h&7y{oB6$zjW{ZG30d zoo7x~oGG%|O2Cy-;73aU|IuZMUYk~?rr(?*?6A3C@JTGDXmfrGi@;2;p05r?JG@tL VZ@Y5l%(mLe59f<))-Fjf1^^DkNt6Hp delta 179 zcmeA^&(wXMX@e1?K!wx7n@pkS9VaCiq)*Ikd9m4vF^z@IkYS-+aL(j)tXgb_45eau zA2;7)jbUMA-fYd4%Pg?5KgdAmRlQ@c=X|G&1@FT)@8FGN5=abMzIy(P=}yzzH*5Xe z?6YjL;!KgvRsyb!0yCzid{DR$?AGDuyh;Dew(!mUf=^;GMVs?mSOl84)n}~Ku6Ccn V{-AR2`_r>0Kb$YJS-T{`7yvruOiBO% diff --git a/Content/SelectionVolume.uasset b/Content/SelectionVolume.uasset new file mode 100644 index 0000000000000000000000000000000000000000..73d06d7c6830ac1fc883baf71e935610588d84e9 GIT binary patch literal 8780 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 literal 0 HcmV?d00001 diff --git a/Source/MetaCastBachelor/DensityField.cpp b/Source/MetaCastBachelor/DensityField.cpp index 0cfcdc9..3aa6e9b 100644 --- a/Source/MetaCastBachelor/DensityField.cpp +++ b/Source/MetaCastBachelor/DensityField.cpp @@ -25,7 +25,7 @@ void FDensityField::InitializeDensityField(const FVector& MinBounds, const FVect for (int32 X = 0; X < XAxisNum; X++) { FVector Position(MinBounds.X + X * XStep, MinBounds.Y + Y * YStep, MinBounds.Z + Z * ZStep); - FVector GridPosition(X, Y, Z); + FIntVector3 GridPosition(X, Y, Z); VoxelList.Add(FVoxel(Position, GridPosition)); } } @@ -140,7 +140,6 @@ void FDensityField::CalculateAndStoreGradients() UE_LOG(LogTemp, Log, TEXT("Gradient calculation completed.")); } - // DEBUG FUNCTIONS void FDensityField::DrawDebugVoxelDensity(const UWorld* World, const AActor* Actor, const float Duration, const float Scale, const float DensityThreshold) const @@ -183,16 +182,11 @@ void FDensityField::DrawDebugVoxelDensity(const UWorld* World, const AActor* Act UE_LOG(LogTemp, Log, TEXT("DensityField drawn with gradient colors using logarithmic scaling!")); } -// CONVERSION FUNCTIONS - -int32 FDensityField::GridPositionToIndex(const int32 X, const int32 Y, const int32 Z) const -{ - return Z * XNum * YNum + Y * XNum + X; -} +// WORLD POSITION CONVERSION FUNCTIONS int32 FDensityField::WorldPositionToIndex(const FVector &Position) const { // Convert world position to grid position - const FVector GridPosition = WorldToGridPosition(Position); + const FIntVector3 GridPosition = WorldToGridPosition(Position); // Check if the grid position is valid if (!IsValidGridPosition(GridPosition.X, GridPosition.Y, GridPosition.Z)) { @@ -203,12 +197,12 @@ int32 FDensityField::WorldPositionToIndex(const FVector &Position) const { return GridPositionToIndex(GridPosition.X, GridPosition.Y, GridPosition.Z); } -FVector FDensityField::WorldToGridPosition(const FVector& Position) const +FIntVector3 FDensityField::WorldToGridPosition(const FVector& Position) const { if (XStep == 0 || YStep == 0 || ZStep == 0) { UE_LOG(LogTemp, Warning, TEXT("Grid steps are zero, cannot convert position to grid coordinates.")); - return FVector(-1, -1, -1); // Return an invalid grid position if any grid step is zero + return FIntVector3(-1, -1, -1); // Return an invalid grid position if any grid step is zero } // Calculate the grid coordinates by subtracting the grid origin and dividing by the grid step sizes @@ -220,20 +214,55 @@ FVector FDensityField::WorldToGridPosition(const FVector& Position) const if (GridX < 0 || GridX >= XNum || GridY < 0 || GridY >= YNum || GridZ < 0 || GridZ >= ZNum) { UE_LOG(LogTemp, Warning, TEXT("Position out of grid bounds, returning invalid grid coordinates.")); - return FVector(-1, -1, -1); // Return an invalid grid position if out of bounds + return FIntVector3(-1, -1, -1); // Return an invalid grid position if out of bounds } - return FVector(GridX, GridY, GridZ); + return FIntVector3(GridX, GridY, GridZ); } -bool FDensityField::IsValidGridPosition(const int32 XBin, const int32 YBin, const int32 ZBin) const +// GRID POSITION CONVERSION FUNCTIONS + +int32 FDensityField::GridPositionToIndex(const int32 X, const int32 Y, const int32 Z) const { - // Check if the provided indices are within the grid bounds - return (XBin >= 0 && XBin < XNum) && - (YBin >= 0 && YBin < YNum) && - (ZBin >= 0 && ZBin < ZNum); + return Z * XNum * YNum + Y * XNum + X; +} + +double FDensityField::GridPositionToVoxelDensity(const int32 XBin, const int32 YBin, const int32 ZBin) const +{ + // First, check if the provided indices are valid + if (IsValidGridPosition(XBin, YBin, ZBin)) + { + // Convert 3D grid coordinates to a linear index + const int32 Index = GridPositionToIndex(XBin, YBin, ZBin); + + return IndexToVoxelDensity(Index); + } + + // Return 0 if the indices are out of bounds or the index is invalid + return 0.0; +} + +FVector FDensityField::GridPositionToVoxelGradient(const int32 XBin, const int32 YBin, const int32 ZBin) const +{ + // First, check if the provided indices are valid + if (IsValidGridPosition(XBin, YBin, ZBin)) + { + // Convert 3D grid coordinates to a linear index + const int32 Index = GridPositionToIndex(XBin, YBin, ZBin); + + // Ensure the index is within the valid range + if (VoxelList.IsValidIndex(Index)) + { + // Return the gradient at the calculated index + return VoxelList[Index].GetVoxelGradient(); + } + } + // Return 0 if the indices are out of bounds or the index is invalid + return FVector::Zero(); } +// INDEX CONVERSION FUNCTIONS + FVector FDensityField::IndexToVoxelGradient(const int32 Index) const { if (VoxelList.IsValidIndex(Index)) @@ -252,30 +281,107 @@ FVector FDensityField::IndexToVoxelPosition(const int32 Index) const return -FVector::One(); } -FVector FDensityField::IndexToGridPosition(const int32 Index) const +FIntVector3 FDensityField::IndexToGridPosition(const int32 Index) const { if (IsValidIndex(Index)) { return VoxelList[Index].GetVoxelGridPos(); } - return -FVector::One(); + return FIntVector3(-1, -1, -1); } +TArray<FVector> FDensityField::IndexToVoxelCornersWorld(const int32 Index) const +{ + TArray<FVector> Corners; + + if (!VoxelList.IsValidIndex(Index)) + { + return Corners; // Return an empty array if the index is invalid + } + + FVector VoxelPosition = VoxelList[Index].GetVoxelPosition(); + + FVector HalfStep = FVector(XStep, YStep, ZStep) * 0.5f; + + // Define the 8 corners relative to the voxel's center position + Corners.Add(VoxelPosition + FVector(-HalfStep.X, -HalfStep.Y, -HalfStep.Z)); + Corners.Add(VoxelPosition + FVector(HalfStep.X, -HalfStep.Y, -HalfStep.Z)); + Corners.Add(VoxelPosition + FVector(HalfStep.X, HalfStep.Y, -HalfStep.Z)); + Corners.Add(VoxelPosition + FVector(-HalfStep.X, HalfStep.Y, -HalfStep.Z)); + Corners.Add(VoxelPosition + FVector(-HalfStep.X, -HalfStep.Y, HalfStep.Z)); + Corners.Add(VoxelPosition + FVector(HalfStep.X, -HalfStep.Y, HalfStep.Z)); + Corners.Add(VoxelPosition + FVector(HalfStep.X, HalfStep.Y, HalfStep.Z)); + Corners.Add(VoxelPosition + FVector(-HalfStep.X, HalfStep.Y, HalfStep.Z)); + + return Corners; +} -// GETTER AND SETTER FUNCTIONS +TArray<int32> FDensityField::IndexToVoxelNeighbors(const int32 Index) const +{ + TArray<int32> Neighbors; + if (IsValidIndex(Index)) + { + const FIntVector3 Pos = IndexToGridPosition(Index); + const int32 X = Pos.X; + const int32 Y = Pos.Y; + const int32 Z = Pos.Z; -FVector FDensityField::GetGridOrigin() const + // List all potential neighbors + TArray<FIntVector> PotentialNeighbors = { + FIntVector(X + 1, Y, Z), FIntVector(X - 1, Y, Z), + FIntVector(X, Y + 1, Z), FIntVector(X, Y - 1, Z), + FIntVector(X, Y, Z + 1), FIntVector(X, Y, Z - 1) + }; + + for (const FIntVector& NeighborPos : PotentialNeighbors) + { + if (IsValidGridPosition(NeighborPos.X, NeighborPos.Y, NeighborPos.Z)) + { + int NeighborIndex = GridPositionToIndex(NeighborPos.X, NeighborPos.Y, NeighborPos.Z); + Neighbors.Add(NeighborIndex); + } + } + } + return Neighbors; +} + +double FDensityField::IndexToVoxelDensity(const int32 Index) const { - return GridOrigin; + if (VoxelList.IsValidIndex(Index)) + { + return VoxelList[Index].GetVoxelDensity(); + } + return 0.0; +} + +// VALIDITY FUNCTIONS + +bool FDensityField::IsValidIndex(const int32 Index) const { + return VoxelList.IsValidIndex(Index); } -double FDensityField::GetVoxelDensityFromIndex(const int32 Index) const +bool FDensityField::IsValidWorldPosition(const FVector& Position) const { - if (VoxelList.IsValidIndex(Index)) - { - return VoxelList[Index].GetVoxelDensity(); - } - return 0.0; + // Convert the world position to grid coordinates + const FIntVector3 GridPosition = WorldToGridPosition(Position); + + // Check if the grid position is within the valid bounds + return IsValidGridPosition(GridPosition.X, GridPosition.Y, GridPosition.Z); +} + +bool FDensityField::IsValidGridPosition(const int32 XBin, const int32 YBin, const int32 ZBin) const +{ + // Check if the provided indices are within the grid bounds + return (XBin >= 0 && XBin < XNum) && + (YBin >= 0 && YBin < YNum) && + (ZBin >= 0 && ZBin < ZNum); +} + +// GETTER AND SETTER FUNCTIONS + +FVector FDensityField::GetGridOrigin() const +{ + return GridOrigin; } int32 FDensityField::GetVoxelNumber() const @@ -283,7 +389,7 @@ int32 FDensityField::GetVoxelNumber() const return VoxelList.Num(); } -void FDensityField::SetVoxelDensity(const int32 Index, const double Density) +void FDensityField::SetVoxelDensityByIndex(const int32 Index, const double Density) { if (VoxelList.IsValidIndex(Index)) { @@ -291,7 +397,7 @@ void FDensityField::SetVoxelDensity(const int32 Index, const double Density) } } -void FDensityField::SetVoxelGradient(const int32 Index, const FVector& Gradient) +void FDensityField::SetVoxelGradientByIndex(const int32 Index, const FVector& Gradient) { if (VoxelList.IsValidIndex(Index)) { @@ -299,40 +405,6 @@ void FDensityField::SetVoxelGradient(const int32 Index, const FVector& Gradient) } } -double FDensityField::GetVoxelDensityFromGridPosition(const int32 XBin, const int32 YBin, const int32 ZBin) const -{ - // First, check if the provided indices are valid - if (IsValidGridPosition(XBin, YBin, ZBin)) - { - // Convert 3D grid coordinates to a linear index - const int32 Index = GridPositionToIndex(XBin, YBin, ZBin); - - return GetVoxelDensityFromIndex(Index); - } - - // Return 0 if the indices are out of bounds or the index is invalid - return 0.0; -} - -FVector FDensityField::GetVoxelGradientFromGridPosition(const int32 XBin, const int32 YBin, const int32 ZBin) const -{ - // First, check if the provided indices are valid - if (IsValidGridPosition(XBin, YBin, ZBin)) - { - // Convert 3D grid coordinates to a linear index - const int32 Index = GridPositionToIndex(XBin, YBin, ZBin); - - // Ensure the index is within the valid range - if (VoxelList.IsValidIndex(Index)) - { - // Return the gradient at the calculated index - return VoxelList[Index].GetVoxelGradient(); - } - } - // Return 0 if the indices are out of bounds or the index is invalid - return FVector::Zero(); -} - FVector FDensityField::GetStep() const { return FVector(XStep, YStep, ZStep); @@ -348,47 +420,4 @@ int32 FDensityField::GetYNum() const { int32 FDensityField::GetZNum() const { return ZNum; -} - -bool FDensityField::IsValidWorldPosition(const FVector& Position) const -{ - // Convert the world position to grid coordinates - const FVector GridPosition = WorldToGridPosition(Position); - - // Check if the grid position is within the valid bounds - return IsValidGridPosition(GridPosition.X, GridPosition.Y, GridPosition.Z); -} - -TArray<int32> FDensityField::GetNeighborsFromIndex(const int32 Index) const -{ - TArray<int32> Neighbors; - if (VoxelList.IsValidIndex(Index)) - { - const int32 X = VoxelList[Index].GetVoxelGridPos().X; - const int32 Y = VoxelList[Index].GetVoxelGridPos().Y; - const int32 Z = VoxelList[Index].GetVoxelGridPos().Z; - - // List all potential neighbors - TArray<FIntVector> PotentialNeighbors = { - FIntVector(X+1, Y, Z), FIntVector(X-1, Y, Z), - FIntVector(X, Y+1, Z), FIntVector(X, Y-1, Z), - FIntVector(X, Y, Z+1), FIntVector(X, Y, Z-1) - }; - - for (const FIntVector& NeighborPos : PotentialNeighbors) - { - if (NeighborPos.X >= 0 && NeighborPos.X < XNum && - NeighborPos.Y >= 0 && NeighborPos.Y < YNum && - NeighborPos.Z >= 0 && NeighborPos.Z < ZNum) - { - int NeighborIndex = GridPositionToIndex(NeighborPos.Z, NeighborPos.Y, NeighborPos.Z); - Neighbors.Add(NeighborIndex); - } - } - } - return Neighbors; -} - -bool FDensityField::IsValidIndex(const int32 Index) const { - return VoxelList.IsValidIndex(Index); -} +} \ No newline at end of file diff --git a/Source/MetaCastBachelor/DensityField.h b/Source/MetaCastBachelor/DensityField.h index 3e71da4..7477fd5 100644 --- a/Source/MetaCastBachelor/DensityField.h +++ b/Source/MetaCastBachelor/DensityField.h @@ -12,17 +12,17 @@ struct FVoxel private: FVector WorldPosition; - FVector GridPosition; + FIntVector3 GridPosition; double VoxelDensity; FVector VoxelGradient; double ClosePointsNumber; public: - FVoxel() : WorldPosition(FVector::ZeroVector), GridPosition(FVector::ZeroVector), VoxelDensity(0.0), VoxelGradient(FVector::ZeroVector), ClosePointsNumber(0.0) {} - FVoxel(const FVector &InPosition, const FVector &InGridPos) : WorldPosition(InPosition), GridPosition(InGridPos), VoxelDensity(0.0), VoxelGradient(FVector::ZeroVector), ClosePointsNumber(0.0) {} + FVoxel() : WorldPosition(FVector::ZeroVector), GridPosition(FIntVector3::ZeroValue), VoxelDensity(0.0), VoxelGradient(FVector::ZeroVector), ClosePointsNumber(0.0) {} + FVoxel(const FVector &InPosition, const FIntVector3 &InGridPos) : WorldPosition(InPosition), GridPosition(InGridPos), VoxelDensity(0.0), VoxelGradient(FVector::ZeroVector), ClosePointsNumber(0.0) {} double GetVoxelDensity() const { return VoxelDensity; } - FVector GetVoxelGridPos() const { return GridPosition; } + FIntVector3 GetVoxelGridPos() const { return GridPosition; } FVector GetVoxelPosition() const { return WorldPosition; } FVector GetVoxelGradient() const { return VoxelGradient; } double GetClosePointsNumber() const { return ClosePointsNumber; } @@ -73,26 +73,27 @@ public: // CONVERSION FUNCTIONS int32 WorldPositionToIndex(const FVector& Position) const; - FVector WorldToGridPosition(const FVector& Position) const; + FIntVector3 WorldToGridPosition(const FVector& Position) const; bool IsValidGridPosition(int32 XBin, int32 YBin, int32 ZBin) const; int32 GridPositionToIndex(int32 X, int32 Y, int32 Z) const; + TArray<FVector> IndexToVoxelCornersWorld(const int32 Index) const; // GETTER AND SETTER FUNCTIONS FVector GetGridOrigin() const; FVector IndexToVoxelGradient(int32 Index) const; FVector IndexToVoxelPosition(int32 Index) const; - FVector IndexToGridPosition(int32 Index) const; + FIntVector3 IndexToGridPosition(int32 Index) const; int32 GetVoxelNumber() const; - double GetVoxelDensityFromIndex(int32 Index) const; - double GetVoxelDensityFromGridPosition(int32 XBin, int32 YBin, int32 ZBin) const; - FVector GetVoxelGradientFromGridPosition(int32 XBin, int32 YBin, int32 ZBin) const; + double IndexToVoxelDensity(int32 Index) const; + double GridPositionToVoxelDensity(int32 XBin, int32 YBin, int32 ZBin) const; + FVector GridPositionToVoxelGradient(int32 XBin, int32 YBin, int32 ZBin) const; FVector GetStep() const; int32 GetXNum() const; int32 GetYNum() const; int32 GetZNum() const; bool IsValidWorldPosition(const FVector& Position) const; - TArray<int32> GetNeighborsFromIndex(const int32 Index) const; + TArray<int32> IndexToVoxelNeighbors(const int32 Index) const; bool IsValidIndex(int32 Index) const; - void SetVoxelDensity(int32 Index, double Density); - void SetVoxelGradient(int32 Index, const FVector& Gradient); + void SetVoxelDensityByIndex(int32 Index, double Density); + void SetVoxelGradientByIndex(int32 Index, const FVector& Gradient); }; diff --git a/Source/MetaCastBachelor/MetaPoint.cpp b/Source/MetaCastBachelor/MetaPoint.cpp index 2e920d0..dd70f5d 100644 --- a/Source/MetaCastBachelor/MetaPoint.cpp +++ b/Source/MetaCastBachelor/MetaPoint.cpp @@ -1,18 +1,30 @@ #include "MetaPoint.h" #include "Utilities.h" -UMetaPoint::UMetaPoint() : Index(0), MyDensityField(nullptr), Threshold(0), MyProceduralMesh(nullptr) +UMetaPoint::UMetaPoint() : Index(0), MyDensityField(nullptr), Threshold(0), World(nullptr), MyProceduralMesh(nullptr) { // Initialize the Marching Cubes algorithm MyMarchingCubes = new MarchingCubes(); + + // Create the procedural mesh component + ProceduralMesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("GeneratedMesh")); } void UMetaPoint::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); +} void UMetaPoint::EraseParticles(const FVector& InputPosition) { @@ -31,20 +43,10 @@ void UMetaPoint::HandleMetaSelectPressed(const FInputActionInstance& Instance) FindAndMarkLocalMaximum(); } - // Method to perform gradient ascent to find the local maximum density starting from a given position. void UMetaPoint::FindAndMarkLocalMaximum() -{ - const UWorld* World = GetWorld(); - - if (!World || !MyPointCloud) { - UE_LOG(LogTemp, Warning, TEXT("Invalid world or point cloud context provided.")); - return; - } - +{ const FVector WorldPosition = SelectionObject->GetComponentLocation(); - UE_LOG(LogTemp, Log, TEXT("World Position: %s"), *WorldPosition.ToString()); - MyDensityField = MyPointCloud->MyDensityField; // Convert the world position of the selection object to the local position relative to the point cloud @@ -52,39 +54,115 @@ void UMetaPoint::FindAndMarkLocalMaximum() // Perform gradient ascent to find the local maximum starting from the converted local position LocalMaximum = FUtilities::FollowGradientToMaximum(MyDensityField, StartPosition); - UE_LOG(LogTemp, Log, TEXT("Local maximum found at world position: %s"), *WorldMaximum.ToString()); // Convert the local maximum back to world coordinates Index = MyDensityField->WorldPositionToIndex(LocalMaximum); Threshold = FUtilities::InterpolateDensityAtPosition(MyDensityField, LocalMaximum); TestingThresholdFactor = 1; + + // Initialize Visited array to false for all voxels + Visited.Init(false, MyDensityField->GetVoxelNumber()); + + // Initialize IndicesToVisit with the starting index + IndicesToVisit.Empty(); + IndicesToVisit.Add(Index); + + // Clear FloodedIndices + FloodedIndices.Empty(); + + World = GetWorld(); } void UMetaPoint::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); - TestingThresholdFactor -= DeltaTime * 0.1f; + //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; + } } void UMetaPoint::SelectParticles(const FVector& InputPosition) { - const UWorld* World = GetWorld(); - WorldMaximum = MyPointCloud->PointCloudVisualizer->GetComponentTransform().TransformPosition(LocalMaximum); - + //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); - auto Voxels = FUtilities::FloodFilling(MyDensityField, Index, Threshold * TestingThresholdFactor); + //DrawDebugSphere(World, WorldMaximum, SelectionRadius / 2, 32, FColor::Red, false, 0); - for (const int32 Index2 : Voxels) + if (AccumulatedTime >= 0.1f) { - MyPointCloud->DrawVoxel(Index2, 0); - } -} + AccumulatedTime = -10.0f; + 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; + }); + }); + } +} +void UMetaPoint::GenerateVoxelMesh(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); + + // 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); + + // 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::Red); + } + } + // Create the mesh section + ProceduralMesh->CreateMeshSection(0, Vertices, Triangles, TArray<FVector>(), TArray<FVector2D>(), VertexColors, TArray<FProcMeshTangent>(), true); +} diff --git a/Source/MetaCastBachelor/MetaPoint.h b/Source/MetaCastBachelor/MetaPoint.h index c95ffd9..ce3e88b 100644 --- a/Source/MetaCastBachelor/MetaPoint.h +++ b/Source/MetaCastBachelor/MetaPoint.h @@ -18,6 +18,20 @@ class UMetaPoint : public UMetaCastBaseline FVector LocalMaximum; FDensityField* MyDensityField; float Threshold; + UPROPERTY() + UWorld* World; + float AccumulatedTime = 0.0; + + UPROPERTY() + UProceduralMeshComponent* ProceduralMesh; + + UPROPERTY(EditAnywhere) + UMaterialInterface* SelectionVolumeMat; + + TArray<bool> Visited; + TArray<int32> IndicesToVisit; + TArray<int32> FloodedIndices; + TArray<int32> RevisitIndices; public: @@ -38,6 +52,8 @@ public: virtual void HandleMetaSelectPressed(const FInputActionInstance& Instance) override; void FindAndMarkLocalMaximum(); + void GenerateVoxelMesh(const TArray<int32>& Voxels) const; + virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; }; diff --git a/Source/MetaCastBachelor/PointCloud.cpp b/Source/MetaCastBachelor/PointCloud.cpp index a5a93cf..d6482df 100644 --- a/Source/MetaCastBachelor/PointCloud.cpp +++ b/Source/MetaCastBachelor/PointCloud.cpp @@ -91,7 +91,7 @@ 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 = 0.5f; // Half the size of a voxel + 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); @@ -102,6 +102,7 @@ void APointCloud::SetupDensityFieldFromPointCloud() const //MyDensityField->DrawDebugVoxelDensity(World, this, 100.0f, 1.0f, 0.8f); } } + void APointCloud::UpdateSelection() { for (int32 i = 0; i < PositionVectors.Num(); i++) @@ -118,7 +119,7 @@ void APointCloud::UpdateSelection() PointCloudVisualizer->SetInputAndConvert2(PositionVectors, PointColors); } -void APointCloud::DrawVoxel(const int Index, float Time) const +void APointCloud::DrawVoxel(const int Index, const float Time) const { if(!MyDensityField->IsValidIndex(Index)) { @@ -129,11 +130,8 @@ void APointCloud::DrawVoxel(const int Index, float Time) const const FVector VoxelWorldPosition = PointCloudVisualizer->GetComponentTransform().TransformPosition(MyDensityField->IndexToVoxelPosition(Index)); const FVector WorldScale = PointCloudVisualizer->GetComponentScale(); - // Scale the voxel size by the component's world scale to ensure it reflects the actual dimensions - FVector ScaledVoxelSize = GetActorRightVector(); - // Draw the debug box with the correct world space dimensions - DrawDebugBox(GetWorld(), VoxelWorldPosition, VoxelHalfExtents, this->GetActorQuat(), FColor::Green, false, Time, 0, 0.1f); + DrawDebugBox(GetWorld(), VoxelWorldPosition, VoxelHalfExtents * WorldScale, this->GetActorQuat(), FColor::Green, false, Time, 0, 0.1f); } diff --git a/Source/MetaCastBachelor/Utilities.cpp b/Source/MetaCastBachelor/Utilities.cpp index 939fe97..343a2ee 100644 --- a/Source/MetaCastBachelor/Utilities.cpp +++ b/Source/MetaCastBachelor/Utilities.cpp @@ -41,14 +41,14 @@ double FUtilities::InterpolateDensityAtPosition(const FDensityField* DensityFiel const float YFraction = GridPos.Y - YBin; const float ZFraction = GridPos.Z - ZBin; - const double D000 = DensityField->GetVoxelDensityFromGridPosition(XBin, YBin, ZBin); - const double D100 = DensityField->GetVoxelDensityFromGridPosition(XBin + 1, YBin, ZBin); - const double D010 = DensityField->GetVoxelDensityFromGridPosition(XBin, YBin + 1, ZBin); - const double D001 = DensityField->GetVoxelDensityFromGridPosition(XBin, YBin, ZBin + 1); - const double D101 = DensityField->GetVoxelDensityFromGridPosition(XBin + 1, YBin, ZBin + 1); - const double D011 = DensityField->GetVoxelDensityFromGridPosition(XBin, YBin + 1, ZBin + 1); - const double D110 = DensityField->GetVoxelDensityFromGridPosition(XBin + 1, YBin + 1, ZBin); - const double D111 = DensityField->GetVoxelDensityFromGridPosition(XBin + 1, YBin + 1, ZBin + 1); + const double D000 = DensityField->GridPositionToVoxelDensity(XBin, YBin, ZBin); + const double D100 = DensityField->GridPositionToVoxelDensity(XBin + 1, YBin, ZBin); + const double D010 = DensityField->GridPositionToVoxelDensity(XBin, YBin + 1, ZBin); + const double D001 = DensityField->GridPositionToVoxelDensity(XBin, YBin, ZBin + 1); + const double D101 = DensityField->GridPositionToVoxelDensity(XBin + 1, YBin, ZBin + 1); + const double D011 = DensityField->GridPositionToVoxelDensity(XBin, YBin + 1, ZBin + 1); + const double D110 = DensityField->GridPositionToVoxelDensity(XBin + 1, YBin + 1, ZBin); + const double D111 = DensityField->GridPositionToVoxelDensity(XBin + 1, YBin + 1, ZBin + 1); // Trilinear interpolation const double DX00 = FMath::Lerp(D000, D100, XFraction); @@ -81,14 +81,14 @@ FVector FUtilities::InterpolateGradientAtPosition(const FDensityField* DensityFi float ZFraction = GridPos.Z - ZBin; // Fetch gradients from corners of the cell - FVector G000 = DensityField->GetVoxelGradientFromGridPosition(XBin, YBin, ZBin); - FVector G100 = DensityField->GetVoxelGradientFromGridPosition(XBin + 1, YBin, ZBin); - FVector G010 = DensityField->GetVoxelGradientFromGridPosition(XBin, YBin + 1, ZBin); - FVector G001 = DensityField->GetVoxelGradientFromGridPosition(XBin, YBin, ZBin + 1); - FVector G110 = DensityField->GetVoxelGradientFromGridPosition(XBin + 1, YBin + 1, ZBin); - FVector G101 = DensityField->GetVoxelGradientFromGridPosition(XBin + 1, YBin, ZBin + 1); - FVector G011 = DensityField->GetVoxelGradientFromGridPosition(XBin, YBin + 1, ZBin + 1); - FVector G111 = DensityField->GetVoxelGradientFromGridPosition(XBin + 1, YBin + 1, ZBin + 1); + FVector G000 = DensityField->GridPositionToVoxelGradient(XBin, YBin, ZBin); + FVector G100 = DensityField->GridPositionToVoxelGradient(XBin + 1, YBin, ZBin); + FVector G010 = DensityField->GridPositionToVoxelGradient(XBin, YBin + 1, ZBin); + FVector G001 = DensityField->GridPositionToVoxelGradient(XBin, YBin, ZBin + 1); + FVector G110 = DensityField->GridPositionToVoxelGradient(XBin + 1, YBin + 1, ZBin); + FVector G101 = DensityField->GridPositionToVoxelGradient(XBin + 1, YBin, ZBin + 1); + FVector G011 = DensityField->GridPositionToVoxelGradient(XBin, YBin + 1, ZBin + 1); + FVector G111 = DensityField->GridPositionToVoxelGradient(XBin + 1, YBin + 1, ZBin + 1); // Interpolate along x for each of the yz pairs FVector G00 = FMath::Lerp(G000, G100, XFraction); @@ -152,7 +152,7 @@ FVector FUtilities::FollowGradientToMaximum(const FDensityField* DensityField, c TArray<int32> FUtilities::FloodFilling(const FDensityField* DensityField, const int32 StartIndex, const float Threshold) { - UE_LOG(LogTemp, Warning, TEXT("T: %f"), Threshold); + //UE_LOG(LogTemp, Warning, TEXT("Threshold for FloodFilling: %f"), Threshold); TArray<int32> VisitedIndices; if (!DensityField) { @@ -178,23 +178,108 @@ TArray<int32> FUtilities::FloodFilling(const FDensityField* DensityField, const Visited[CurrentIndex] = true; VisitedIndices.Add(CurrentIndex); - TArray<int32> Neighbors = DensityField->GetNeighborsFromIndex(CurrentIndex); - UE_LOG(LogTemp, Log, TEXT("Visiting Node %d at Position %s with %d neighbors."), CurrentIndex, *DensityField->IndexToGridPosition(CurrentIndex).ToString(), Neighbors.Num()); + TArray<int32> Neighbors = DensityField->IndexToVoxelNeighbors(CurrentIndex); + //UE_LOG(LogTemp, Log, TEXT("Visiting Node %d at Position %s with %d neighbors."), CurrentIndex, *DensityField->IndexToGridPosition(CurrentIndex).ToString(), Neighbors.Num()); for (int32 NeighborIndex : Neighbors) { - if (DensityField->GetVoxelDensityFromIndex(NeighborIndex) > Threshold && !Visited[NeighborIndex]) { + const double NeighborDensity = DensityField->IndexToVoxelDensity(NeighborIndex); + //UE_LOG(LogTemp, Log, TEXT("Lookign at Neighbor %d at Position %s with density: %f."), NeighborIndex, *DensityField->IndexToGridPosition(NeighborIndex).ToString(), NeighborDensity); + + if (NeighborDensity > Threshold && !Visited[NeighborIndex]) { Stack.Push(NeighborIndex); + //UE_LOG(LogTemp, Log, TEXT("Pushing Neighbor %d to stack."), NeighborIndex); } } } } // Logging final details - UE_LOG(LogTemp, Log, TEXT("Flood filling completed from start index %d. Total voxels selected: %d"), StartIndex, VisitedIndices.Num()); + //UE_LOG(LogTemp, Log, TEXT("Flood filling completed from start index %d. Total voxels selected: %d"), StartIndex, VisitedIndices.Num()); for (const int32 idx : VisitedIndices) { - UE_LOG(LogTemp, Log, TEXT("Selected Voxel Index: %d at Grid Position: %s"), idx, *DensityField->IndexToGridPosition(idx).ToString()); + //UE_LOG(LogTemp, Log, TEXT("Selected Voxel Index: %d at Grid Position: %s"), idx, *DensityField->IndexToGridPosition(idx).ToString()); } 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) { + UE_LOG(LogTemp, Warning, TEXT("DensityField is null.")); + return; + } + + TArray<int32> CurrentPotentialRevisit; + + constexpr int MaxIterations = 100; + int IterationCount = 0; + while (IndicesToVisit.Num() > 0) + { + IterationCount++; + if(IterationCount > MaxIterations) + { + while (IndicesToVisit.Num() > 0) { + CurrentPotentialRevisit.Add(IndicesToVisit.Pop()); + } + break; + } + + int32 CurrentIndex = IndicesToVisit.Pop(); + VisitedIndices[CurrentIndex] = true; + const double CurrentDensity = DensityField->IndexToVoxelDensity(CurrentIndex); + + if (CurrentDensity >= Threshold) { + FloodedIndices.Add(CurrentIndex); + + TArray<int32> Neighbors = DensityField->IndexToVoxelNeighbors(CurrentIndex); + for (int32 NeighborIndex : Neighbors) + { + if (!VisitedIndices[NeighborIndex]) + { + IndicesToVisit.Push(NeighborIndex); + } + } + } else { + CurrentPotentialRevisit.Add(CurrentIndex); + } + } + + // Update PotentialRevisit with the new list from this run + PotentialRevisit = CurrentPotentialRevisit; +} + + + diff --git a/Source/MetaCastBachelor/Utilities.h b/Source/MetaCastBachelor/Utilities.h index 72313c1..6d406d7 100644 --- a/Source/MetaCastBachelor/Utilities.h +++ b/Source/MetaCastBachelor/Utilities.h @@ -11,4 +11,5 @@ public: static float GaussianKernel(float Distance, float Sigma); static FVector FollowGradientToMaximum(const FDensityField* DensityField, const FVector& StartPosition); static TArray<int32> FloodFilling(const FDensityField* DensityField, const int32 StartIndex, const float Threshold); + static void FloodFilling_2(const FDensityField* DensityField, const float Threshold, TArray<bool>& VisitedIndices, TArray<int32>& IndicesToVisit, TArray<int32>& FloodedIndices, TArray<int32>& PotentialRevisit); }; -- GitLab