// Copyright Epic Games, Inc. All Rights Reserved. #include "ClothPaintTools.h" #include "ClothMeshAdapter.h" #include "ClothPaintSettings.h" #include "ClothPaintToolCommands.h" #include "ClothPainter.h" #include "Components/PrimitiveComponent.h" #include "Components/SkeletalMeshComponent.h" #include "Containers/Map.h" #include "Containers/Queue.h" #include "Delegates/Delegate.h" #include "DetailCategoryBuilder.h" #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.h" #include "Engine/EngineTypes.h" #include "Engine/HitResult.h" #include "Engine/NetSerialization.h" #include "Framework/Commands/UIAction.h" #include "Framework/Commands/UICommandList.h" #include "HAL/PlatformCrt.h" #include "IDetailCustomization.h" #include "IDetailsView.h" #include "IMeshPaintGeometryAdapter.h" #include "Input/Reply.h" #include "Internationalization/Internationalization.h" #include "Math/Color.h" #include "Math/NumericLimits.h" #include "Math/Transform.h" #include "Math/UnrealMathSSE.h" #include "Math/Vector.h" #include "Math/Vector4.h" #include "MeshPaintHelpers.h" #include "MeshPaintSettings.h" #include "MeshPaintTypes.h" #include "Misc/AssertionMacros.h" #include "PropertyEditorDelegates.h" #include "PrimitiveDrawingUtils.h" #include "ScopedTransaction.h" #include "Serialization/StructuredArchiveAdapters.h" #include "Templates/Casts.h" #include "Templates/Tuple.h" #include "UObject/Class.h" #include "UObject/NameTypes.h" #include "UObject/Package.h" #include "UObject/WeakObjectPtr.h" #include "UObject/WeakObjectPtrTemplates.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/Input/SButton.h" class FEditorViewportClient; class FSceneView; class FViewport; class IPropertyHandle; #define LOCTEXT_NAMESPACE "ClothTools" FClothPaintTool_Brush::~FClothPaintTool_Brush() { if(Settings) { Settings->RemoveFromRoot(); } } FText FClothPaintTool_Brush::GetDisplayName() const { return LOCTEXT("ToolName_Brush", "Brush"); } FPerVertexPaintAction FClothPaintTool_Brush::GetPaintAction(const FMeshPaintParameters& InPaintParams, UClothPainterSettings* InPainterSettings) { return FPerVertexPaintAction::CreateRaw(this, &FClothPaintTool_Brush::PaintAction, InPaintParams.InverseBrushToWorldMatrix); } UObject* FClothPaintTool_Brush::GetSettingsObject() { if(!Settings) { Settings = DuplicateObject(GetMutableDefault(), GetTransientPackage()); Settings->AddToRoot(); } return Settings; } bool FClothPaintTool_Brush::HasValueRange() { return true; } void FClothPaintTool_Brush::GetValueRange(float& OutRangeMin, float& OutRangeMax) { OutRangeMin = Settings->PaintValue; OutRangeMax = OutRangeMin; } void FClothPaintTool_Brush::PaintAction(FPerVertexPaintActionArgs& InArgs, int32 VertexIndex, FMatrix InverseBrushMatrix) { FClothMeshPaintAdapter* ClothAdapter = (FClothMeshPaintAdapter*)InArgs.Adapter; if(ClothAdapter) { TSharedPtr SharedPainter = Painter.Pin(); if(SharedPainter.IsValid()) { UPaintBrushSettings* BrushSettings = SharedPainter->GetBrushSettings(); UClothPainterSettings* PaintSettings = Cast(SharedPainter->GetPainterSettings()); FVector Position; ClothAdapter->GetVertexPosition(VertexIndex, Position); Position = ClothAdapter->GetComponentToWorldMatrix().TransformPosition(Position); float Value = SharedPainter->GetPropertyValue(VertexIndex); const float BrushRadius = BrushSettings->GetBrushRadius(); MeshPaintHelpers::ApplyBrushToVertex(Position, InverseBrushMatrix, BrushRadius, BrushSettings->BrushFalloffAmount, BrushSettings->BrushStrength, Settings->PaintValue, Value); SharedPainter->SetPropertyValue(VertexIndex, Value); } } } FClothPaintTool_Gradient::~FClothPaintTool_Gradient() { if(Settings) { Settings->RemoveFromRoot(); } } FText FClothPaintTool_Gradient::GetDisplayName() const { return LOCTEXT("ToolName_Gradient", "Gradient"); } bool FClothPaintTool_Gradient::InputKey(IMeshPaintGeometryAdapter* Adapter, FEditorViewportClient* InViewportClient, FViewport* InViewport, FKey InKey, EInputEvent InEvent) { bool bHandled = false; if(InKey == EKeys::LeftControl || InKey == EKeys::RightControl) { if(InEvent == IE_Pressed) { bSelectingBeginPoints = false; bHandled = true; } else if(InEvent == IE_Released) { bSelectingBeginPoints = true; bHandled = true; } } return bHandled; } FPerVertexPaintAction FClothPaintTool_Gradient::GetPaintAction(const FMeshPaintParameters& InPaintParams, UClothPainterSettings* InPainterSettings) { return FPerVertexPaintAction::CreateRaw(this, &FClothPaintTool_Gradient::PaintAction, InPaintParams.InverseBrushToWorldMatrix); } bool FClothPaintTool_Gradient::IsPerVertex() const { return false; } void FClothPaintTool_Gradient::Render(USkeletalMeshComponent* InComponent, IMeshPaintGeometryAdapter* InAdapter, const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) { TSharedPtr SharedPainter = Painter.Pin(); if(!SharedPainter.IsValid()) { return; } const float VertexPointSize = GetDefault()->VertexPreviewSize; const UClothPainterSettings* PaintSettings = CastChecked(SharedPainter->GetPainterSettings()); const UPaintBrushSettings* BrushSettings = SharedPainter->GetBrushSettings(); TArray PaintRays; MeshPaintHelpers::RetrieveViewportPaintRays(View, Viewport, PDI, PaintRays); const FMatrix ComponentToWorldMatrix = InComponent->GetComponentTransform().ToMatrixWithScale(); for (const int32& Index : GradientStartIndices) { FVector Vertex; InAdapter->GetVertexPosition(Index, Vertex); const FVector WorldPositionVertex = ComponentToWorldMatrix.TransformPosition(Vertex); PDI->DrawPoint(WorldPositionVertex, FLinearColor::Green, VertexPointSize, SDPG_World); } for (const int32& Index : GradientEndIndices) { FVector Vertex; InAdapter->GetVertexPosition(Index, Vertex); const FVector WorldPositionVertex = ComponentToWorldMatrix.TransformPosition(Vertex); PDI->DrawPoint(WorldPositionVertex, FLinearColor::Red, VertexPointSize, SDPG_World); } for (const MeshPaintHelpers::FPaintRay& PaintRay : PaintRays) { const FHitResult& HitResult = SharedPainter->GetHitResult(PaintRay.RayStart, PaintRay.RayDirection); if (HitResult.Component == InComponent) { const FVector ComponentSpaceCameraPosition(ComponentToWorldMatrix.InverseTransformPosition(PaintRay.CameraLocation)); const FVector ComponentSpaceBrushPosition(ComponentToWorldMatrix.InverseTransformPosition(HitResult.Location)); const float ComponentSpaceBrushRadius = ComponentToWorldMatrix.InverseTransformVector(FVector(Settings->bUseRegularBrush ? BrushSettings->GetBrushRadius() : 2.0f, 0.0f, 0.0f)).Size(); const float ComponentSpaceSquaredBrushRadius = ComponentSpaceBrushRadius * ComponentSpaceBrushRadius; // Draw hovered vertex TArray InRangeVertices = InAdapter->SphereIntersectVertices(ComponentSpaceSquaredBrushRadius, ComponentSpaceBrushPosition, ComponentSpaceCameraPosition, BrushSettings->bOnlyFrontFacingTriangles); InRangeVertices.Sort([=](const FVector& PointOne, const FVector& PointTwo) { return (PointOne - ComponentSpaceBrushPosition).SizeSquared() < (PointTwo - ComponentSpaceBrushPosition).SizeSquared(); }); if (InRangeVertices.Num()) { if (!Settings->bUseRegularBrush) { InRangeVertices.SetNum(1); } for (const FVector& Vertex : InRangeVertices) { const FVector WorldPositionVertex = ComponentToWorldMatrix.TransformPosition(Vertex); PDI->DrawPoint(WorldPositionVertex, bSelectingBeginPoints ? FLinearColor::Green : FLinearColor::Red, VertexPointSize, SDPG_Foreground); } } } } } bool FClothPaintTool_Gradient::ShouldRenderInteractors() const { return Settings->bUseRegularBrush; } UObject* FClothPaintTool_Gradient::GetSettingsObject() { if(!Settings) { Settings = DuplicateObject(GetMutableDefault(), GetTransientPackage()); Settings->AddToRoot(); } return Settings; } void FClothPaintTool_Gradient::Activate(TWeakPtr InCommands) { GradientStartIndices.Empty(); GradientEndIndices.Empty(); TSharedPtr SharedCommands = InCommands.Pin(); if(SharedCommands.IsValid()) { const FClothPaintToolCommands_Gradient& Commands = FClothPaintToolCommands_Gradient::Get(); SharedCommands->MapAction(Commands.ApplyGradient, FExecuteAction::CreateRaw(this, &FClothPaintTool_Gradient::ApplyGradient), FCanExecuteAction::CreateRaw(this, &FClothPaintTool_Gradient::CanApplyGradient) ); } } void FClothPaintTool_Gradient::Deactivate(TWeakPtr InCommands) { TSharedPtr SharedCommands = InCommands.Pin(); if(SharedCommands.IsValid()) { const FClothPaintToolCommands_Gradient& Commands = FClothPaintToolCommands_Gradient::Get(); SharedCommands->UnmapAction(Commands.ApplyGradient); } } void FClothPaintTool_Gradient::OnMeshChanged() { GradientStartIndices.Empty(); GradientEndIndices.Empty(); } bool FClothPaintTool_Gradient::HasValueRange() { return true; } void FClothPaintTool_Gradient::GetValueRange(float& OutRangeMin, float& OutRangeMax) { OutRangeMin = FMath::Min(Settings->GradientStartValue, Settings->GradientEndValue); OutRangeMax = FMath::Max(Settings->GradientStartValue, Settings->GradientEndValue); } void FClothPaintTool_Gradient::PaintAction(FPerVertexPaintActionArgs& InArgs, int32 VertexIndex, FMatrix InverseBrushMatrix) { TSharedPtr SharedPainter = Painter.Pin(); if(!SharedPainter.IsValid()) { return; } const FHitResult HitResult = InArgs.HitResult; UClothPainterSettings* PaintSettings = CastChecked(SharedPainter->GetPainterSettings()); const UPaintBrushSettings* BrushSettings = InArgs.BrushSettings; FClothMeshPaintAdapter* Adapter = (FClothMeshPaintAdapter*)InArgs.Adapter; const FMatrix ComponentToWorldMatrix = HitResult.Component->GetComponentTransform().ToMatrixWithScale(); const FVector ComponentSpaceCameraPosition(ComponentToWorldMatrix.InverseTransformPosition(InArgs.CameraPosition)); const FVector ComponentSpaceBrushPosition(ComponentToWorldMatrix.InverseTransformPosition(HitResult.Location)); const float ComponentSpaceBrushRadius = ComponentToWorldMatrix.InverseTransformVector(FVector(Settings->bUseRegularBrush ? BrushSettings->GetBrushRadius() : 2.0f, 0.0f, 0.0f)).Size(); const float ComponentSpaceSquaredBrushRadius = ComponentSpaceBrushRadius * ComponentSpaceBrushRadius; // Override these flags becuase we're not actually "painting" so the state would be incorrect SharedPainter->SetIsPainting(true); TArray InRangeVertices = Adapter->SphereIntersectVertices(ComponentSpaceSquaredBrushRadius, ComponentSpaceBrushPosition, ComponentSpaceCameraPosition, BrushSettings->bOnlyFrontFacingTriangles); TSet InRangeIndicesSet; Adapter->GetInfluencedVertexIndices(ComponentSpaceSquaredBrushRadius, ComponentSpaceBrushPosition, ComponentSpaceCameraPosition, BrushSettings->bOnlyFrontFacingTriangles, InRangeIndicesSet); // Must have an array for sorting/counting TArray InRangeIndices = InRangeIndicesSet.Array(); if (InRangeIndices.Num()) { // Sort based on distance from brush centre InRangeIndices.Sort([=](const int32& Idx0, const int32& Idx1) { FVector PointOne; FVector PointTwo; Adapter->GetVertexPosition(Idx0, PointOne); Adapter->GetVertexPosition(Idx1, PointTwo); return (PointOne - ComponentSpaceBrushPosition).SizeSquared() < (PointTwo - ComponentSpaceBrushPosition).SizeSquared(); }); // Selection mode if (!Settings->bUseRegularBrush) { InRangeIndices.SetNum(1); } TArray& CurrentList = bSelectingBeginPoints ? GradientStartIndices : GradientEndIndices; TArray& OtherList = bSelectingBeginPoints ? GradientEndIndices : GradientStartIndices; // Add selected verts to current list for (const int32& Index : InRangeIndices) { if (InArgs.Action == EMeshPaintAction::Erase && CurrentList.Contains(Index)) { CurrentList.Remove(Index); } else if (InArgs.Action == EMeshPaintAction::Paint) { CurrentList.AddUnique(Index); OtherList.Remove(Index); } } } } void FClothPaintTool_Gradient::ApplyGradient() { TSharedPtr SharedPainter = Painter.Pin(); if(!SharedPainter.IsValid()) { return; } TSharedPtr SharedAdapter = SharedPainter->GetAdapter(); if(!SharedAdapter.IsValid()) { return; } FClothMeshPaintAdapter* Adapter = (FClothMeshPaintAdapter*)SharedAdapter.Get(); { FScopedTransaction Transaction(LOCTEXT("ApplyGradientTransaction", "Apply gradient")); Adapter->PreEdit(); const TArray Verts = Adapter->GetMeshVertices(); const int32 NumVerts = Verts.Num(); for(int32 VertIndex = 0; VertIndex < NumVerts; ++VertIndex) { const FVector& Vert = Verts[VertIndex]; // Get distances // TODO: Look into surface distance instead of 3D distance? May be necessary for some complex shapes float DistanceToStartSq = MAX_flt; for(const int32& StartIndex : GradientStartIndices) { FVector StartPoint; Adapter->GetVertexPosition(StartIndex, StartPoint); const float DistanceSq = (StartPoint - Vert).SizeSquared(); if(DistanceSq < DistanceToStartSq) { DistanceToStartSq = DistanceSq; } } float DistanceToEndSq = MAX_flt; for(const int32& EndIndex : GradientEndIndices) { FVector EndPoint; Adapter->GetVertexPosition(EndIndex, EndPoint); const float DistanceSq = (EndPoint - Vert).SizeSquared(); if(DistanceSq < DistanceToEndSq) { DistanceToEndSq = DistanceSq; } } // Apply to the mesh UClothPainterSettings* PaintSettings = CastChecked(SharedPainter->GetPainterSettings()); const float Value = FMath::LerpStable(Settings->GradientStartValue, Settings->GradientEndValue, DistanceToStartSq / (DistanceToStartSq + DistanceToEndSq)); SharedPainter->SetPropertyValue(VertIndex, Value); } // Finished edit Adapter->PostEdit(); } // Empty the point lists as the operation is complete GradientStartIndices.Reset(); GradientEndIndices.Reset(); // Move back to selecting begin points bSelectingBeginPoints = true; } bool FClothPaintTool_Gradient::CanApplyGradient() { return GradientEndIndices.Num() > 0 && GradientStartIndices.Num() > 0; } class FSmoothToolCustomization : public IDetailCustomization { public: FSmoothToolCustomization() = delete; FSmoothToolCustomization(TSharedPtr InPainter) : Painter(InPainter) {} static TSharedRef MakeInstance(TSharedPtr InPainter) { return MakeShareable(new FSmoothToolCustomization(InPainter)); } virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override { const FName ToolCategoryName = "ToolSettings"; IDetailCategoryBuilder& CategoryBuilder = DetailBuilder.EditCategory(ToolCategoryName); TArray> DefaultProperties; CategoryBuilder.GetDefaultProperties(DefaultProperties); for(TSharedRef& Handle : DefaultProperties) { CategoryBuilder.AddProperty(Handle); } FDetailWidgetRow& MeshSmoothRow = CategoryBuilder.AddCustomRow(LOCTEXT("MeshSmoothRowName", "MeshSmooth")); MeshSmoothRow.ValueContent() [ SNew(SButton) .Text(LOCTEXT("MeshSmoothButtonText", "Smooth Mesh")) .ToolTipText(LOCTEXT("MeshSmoothButtonToolTip", "Applies the smooth operation to the whole mesh at once.")) .OnClicked(this, &FSmoothToolCustomization::OnMeshSmoothClicked) ]; } private: FReply OnMeshSmoothClicked() { if(Painter.IsValid()) { const int32 NumVerts = Painter->GetAdapter()->GetMeshVertices().Num(); TSet IndexSet; IndexSet.Reserve(NumVerts); for(int32 Index = 0; Index < NumVerts; ++Index) { IndexSet.Add(Index); } TSharedPtr SmoothTool = StaticCastSharedPtr(Painter->GetSelectedTool()); if(SmoothTool.IsValid()) { SmoothTool->SmoothVertices(IndexSet, Painter); } } return FReply::Handled(); } TSharedPtr Painter; }; FClothPaintTool_Smooth::~FClothPaintTool_Smooth() { if(Settings) { Settings->RemoveFromRoot(); } } FPerVertexPaintAction FClothPaintTool_Smooth::GetPaintAction(const FMeshPaintParameters& InPaintParams, UClothPainterSettings* InPainterSettings) { return FPerVertexPaintAction::CreateRaw(this, &FClothPaintTool_Smooth::PaintAction, InPaintParams.InverseBrushToWorldMatrix); } FText FClothPaintTool_Smooth::GetDisplayName() const { return LOCTEXT("ToolName_Smooth", "Smooth"); } UObject* FClothPaintTool_Smooth::GetSettingsObject() { if(!Settings) { Settings = DuplicateObject(GetMutableDefault(), GetTransientPackage()); Settings->AddToRoot(); } return Settings; } bool FClothPaintTool_Smooth::IsPerVertex() const { return false; } void FClothPaintTool_Smooth::RegisterSettingsObjectCustomizations(IDetailsView* InDetailsView) { InDetailsView->RegisterInstancedCustomPropertyLayout(UClothPaintTool_SmoothSettings::StaticClass(), FOnGetDetailCustomizationInstance::CreateStatic(&FSmoothToolCustomization::MakeInstance, Painter.Pin())); } void FClothPaintTool_Smooth::PaintAction(FPerVertexPaintActionArgs& InArgs, int32 VertexIndex, FMatrix InverseBrushMatrix) { TSharedPtr SharedPainter = Painter.Pin(); if(!SharedPainter.IsValid()) { return; } const FHitResult HitResult = InArgs.HitResult; UClothPainterSettings* PaintSettings = CastChecked(SharedPainter->GetPainterSettings()); const UPaintBrushSettings* BrushSettings = InArgs.BrushSettings; FClothMeshPaintAdapter* Adapter = (FClothMeshPaintAdapter*)InArgs.Adapter; const FMatrix ComponentToWorldMatrix = HitResult.Component->GetComponentTransform().ToMatrixWithScale(); const FVector ComponentSpaceCameraPosition(ComponentToWorldMatrix.InverseTransformPosition(InArgs.CameraPosition)); const FVector ComponentSpaceBrushPosition(ComponentToWorldMatrix.InverseTransformPosition(HitResult.Location)); const float ComponentSpaceBrushRadius = ComponentToWorldMatrix.InverseTransformVector(FVector(BrushSettings->GetBrushRadius(), 0.0f, 0.0f)).Size(); const float ComponentSpaceSquaredBrushRadius = ComponentSpaceBrushRadius * ComponentSpaceBrushRadius; // Override these flags becuase we're not actually "painting" so the state would be incorrect SharedPainter->SetIsPainting(true); TSet InfluencedVertices; Adapter->GetInfluencedVertexIndices(ComponentSpaceSquaredBrushRadius, ComponentSpaceBrushPosition, ComponentSpaceCameraPosition, BrushSettings->bOnlyFrontFacingTriangles, InfluencedVertices); SmoothVertices(InfluencedVertices, SharedPainter); } void FClothPaintTool_Smooth::SmoothVertices(const TSet &InfluencedVertices, TSharedPtr SharedPainter) { check(SharedPainter.IsValid()); TSharedPtr AdapterInterface = SharedPainter->GetAdapter(); FClothMeshPaintAdapter* Adapter = (FClothMeshPaintAdapter*)AdapterInterface.Get(); const int32 NumVerts = InfluencedVertices.Num(); if(NumVerts > 0) { TArray NewValues; NewValues.AddZeroed(NumVerts); int32 InfluencedIndex = 0; for(const int32 Index : InfluencedVertices) { const TArray* Neighbors = Adapter->GetVertexNeighbors(Index); if(Neighbors && Neighbors->Num() > 0) { float Accumulator = 0.0f; for(int32 NeighborIndex : (*Neighbors)) { Accumulator += SharedPainter->GetPropertyValue(NeighborIndex); } NewValues[InfluencedIndex] = Accumulator / Neighbors->Num(); } else { NewValues[InfluencedIndex] = SharedPainter->GetPropertyValue(Index); } ++InfluencedIndex; } int32 ValueIndex = 0; for(int32 Index : InfluencedVertices) { float Current = SharedPainter->GetPropertyValue(Index); float Difference = NewValues[ValueIndex] - Current; SharedPainter->SetPropertyValue(Index, Current + Difference * Settings->Strength); ++ValueIndex; } } } FClothPaintTool_Fill::~FClothPaintTool_Fill() { if(Settings) { Settings->RemoveFromRoot(); } } FPerVertexPaintAction FClothPaintTool_Fill::GetPaintAction(const FMeshPaintParameters& InPaintParams, UClothPainterSettings* InPainterSettings) { return FPerVertexPaintAction::CreateRaw(this, &FClothPaintTool_Fill::PaintAction, InPaintParams.InverseBrushToWorldMatrix); } FText FClothPaintTool_Fill::GetDisplayName() const { return LOCTEXT("ToolName_Fill", "Fill"); } UObject* FClothPaintTool_Fill::GetSettingsObject() { if(!Settings) { Settings = DuplicateObject(GetMutableDefault(), GetTransientPackage()); Settings->AddToRoot(); } return Settings; } bool FClothPaintTool_Fill::IsPerVertex() const { return false; } bool FClothPaintTool_Fill::ShouldRenderInteractors() const { return false; } void FClothPaintTool_Fill::Render(USkeletalMeshComponent* InComponent, IMeshPaintGeometryAdapter* InAdapter, const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) { TSharedPtr SharedPainter = Painter.Pin(); if(!SharedPainter.IsValid()) { return; } const float VertexPointSize = GetDefault()->VertexPreviewSize; const UClothPainterSettings* PaintSettings = CastChecked(SharedPainter->GetPainterSettings()); const UPaintBrushSettings* BrushSettings = SharedPainter->GetBrushSettings(); TArray PaintRays; MeshPaintHelpers::RetrieveViewportPaintRays(View, Viewport, PDI, PaintRays); const FMatrix ComponentToWorldMatrix = InComponent->GetComponentTransform().ToMatrixWithScale(); for(const MeshPaintHelpers::FPaintRay& PaintRay : PaintRays) { const FHitResult& HitResult = SharedPainter->GetHitResult(PaintRay.RayStart, PaintRay.RayDirection); if(HitResult.Component == InComponent) { const FVector ComponentSpaceCameraPosition(ComponentToWorldMatrix.InverseTransformPosition(PaintRay.CameraLocation)); const FVector ComponentSpaceBrushPosition(ComponentToWorldMatrix.InverseTransformPosition(HitResult.Location)); const float ComponentSpaceBrushRadius = ComponentToWorldMatrix.InverseTransformVector(FVector(QueryRadius, 0.0f, 0.0f)).Size(); const float ComponentSpaceSquaredBrushRadius = ComponentSpaceBrushRadius * ComponentSpaceBrushRadius; // Draw hovered vertex TArray InRangeVertices = InAdapter->SphereIntersectVertices(ComponentSpaceSquaredBrushRadius, ComponentSpaceBrushPosition, ComponentSpaceCameraPosition, BrushSettings->bOnlyFrontFacingTriangles); InRangeVertices.Sort([=](const FVector& PointOne, const FVector& PointTwo) { return (PointOne - ComponentSpaceBrushPosition).SizeSquared() < (PointTwo - ComponentSpaceBrushPosition).SizeSquared(); }); if(InRangeVertices.Num()) { InRangeVertices.SetNum(1); // reduce this for(const FVector& Vertex : InRangeVertices) { const FVector WorldPositionVertex = ComponentToWorldMatrix.TransformPosition(Vertex); PDI->DrawPoint(WorldPositionVertex, FLinearColor::Green, VertexPointSize, SDPG_Foreground); } } } } } bool FClothPaintTool_Fill::HasValueRange() { return true; } void FClothPaintTool_Fill::GetValueRange(float& OutRangeMin, float& OutRangeMax) { OutRangeMin = Settings->FillValue; OutRangeMax = OutRangeMin; } void FClothPaintTool_Fill::PaintAction(FPerVertexPaintActionArgs& InArgs, int32 VertexIndex, FMatrix InverseBrushMatrix) { TSharedPtr SharedPainter = Painter.Pin(); if(!SharedPainter.IsValid()) { return; } const FHitResult HitResult = InArgs.HitResult; UClothPainterSettings* PaintSettings = CastChecked(SharedPainter->GetPainterSettings()); const UPaintBrushSettings* BrushSettings = InArgs.BrushSettings; FClothMeshPaintAdapter* Adapter = (FClothMeshPaintAdapter*)InArgs.Adapter; const FMatrix ComponentToWorldMatrix = HitResult.Component->GetComponentTransform().ToMatrixWithScale(); const FVector ComponentSpaceCameraPosition(ComponentToWorldMatrix.InverseTransformPosition(InArgs.CameraPosition)); const FVector ComponentSpaceBrushPosition(ComponentToWorldMatrix.InverseTransformPosition(HitResult.Location)); const float ComponentSpaceBrushRadius = ComponentToWorldMatrix.InverseTransformVector(FVector(QueryRadius, 0.0f, 0.0f)).Size(); const float ComponentSpaceSquaredBrushRadius = ComponentSpaceBrushRadius * ComponentSpaceBrushRadius; // Override these flags becuase we're not actually "painting" so the state would be incorrect SharedPainter->SetIsPainting(true); TArray> Verts; Adapter->GetInfluencedVertexData(ComponentSpaceSquaredBrushRadius, ComponentSpaceBrushPosition, ComponentSpaceCameraPosition, BrushSettings->bOnlyFrontFacingTriangles, Verts); if(Verts.Num() > 0) { // Sort based on distance from brush centre Verts.Sort([=](const TPair& Vert0, const TPair& Vert1) { return (Vert0.Value - ComponentSpaceBrushPosition).SizeSquared() < (Vert1.Value - ComponentSpaceBrushPosition).SizeSquared(); }); // Fill operates on one vertex only, the closest int32 ChosenIndex = Verts[0].Key; // An expansion queue to handle the fill operation TQueue VertQueue; // Query values to account for the threshold around the selected value const float QueryValue = SharedPainter->GetPropertyValue(ChosenIndex); const float MinQueryValue = QueryValue - Settings->Threshold; const float MaxQueryValue = QueryValue + Settings->Threshold; // Set the selected vert to the new vaue and add it to the expansion queue SharedPainter->SetPropertyValue(ChosenIndex, Settings->FillValue); VertQueue.Enqueue(ChosenIndex); int32 CurrIndex = 0; while(!VertQueue.IsEmpty()) { // Remove the current vert from the queue VertQueue.Dequeue(CurrIndex); // Grab the neighbors of the current vertex const TArray* Neighbors = Adapter->GetVertexNeighbors(CurrIndex); if(Neighbors && Neighbors->Num() > 0) { for(int32 NeighborIndex : (*Neighbors)) { // For each neighbor, get its current value and if it's not the correct final // value, set it and then add to the queue for expansion on the next loop const float NeighborValue = SharedPainter->GetPropertyValue(NeighborIndex); if(NeighborValue != Settings->FillValue && NeighborValue >= MinQueryValue && NeighborValue <= MaxQueryValue) { SharedPainter->SetPropertyValue(NeighborIndex, Settings->FillValue); VertQueue.Enqueue(NeighborIndex); } } } } } } #undef LOCTEXT_NAMESPACE