// Copyright 2020 Phyronnaz #include "VoxelDebugEditor.h" #include "VoxelDebug.h" #include "VoxelUtilities/VoxelMathUtilities.h" #include "VoxelUtilities/VoxelTextureUtilities.h" #include "VoxelUtilities/VoxelDistanceFieldUtilities.h" #include "VoxelUtilities/VoxelConfigUtilities.h" #include "Styling/SlateStyleRegistry.h" #include "Framework/Docking/TabManager.h" #include "Widgets/Docking/SDockTab.h" #include "Widgets/Images/SImage.h" #include "Widgets/Layout/SScaleBox.h" #include "Widgets/Layout/SSplitter.h" #include "Widgets/Layout/SGridPanel.h" #include "Widgets/Layout/SBox.h" #include "Widgets/SBoxPanel.h" #include "Widgets/Input/SSpinBox.h" #include "Widgets/Input/SCheckBox.h" #include "Engine/Texture2D.h" #include "Engine/StaticMesh.h" #include "Modules/ModuleManager.h" #include "UObject/StrongObjectPtr.h" #include "PropertyEditorModule.h" #include "Async/Async.h" class SVoxelDebug : public SCompoundWidget { public: SLATE_BEGIN_ARGS(SVoxelDebug) { } SLATE_END_ARGS() void Construct(const FArguments& Args) { FVoxelDebug::GetDelegate().Add(MakeWeakPtrDelegate(this, [=](FName Name, const FIntVector& Size, TArrayView Data) { AsyncTask(ENamedThreads::GameThread, [Name, Size, Data = TArray(Data.GetData(), Data.Num())]() { auto& CustomData = *GetMutableDefault(); auto& Array = CustomData.FloatData.FindOrAdd(Name); Array.Insert({ Data, Size }, 0); Array.SetNum(FMath::Min(Array.Num(), 100)); CustomData.DataToDisplay.FindOrAdd(Name); }); })); FVoxelDebug::GetDelegate().Add(MakeWeakPtrDelegate(this, [=](FName Name, const FIntVector& Size, TArrayView Data) { AsyncTask(ENamedThreads::GameThread, [Name, Size, Data = TArray(Data.GetData(), Data.Num())]() { auto& CustomData = *GetMutableDefault(); auto& Array = CustomData.ValueData.FindOrAdd(Name); Array.Insert({ Data, Size }, 0); Array.SetNum(FMath::Min(Array.Num(), 100)); CustomData.DataToDisplay.FindOrAdd(Name); }); })); FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked("PropertyEditor"); FDetailsViewArgs DetailsViewArgs(false, false, false, FDetailsViewArgs::HideNameArea); DetailsViewArgs.DefaultsOnlyVisibility = EEditDefaultsOnlyNodeVisibility::Automatic; Details = PropertyEditorModule.CreateDetailView(DetailsViewArgs); const auto BaseDetails = PropertyEditorModule.CreateDetailView(DetailsViewArgs); const auto Lambda = [=](const FPropertyChangedEvent& PropertyChangedEvent) { UpdateTexture(); }; Details->OnFinishedChangingProperties().AddLambda(Lambda); BaseDetails->OnFinishedChangingProperties().AddLambda(Lambda); BaseDetails->SetObject(GetMutableDefault()); ChildSlot [ SNew(SSplitter) + SSplitter::Slot() .Value(1.f) [ SNew(SScaleBox) .Stretch(EStretch::ScaleToFit) [ SNew(SOverlay) + SOverlay::Slot() [ SNew(SImage) .Image(&*Brush) ] + SOverlay::Slot() [ SAssignNew(Grid, SGridPanel) ] ] ] + SSplitter::Slot() .Value(1.f) [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ BaseDetails ] + SVerticalBox::Slot() .AutoHeight() [ Details.ToSharedRef() ] ] ]; GetMutableDefault()->PostEditChange.AddSP(this, &SVoxelDebug::UpdateTexture); FVoxelConfigUtilities::LoadConfig(GetMutableDefault(), "VoxelDebugParameters_JumpFlood"); FVoxelConfigUtilities::LoadConfig(GetMutableDefault(), "VoxelDebugParameters_CustomData"); UpdateTexture(); } void UpdateTexture() { const auto GetSliceIndex = [](auto& Parameters, int32 X, int32 Y, const FIntVector& Size) { Parameters.Slice = FMath::Clamp(Parameters.Slice, 0, Size[int32(Parameters.SliceAxis)] - 1); FIntVector Position; Position[int32(Parameters.SliceAxis)] = Parameters.Slice; Position[(int32(Parameters.SliceAxis) + 1) % 3] = Parameters.bFlip ? Y : X; Position[(int32(Parameters.SliceAxis) + 2) % 3] = Parameters.bFlip ? X : Y; return Position.X + Size.X * Position.Y + Size.X * Size.Y * Position.Z; }; const auto ProjectToTexture = [](auto& Parameters, auto X, auto Y, auto Z, auto& OutX, auto& OutY) { TArray::Type, TFixedAllocator<3>> Array = { X, Y, Z }; OutX = Array[(int32(Parameters.SliceAxis) + 1) % 3]; OutY = Array[(int32(Parameters.SliceAxis) + 2) % 3]; if (Parameters.bFlip) { Swap(OutX, OutY); } }; const auto GetTextureSize = [&](auto& Parameters, const FIntVector& Size) { FIntPoint TextureSize; ProjectToTexture(Parameters, Size.X, Size.Y, Size.Z, TextureSize.X, TextureSize.Y); return TextureSize; }; const auto GetColorIndex = [](const FIntPoint& TextureSize, int32 X, int32 Y) { return X + TextureSize.X * (TextureSize.Y - 1 - Y); }; GetMutableDefault()->bUpdate = false; const auto Type = GetDefault()->DebugType; if (Type == EVoxelDebugType::JumpFlood) { auto& Parameters = *GetMutableDefault(); Details->SetObject(&Parameters); FVoxelConfigUtilities::SaveConfig(&Parameters, "VoxelDebugParameters_JumpFlood"); FIntVector Size = FIntVector::ZeroValue; TArray SurfacePositions; TArray Distances; if (Parameters.bUseMesh) { if (!Parameters.Mesh) { return; } if (Mesh != Parameters.Mesh) { UVoxelMeshImporterLibrary::CreateMeshDataFromStaticMesh(Parameters.Mesh, MeshData); Mesh = Parameters.Mesh; } const double StartTime = FPlatformTime::Seconds(); FIntVector PositionOffset; int32 NumLeaks = 0; UVoxelMeshImporterLibrary::ConvertMeshToDistanceField( MeshData, Parameters.Transform, Parameters.MeshImporterSettings, Parameters.BoxExtension, Distances, SurfacePositions, Size, PositionOffset, NumLeaks, Parameters.bUseCPU ? EVoxelComputeDevice::CPU : EVoxelComputeDevice::GPU, Parameters.bMultiThreaded, Parameters.Passes); const double EndTime = FPlatformTime::Seconds(); Parameters.TimeInSeconds = EndTime - StartTime; } else { Size = FIntVector(Parameters.TextureSize); SurfacePositions.SetNumUninitialized(Size.X * Size.Y * Size.Z); Distances.SetNumUninitialized(Size.X * Size.Y * Size.Z); for (int32 X = 0; X < Size.X; X++) { for (int32 Y = 0; Y < Size.Y; Y++) { for (int32 Z = 0; Z < Size.Z; Z++) { FVector3f Position; float Distance; if (FVector3f(X - Size.X / 2.f, Y - Size.Y / 2.f, Z - Size.Z / 2.f).Size() < 16) { Position = FVector3f(X, Y, Z); Distance = 0.f; } else { Position = FVector3f(1e9); Distance = 1e9; } const int32 Index = X + Size.X * Y + Size.X * Size.Y * Z; SurfacePositions[Index] = Position; Distances[Index] = Distance; } } } const double StartTime = FPlatformTime::Seconds(); FVoxelDistanceFieldUtilities::DownSample(Size, Distances, SurfacePositions, Parameters.Divisor, Parameters.bShrink); FVoxelDistanceFieldUtilities::JumpFlood(Size, SurfacePositions, Parameters.bUseCPU ? EVoxelComputeDevice::CPU : EVoxelComputeDevice::GPU, Parameters.bMultiThreaded, Parameters.Passes); FVoxelDistanceFieldUtilities::GetDistancesFromSurfacePositions(Size, SurfacePositions, Distances); const double EndTime = FPlatformTime::Seconds(); Parameters.TimeInSeconds = EndTime - StartTime; } Parameters.Size = Size; const auto TextureSize = GetTextureSize(Parameters, Size); TArray Colors; Colors.Empty(TextureSize.X * TextureSize.Y); Colors.SetNumUninitialized(TextureSize.X * TextureSize.Y); for (int32 X = 0; X < TextureSize.X; X++) { for (int32 Y = 0; Y < TextureSize.Y; Y++) { const int32 ColorIndex = GetColorIndex(TextureSize, X, Y); const int32 Index = GetSliceIndex(Parameters, X, Y, Size); const FVector3f Position = SurfacePositions[Index]; const float Distance = Distances[Index]; if (Position.X >= 1e9) { Colors[ColorIndex] = FColor::Blue; } else { if (Parameters.bShowDistances) { Colors[ColorIndex] = FVoxelDistanceFieldUtilities::GetDistanceFieldColor(Distance / float(TextureSize.GetMax())); } else { float FX; float FY; ProjectToTexture(Parameters, Position.X / Size.X, Position.Y / Size.Y, Position.Z / Size.Z, FX, FY); Colors[ColorIndex] = FColor( FVoxelUtilities::FloatToUINT8(FX), FVoxelUtilities::FloatToUINT8(FY), 0); } } } } SetColors(Colors, TextureSize); SetGrid({}, {}); } else { auto& Parameters = *GetMutableDefault(); Details->SetObject(&Parameters); FVoxelConfigUtilities::SaveConfig(&Parameters, "VoxelDebugParameters_CustomData"); FName Name; for (auto& It : Parameters.DataToDisplay) { if (It.Value) { Name = It.Key; break; } } TArray Colors; TArray Text; FIntVector Size { ForceInit }; FIntPoint TextureSize { ForceInit }; if (auto* ValueData = Parameters.ValueData.Find(Name)) { Parameters.Frame = FMath::Clamp(Parameters.Frame, 0, ValueData->Num() - 1); auto& Data = (*ValueData)[Parameters.Frame].Data; Size = (*ValueData)[Parameters.Frame].Size; TextureSize = GetTextureSize(Parameters, Size); Colors.Empty(TextureSize.X * TextureSize.Y); Colors.SetNumUninitialized(TextureSize.X * TextureSize.Y); Text.Empty(TextureSize.X * TextureSize.Y); Text.SetNum(TextureSize.X * TextureSize.Y); for (int32 X = 0; X < TextureSize.X; X++) { for (int32 Y = 0; Y < TextureSize.Y; Y++) { const int32 Index = GetSliceIndex(Parameters, X, Y, Size); const int32 ColorIndex = GetColorIndex(TextureSize, X, Y); const FVoxelValue Value = Data[Index]; if (Value.GetStorage() == 0) { Colors[ColorIndex] = FColor::Green; } else { Colors[ColorIndex] = FColor( FVoxelUtilities::FloatToUINT8(-Value.ToFloat()), 0, FVoxelUtilities::FloatToUINT8(Value.ToFloat())); } Text[ColorIndex] = LexToString(Value.GetStorage()); } } } if (auto* FloatData = Parameters.FloatData.Find(Name)) { Parameters.Frame = FMath::Clamp(Parameters.Frame, 0, FloatData->Num() - 1); auto& Data = (*FloatData)[Parameters.Frame].Data; Size = (*FloatData)[Parameters.Frame].Size; TextureSize = GetTextureSize(Parameters, Size); Colors.Empty(TextureSize.X * TextureSize.Y); Colors.SetNumUninitialized(TextureSize.X * TextureSize.Y); Text.Empty(TextureSize.X * TextureSize.Y); Text.SetNum(TextureSize.X * TextureSize.Y); for (int32 X = 0; X < TextureSize.X; X++) { for (int32 Y = 0; Y < TextureSize.Y; Y++) { const int32 Index = GetSliceIndex(Parameters, X, Y, Size); const int32 ColorIndex = GetColorIndex(TextureSize, X, Y); const float Distance = Data[Index]; if (Distance >= 1e9) { Colors[ColorIndex] = FColor::Green; } else { const float Value = Distance / float(TextureSize.GetMax()); Colors[ColorIndex] = FColor( FVoxelUtilities::FloatToUINT8(-Value), 0, FVoxelUtilities::FloatToUINT8(Value)); } Text[ColorIndex] = LexToString(Distance); } } } Parameters.Size = Size; SetColors(Colors, TextureSize); SetGrid(Text, TextureSize); } } void SetColors(const TArray& Colors, const FIntPoint& Size) { auto* Texture = TexturePtr.Get(); if (Size.X == 0 || Size.Y == 0) { FVoxelTextureUtilities::UpdateColorTexture(Texture, { 1, 1 }, { FColor::Black }); } else { FVoxelTextureUtilities::UpdateColorTexture(Texture, Size, Colors); } if (ensure(Texture)) { Texture->Filter = TF_Nearest; TexturePtr.Reset(Texture); } Brush->SetImageSize(Size); Brush->SetResourceObject(Texture); } void SetGrid(const TArray& Data, const FIntPoint& Size) const { Grid->ClearChildren(); if (Size.X == 0 || Size.Y == 0 || Data.Num() == 0) { return; } check(Data.Num() == Size.X * Size.Y); for (int32 X = 0; X < Size.X; X++) { for (int32 Y = 0; Y < Size.Y; Y++) { Grid->AddSlot(X, Y) [ SNew(SBox) .WidthOverride(50) .HeightOverride(50) .Padding(FMargin(2)) .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew(SScaleBox) .Stretch(EStretch::ScaleToFitX) [ SNew(STextBlock) .Text(FText::FromString(Data[X + Size.X * Y])) ] ] ]; } } } private: const TSharedRef Brush = MakeShared(); TStrongObjectPtr TexturePtr; TSharedPtr Details; TSharedPtr Grid; TWeakObjectPtr Mesh; FVoxelMeshImporterInputData MeshData; }; void UVoxelDebugParameters_CustomData::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); PostEditChange.Broadcast(); } TSharedRef FVoxelDebugEditor::CreateTab(const FSpawnTabArgs& Args) { return SNew(SDockTab) .Icon(FSlateStyleRegistry::FindSlateStyle("VoxelStyle")->GetBrush("VoxelIcon")) .TabRole(ETabRole::NomadTab) [ SNew(SVoxelDebug) ]; }