// Copyright Epic Games, Inc. All Rights Reserved. #include "MeshAttributePaintTool.h" #include "InteractiveToolManager.h" #include "ToolBuilderUtil.h" #include "Drawing/MeshDebugDrawing.h" #include "ToolSetupUtil.h" #include "Selection/StoredMeshSelectionUtil.h" // GetCurrentGeometrySelectionForTarget #include "Selections/GeometrySelectionUtil.h" #include "Selections/MeshConnectedComponents.h" #include "MeshDescription.h" #include "TargetInterfaces/DynamicMeshProvider.h" #include "TargetInterfaces/DynamicMeshCommitter.h" #include "TargetInterfaces/MeshDescriptionProvider.h" #include "TargetInterfaces/MeshDescriptionCommitter.h" #include "TargetInterfaces/SceneComponentBackedTarget.h" #include "ModelingToolTargetUtil.h" #include "DynamicMesh/DynamicMeshAttributeSet.h" #include "DynamicMesh/NonManifoldMappingSupport.h" #include "ToolTargetManager.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(MeshAttributePaintTool) using namespace UE::Geometry; #define LOCTEXT_NAMESPACE "UMeshAttributePaintTool" class FMeshDescriptionVertexAttributeAdapter : public IMeshVertexAttributeAdapter { public: FMeshDescription* Mesh; FName AttributeName; TVertexAttributesRef Attribute; FMeshDescriptionVertexAttributeAdapter(FMeshDescription* MeshIn, FName AttribNameIn, TVertexAttributesRef AttribIn) : Mesh(MeshIn), AttributeName(AttribNameIn), Attribute(AttribIn) { } virtual int32 ElementNum() const override { return Attribute.GetNumElements(); } virtual float GetValue(int32 Index) const override { return Attribute.Get(FVertexID(Index)); } virtual void SetValue(int32 Index, float Value) override { Attribute.Set(FVertexID(Index), Value); } virtual FInterval1f GetValueRange() override { return FInterval1f(0.0f, 1.0f); } }; class FDynamicMeshVertexAttributeAdapter : public IMeshVertexAttributeAdapter { public: FDynamicMesh3* Mesh; FDynamicMeshWeightAttribute* WeightAttribute; FDynamicMeshVertexAttributeAdapter(FDynamicMesh3* InMesh, FDynamicMeshWeightAttribute* InWeightAttribute) : Mesh(InMesh), WeightAttribute(InWeightAttribute) { } virtual int32 ElementNum() const override { return Mesh->MaxVertexID(); } virtual float GetValue(int32 Index) const override { float Wt; WeightAttribute->GetValue(Index, &Wt); return Wt; } virtual void SetValue(int32 Index, float Value) override { WeightAttribute->SetScalarValue(Index, Value); } virtual FInterval1f GetValueRange() override { return FInterval1f(0.0f, 1.0f); } }; class FDynamicMeshVertexAttributeSource : public IMeshVertexAttributeSource { public: FDynamicMesh3* Mesh = nullptr; FDynamicMeshVertexAttributeSource(FDynamicMesh3* MeshIn) { Mesh = MeshIn; } virtual int32 GetAttributeElementNum() override { return Mesh->MaxVertexID(); } virtual TArray GetAttributeList() override { TArray Result; if (FDynamicMeshAttributeSet* Attributes = Mesh->Attributes()) { const int32 NumLayers = Attributes->NumWeightLayers(); Result.Reserve(NumLayers); for (int32 LayerIdx = 0; LayerIdx < NumLayers; ++LayerIdx) { Result.Add(Attributes->GetWeightLayer(LayerIdx)->GetName()); } } return Result; } virtual TUniquePtr GetAttribute(FName AttributeName) override { if (FDynamicMeshAttributeSet* Attributes = Mesh->Attributes()) { const int32 NumLayers = Attributes->NumWeightLayers(); for (int32 LayerIdx = 0; LayerIdx < NumLayers; ++LayerIdx) { FDynamicMeshWeightAttribute* WeightLayer = Attributes->GetWeightLayer(LayerIdx); if (WeightLayer->GetName() == AttributeName) { return MakeUnique(Mesh, WeightLayer); } } } return nullptr; } }; class FMeshDescriptionVertexAttributeSource : public IMeshVertexAttributeSource { public: FMeshDescription* Mesh = nullptr; FMeshDescriptionVertexAttributeSource(FMeshDescription* MeshIn) { Mesh = MeshIn; } virtual int32 GetAttributeElementNum() override { return Mesh->Vertices().Num(); } virtual TArray GetAttributeList() override { TAttributesSet& VertexAttribs = Mesh->VertexAttributes(); TArray Result; VertexAttribs.ForEach([&](const FName AttributeName, auto AttributesRef) { if (VertexAttribs.HasAttributeOfType(AttributeName)) { Result.Add(AttributeName); } }); return Result; } virtual TUniquePtr GetAttribute(FName AttributeName) override { TAttributesSet& VertexAttribs = Mesh->VertexAttributes(); TVertexAttributesRef Attrib = VertexAttribs.GetAttributesRef(AttributeName); if (Attrib.IsValid()) { return MakeUnique(Mesh, AttributeName, Attrib); } return nullptr; } }; void UMeshAttributePaintToolProperties::Initialize(const TArray& AttributeNames, bool bInitialize) { Attributes.Reset(AttributeNames.Num()); for (const FName& AttributeName : AttributeNames) { Attributes.Add(AttributeName.ToString()); } if (bInitialize) { Attribute = (Attributes.Num() > 0) ? Attributes[0] : TEXT(""); } } bool UMeshAttributePaintToolProperties::ValidateSelectedAttribute(bool bUpdateIfInvalid) { int32 FoundIndex = Attributes.IndexOfByKey(Attribute); if (FoundIndex == INDEX_NONE) { if (bUpdateIfInvalid) { Attribute = (Attributes.Num() > 0) ? Attributes[0] : TEXT(""); } return false; } return true; } int32 UMeshAttributePaintToolProperties::GetSelectedAttributeIndex() { ensure(INDEX_NONE == -1); int32 FoundIndex = Attributes.IndexOfByKey(Attribute); return FoundIndex; } void UMeshAttributePaintEditActions::PostAction(EMeshAttributePaintToolActions Action) { if (ParentTool.IsValid() && Cast(ParentTool)) { Cast(ParentTool)->RequestAction(Action); } } /* * ToolBuilder */ UMeshSurfacePointTool* UMeshAttributePaintToolBuilder::CreateNewTool(const FToolBuilderState& SceneState) const { UMeshAttributePaintTool* SelectionTool = NewObject(SceneState.ToolManager); SelectionTool->SetWorld(SceneState.World); if (ColorMapFactory) { SelectionTool->SetColorMap(ColorMapFactory()); } return SelectionTool; } // TODO: May want a base class for brush tools with selection? void UMeshAttributePaintToolBuilder::InitializeNewTool(UMeshSurfacePointTool* NewTool, const FToolBuilderState& SceneState) const { Super::InitializeNewTool(NewTool, SceneState); UMeshAttributePaintTool* Tool = Cast< UMeshAttributePaintTool>(NewTool); if (ensure(Tool && Tool->GetTarget())) { UE::Geometry::FGeometrySelection Selection; bool bHaveSelection = UE::Geometry::GetCurrentGeometrySelectionForTarget(SceneState, Tool->GetTarget(), Selection); if (bHaveSelection) { Tool->SetGeometrySelection(MoveTemp(Selection)); } } } bool UMeshAttributePaintToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const { return UMeshSurfacePointMeshEditingToolBuilder::CanBuildTool(SceneState) && SceneState.TargetManager->CountSelectedAndTargetableWithPredicate(SceneState, GetTargetRequirements(), [](UActorComponent& Component) { return !ToolBuilderUtil::IsVolume(Component); }) >= 1; } const FToolTargetTypeRequirements& UMeshAttributePaintToolBuilder::GetTargetRequirements() const { static FToolTargetTypeRequirements TypeRequirements({ UMaterialProvider::StaticClass(), UDynamicMeshProvider::StaticClass(), UDynamicMeshCommitter::StaticClass(), USceneComponentBackedTarget::StaticClass() }); return TypeRequirements; } void UMeshAttributePaintTool::SetWorld(UWorld* World) { this->TargetWorld = World; } void UMeshAttributePaintTool::SetGeometrySelection(const UE::Geometry::FGeometrySelection& SelectionIn) { GeometrySelection = SelectionIn; } void UMeshAttributePaintTool::Setup() { // want this before brush size/etc BrushActionProps = NewObject(this); BrushActionProps->RestoreProperties(this); AddToolPropertySource(BrushActionProps); UDynamicMeshBrushTool::Setup(); // hide strength and falloff BrushProperties->RestoreProperties(this); AttribProps = NewObject(this); AttribProps->RestoreProperties(this); AddToolPropertySource(AttribProps); //AttributeEditActions = NewObject(this); //AttributeEditActions->Initialize(this); //AddToolPropertySource(AttributeEditActions); // configure preview mesh PreviewMesh->SetTangentsMode(EDynamicMeshComponentTangentsMode::AutoCalculated); //PreviewMesh->EnableWireframe(SelectionProps->bShowWireframe); PreviewMesh->SetShadowsEnabled(false); BrushActionProps->bToolHasSelection = GeometrySelection.IsSet(); SelectionTids.Reset(); SelectionVids.Reset(); PreviewMesh->EditMesh([this](FDynamicMesh3& Mesh) { // enable vtx colors on preview mesh Mesh.EnableAttributes(); Mesh.Attributes()->DisablePrimaryColors(); Mesh.Attributes()->EnablePrimaryColors(); // Create an overlay that has no split elements, init with zero value. Mesh.Attributes()->PrimaryColors()->CreateFromPredicate([](int ParentVID, int TriIDA, int TriIDB){return true;}, 0.f); if (GeometrySelection.IsSet()) { UE::Geometry::EnumerateSelectionTriangles(GeometrySelection.GetValue(), *PreviewMesh->GetMesh(), [this, &Mesh](int32 TriangleID) { SelectionTids.Add(TriangleID); FIndex3i Triangle = Mesh.GetTriangle(TriangleID); for (int i = 0; i < 3; ++i) { SelectionVids.Add(Triangle[i]); } }); PreviewMesh->EnableSecondaryTriangleBuffers([this](const UE::Geometry::FDynamicMesh3*, int32 Tid) { return !SelectionTids.Contains(Tid); }); PreviewMesh->SetSecondaryBuffersVisibility(!BrushActionProps->bIsolateGeometrySelection); } }); // build octree VerticesOctree.Initialize(PreviewMesh->GetMesh(), true); UMaterialInterface* VtxColorMaterial = ToolSetupUtil::GetVertexColorMaterial(GetToolManager()); if (VtxColorMaterial != nullptr) { PreviewMesh->SetOverrideRenderMaterial(VtxColorMaterial); } RecalculateBrushRadius(); SetToolDisplayName(LOCTEXT("ToolName", "Paint WeightMaps")); GetToolManager()->DisplayMessage( LOCTEXT("OnStartAttribPaint", "Paint per-vertex attribute maps. Ctrl to Erase/Subtract, Shift to Smooth. [/] to change Brush Size."), EToolMessageLevel::UserNotification); ColorMapper = MakeUnique(); if (Cast(Target) && Cast(Target)) { EditedMesh = MakeUnique(); *EditedMesh = *UE::ToolTarget::GetMeshDescription(Target); AttributeSource = MakeUnique(EditedMesh.Get()); } else { EditedDynamicMesh = MakeUnique(); FGetMeshParameters Params; Params.bWantMeshTangents = true; *EditedDynamicMesh = UE::ToolTarget::GetDynamicMeshCopy(Target, Params); AttributeSource = MakeUnique< FDynamicMeshVertexAttributeSource>(EditedDynamicMesh.Get()); } AttribProps->Initialize(AttributeSource->GetAttributeList(), true); if (AttribProps->Attributes.Num() == 0) { GetToolManager()->DisplayMessage( LOCTEXT("StartAttribPaintFailed", "No Float attributes exist for this mesh. Use the Attribute Editor to create one."), EToolMessageLevel::UserWarning); } InitializeAttributes(); if (AttribProps->Attributes.Num() > 0) { PendingNewSelectedIndex = 0; } SelectedAttributeWatcher.Initialize([this]() { AttribProps->ValidateSelectedAttribute(true); return AttribProps->GetSelectedAttributeIndex(); }, [this](int32 NewValue) { PendingNewSelectedIndex = NewValue; }, AttribProps->GetSelectedAttributeIndex()); bVisibleAttributeValid = false; BrushActionProps->WatchProperty(BrushActionProps->bIsolateGeometrySelection, [this](bool) { PreviewMesh->SetSecondaryBuffersVisibility(!BrushActionProps->bIsolateGeometrySelection); }); } void UMeshAttributePaintTool::RegisterActions(FInteractiveToolActionSet& ActionSet) { UDynamicMeshBrushTool::RegisterActions(ActionSet); } void UMeshAttributePaintTool::RequestAction(EMeshAttributePaintToolActions ActionType) { if (bHavePendingAction) { return; } PendingAction = ActionType; bHavePendingAction = true; } void UMeshAttributePaintTool::SetColorMap(TUniquePtr ColorMap) { ColorMapper = MoveTemp(ColorMap); } void UMeshAttributePaintTool::OnTick(float DeltaTime) { SelectedAttributeWatcher.CheckAndUpdate(); if (bStampPending) { ApplyStamp(LastStamp); bStampPending = false; } if (bHavePendingAction) { ApplyAction(PendingAction); bHavePendingAction = false; PendingAction = EMeshAttributePaintToolActions::NoAction; } if (PendingNewSelectedIndex >= 0) { UpdateSelectedAttribute(PendingNewSelectedIndex); PendingNewSelectedIndex = -1; } if (bVisibleAttributeValid == false) { UpdateVisibleAttribute(); bVisibleAttributeValid = true; } } void UMeshAttributePaintTool::OnBeginDrag(const FRay& WorldRay) { UDynamicMeshBrushTool::OnBeginDrag(WorldRay); PreviewBrushROI.Reset(); bInRemoveStroke = GetCtrlToggle(); bInSmoothStroke = GetShiftToggle(); BeginChange(); StartStamp = UBaseBrushTool::LastBrushStamp; LastStamp = StartStamp; bStampPending = true; } void UMeshAttributePaintTool::OnUpdateDrag(const FRay& WorldRay) { UDynamicMeshBrushTool::OnUpdateDrag(WorldRay); LastStamp = UBaseBrushTool::LastBrushStamp; bStampPending = true; } void UMeshAttributePaintTool::OnEndDrag(const FRay& Ray) { UDynamicMeshBrushTool::OnEndDrag(Ray); bInRemoveStroke = bInSmoothStroke = false; bStampPending = false; // close change record TUniquePtr Change = EndChange(); if (Change) { GetToolManager()->EmitObjectChange(this, MoveTemp(Change), LOCTEXT("AttributeValuesChange", "Paint")); LongTransactions.Close(GetToolManager()); } } bool UMeshAttributePaintTool::OnUpdateHover(const FInputDeviceRay& DevicePos) { UDynamicMeshBrushTool::OnUpdateHover(DevicePos); // todo get rid of this redundant hit test! FHitResult OutHit; if (UDynamicMeshBrushTool::HitTest(DevicePos.WorldRay, OutHit)) { PreviewBrushROI.Reset(); CalculateVertexROI(LastBrushStamp, PreviewBrushROI); } return true; } bool UMeshAttributePaintTool::HitTest(const FRay& Ray, FHitResult& OutHit) { TFunction Filter = nullptr; if (ShouldFilterTriangles()) { Filter = [this](int32 Tid) { return SelectionTids.Contains(Tid); }; } return PreviewMesh->FindRayIntersection(FRay3d(Ray), OutHit, Filter); } void UMeshAttributePaintTool::CalculateVertexROI(const FBrushStampData& Stamp, TArray& VertexROI) { FTransform3d Transform(Cast(Target)->GetWorldTransform()); FVector3d StampPosLocal = Transform.InverseTransformPosition((FVector3d)Stamp.WorldPosition); float Radius = GetCurrentBrushRadiusLocal(); float RadiusSqr = Radius * Radius; const FDynamicMesh3* Mesh = PreviewMesh->GetPreviewDynamicMesh(); FAxisAlignedBox3d QueryBox(StampPosLocal, Radius); if (ShouldFilterTriangles() && ensure(!SelectionVids.IsEmpty())) { VerticesOctree.RangeQuery(QueryBox, [this, Mesh, &StampPosLocal, RadiusSqr](int32 VertexID) { return SelectionVids.Contains(VertexID) && DistanceSquared(Mesh->GetVertex(VertexID), StampPosLocal) < RadiusSqr; }, VertexROI); } else { VerticesOctree.RangeQuery(QueryBox, [Mesh, &StampPosLocal, RadiusSqr](int32 VertexID) { return DistanceSquared(Mesh->GetVertex(VertexID), StampPosLocal) < RadiusSqr; }, VertexROI); } } void UMeshAttributePaintTool::InitializeAttributes() { AttributeBufferCount = AttributeSource->GetAttributeElementNum(); TArray AttributeNames = AttributeSource->GetAttributeList(); Attributes.SetNum(AttributeNames.Num()); for (int32 k = 0; k < AttributeNames.Num(); ++k) { Attributes[k].Name = AttributeNames[k]; Attributes[k].Attribute = AttributeSource->GetAttribute(AttributeNames[k]); Attributes[k].CurrentValues.SetNum(AttributeBufferCount); for (int32 i = 0; i < AttributeBufferCount; ++i) { Attributes[k].CurrentValues[i] = Attributes[k].Attribute->GetValue(i); } Attributes[k].InitialValues = Attributes[k].CurrentValues; } CurrentAttributeIndex = -1; PendingNewSelectedIndex = -1; } void UMeshAttributePaintTool::StoreCurrentAttribute() { if (CurrentAttributeIndex >= 0) { FAttributeData& AttribData = Attributes[CurrentAttributeIndex]; for (int32 k = 0; k < AttributeBufferCount; ++k) { AttribData.Attribute->SetValue(k, AttribData.CurrentValues[k]); } CurrentAttributeIndex = -1; CurrentValueRange = FInterval1f(0.0f, 1.0f); } } void UMeshAttributePaintTool::UpdateVisibleAttribute() { // copy current value set back to attribute (should we just always be doing this??) StoreCurrentAttribute(); CurrentAttributeIndex = AttribProps->GetSelectedAttributeIndex(); if (CurrentAttributeIndex >= 0) { FAttributeData& AttribData = Attributes[CurrentAttributeIndex]; CurrentValueRange = AttribData.Attribute->GetValueRange(); // update mesh with new value colors PreviewMesh->EditMesh([&](FDynamicMesh3& Mesh) { FDynamicMeshColorOverlay* ColorOverlay = Mesh.Attributes()->PrimaryColors(); FNonManifoldMappingSupport NonManifoldMappingSupport(Mesh); for (int32 elid : ColorOverlay->ElementIndicesItr()) { const int32 vid = ColorOverlay->GetParentVertex(elid); const int32 srcvid = NonManifoldMappingSupport.GetOriginalNonManifoldVertexID(vid); const float Value = AttribData.CurrentValues[srcvid]; const FVector4f Color4f = ToVector4(ColorMapper->ToColor(Value)); ColorOverlay->SetElement(elid, Color4f); } }); AttribProps->Attribute = AttribData.Name.ToString(); } } double UMeshAttributePaintTool::CalculateBrushFalloff(double Distance) { double f = FMathd::Clamp(1.0 - BrushProperties->BrushFalloffAmount, 0.0, 1.0); double d = Distance / GetCurrentBrushRadiusLocal(); double w = 1; if (d > f) { d = FMathd::Clamp((d - f) / (1.0 - f), 0.0, 1.0); w = (1.0 - d * d); w = w * w * w; } return w; } void UMeshAttributePaintTool::ApplyStamp(const FBrushStampData& Stamp) { if (CurrentAttributeIndex < 0) { return; } FAttributeData& AttribData = Attributes[CurrentAttributeIndex]; FStampActionData ActionData; CalculateVertexROI(Stamp, ActionData.ROIVertices); if (BrushActionProps->BrushAction == EBrushActionMode::FloodFill) { const bool bEmptyROIVertices = (ActionData.ROIVertices.Num() == 0); if (bEmptyROIVertices) // append a vertex from the hit triangle to start the flood fill. { const int32 TID = Stamp.HitResult.FaceIndex; const FDynamicMesh3* Mesh = PreviewMesh->GetPreviewDynamicMesh(); if (Mesh->IsTriangle(TID)) { const FIndex3i VIDs = Mesh->GetTriangle(TID); ActionData.ROIVertices.Add(VIDs[0]); } } ApplyStamp_FloodFill(Stamp, ActionData); } else { ApplyStamp_Paint(Stamp, ActionData); } // track changes if (ActiveChangeBuilder) { ActiveChangeBuilder->UpdateValues(ActionData.ROIVertices, ActionData.ROIBefore, ActionData.ROIAfter); } // update values and colors PreviewMesh->DeferredEditMesh([&](FDynamicMesh3& Mesh) { TArray ElIDs; FDynamicMeshColorOverlay* ColorOverlay = Mesh.Attributes()->PrimaryColors(); FNonManifoldMappingSupport NonManifoldMappingSupport(Mesh); int32 NumVertices = ActionData.ROIVertices.Num(); for (int32 k = 0; k < NumVertices; ++k) { int32 vid = ActionData.ROIVertices[k]; int32 srcvid = NonManifoldMappingSupport.GetOriginalNonManifoldVertexID(vid); AttribData.CurrentValues[srcvid] = ActionData.ROIAfter[k]; FVector4f NewColor( ToVector4(ColorMapper->ToColor(ActionData.ROIAfter[k])) ); ColorOverlay->GetVertexElements(vid, ElIDs); for (int elid : ElIDs) { ColorOverlay->SetElement(elid, NewColor); } ElIDs.Reset(); } }, false); PreviewMesh->NotifyDeferredEditCompleted(UPreviewMesh::ERenderUpdateMode::FastUpdate, EMeshRenderAttributeFlags::VertexColors, false); } void UMeshAttributePaintTool::ApplyStamp_Paint(const FBrushStampData& Stamp, FStampActionData& ActionData) { FTransform3d Transform(Cast(Target)->GetWorldTransform()); FVector3d StampPosLocal = Transform.InverseTransformPosition((FVector3d)Stamp.WorldPosition); int32 NumVertices = ActionData.ROIVertices.Num(); ActionData.ROIBefore.SetNum(NumVertices); ActionData.ROIAfter.SetNum(NumVertices); FAttributeData& AttribData = Attributes[CurrentAttributeIndex]; const FDynamicMesh3* CurrentMesh = PreviewMesh->GetMesh(); FNonManifoldMappingSupport NonManifoldMappingSupport(*CurrentMesh); if (bInSmoothStroke) { float SmoothSpeed = 0.25f; for (int32 k = 0; k < NumVertices; ++k) { int32 vid = ActionData.ROIVertices[k]; FVector3d Position = CurrentMesh->GetVertex(vid); float ValueSum = 0, WeightSum = 0; for (int32 NbrVID : CurrentMesh->VtxVerticesItr(vid)) { int32 srcNbrVID = NonManifoldMappingSupport.GetOriginalNonManifoldVertexID(NbrVID); FVector3d NbrPos = CurrentMesh->GetVertex(NbrVID); float Weight = FMathf::Clamp(1.0f / DistanceSquared(NbrPos, Position), 0.0001f, 1000.0f); ValueSum += Weight * AttribData.CurrentValues[srcNbrVID]; WeightSum += Weight; } ValueSum /= WeightSum; float Falloff = (float)CalculateBrushFalloff(Distance(Position, StampPosLocal)); const int32 srcvid = NonManifoldMappingSupport.GetOriginalNonManifoldVertexID(vid); float NewValue = FMathf::Lerp(AttribData.CurrentValues[srcvid], ValueSum, SmoothSpeed*Falloff); ActionData.ROIBefore[k] = AttribData.CurrentValues[srcvid]; ActionData.ROIAfter[k] = CurrentValueRange.Clamp(NewValue); } } else { bool bInvert = bInRemoveStroke; float Sign = (bInvert) ? -1.0f : 1.0f; float UseStrength = Sign * BrushProperties->BrushStrength * CurrentValueRange.Length(); for (int32 k = 0; k < NumVertices; ++k) { int32 vid = ActionData.ROIVertices[k]; int32 srcvid = NonManifoldMappingSupport.GetOriginalNonManifoldVertexID(vid); FVector3d Position = CurrentMesh->GetVertex(vid); float Falloff = (float)CalculateBrushFalloff(Distance(Position, StampPosLocal)); ActionData.ROIBefore[k] = AttribData.CurrentValues[srcvid]; ActionData.ROIAfter[k] = CurrentValueRange.Clamp(ActionData.ROIBefore[k] + UseStrength*Falloff); } } } void UMeshAttributePaintTool::ApplyStamp_FloodFill(const FBrushStampData& Stamp, FStampActionData& ActionData) { FAttributeData& AttribData = Attributes[CurrentAttributeIndex]; const FDynamicMesh3* CurrentMesh = PreviewMesh->GetMesh(); // convert to connected triangle set TSet RemainingTriangles; for (int32 vid : ActionData.ROIVertices) { if (ShouldFilterTriangles()) { CurrentMesh->EnumerateVertexTriangles(vid, [&](int32 tid) { if (SelectionTids.Contains(tid)) { RemainingTriangles.Add(tid); } }); } else { CurrentMesh->EnumerateVertexTriangles(vid, [&](int32 tid) { RemainingTriangles.Add(tid); }); } } float SetValue = BrushProperties->BrushStrength * CurrentValueRange.Length(); if (bInRemoveStroke) { SetValue = CurrentValueRange.Min; } FNonManifoldMappingSupport NonManifoldMappingSupport(*CurrentMesh); ActionData.ROIVertices.Reset(); TArray InputTriROI, OutputTriROI, QueueTempBuffer; TSet DoneTempBuffer, DoneVertices; while (RemainingTriangles.Num() > 0) { OutputTriROI.Reset(); QueueTempBuffer.Reset(); DoneTempBuffer.Reset(); InputTriROI.Reset(); // get a single set element via an iterator InputTriROI.Add(*RemainingTriangles.CreateConstIterator()); TFunction TidFilter = [](int32, int32){ return true; }; if (ShouldFilterTriangles()) { TidFilter = [this](int32 Index, int32 Tid) { return SelectionTids.Contains(Tid); }; } FMeshConnectedComponents::GrowToConnectedTriangles(CurrentMesh, InputTriROI, OutputTriROI, &QueueTempBuffer, &DoneTempBuffer, TidFilter); for (int32 tid : OutputTriROI) { RemainingTriangles.Remove(tid); FIndex3i TriVertices = CurrentMesh->GetTriangle(tid); for (int32 j = 0; j < 3; ++j) { if (DoneVertices.Contains(TriVertices[j]) == false) { int32 vid = TriVertices[j]; ActionData.ROIVertices.Add(vid); int32 srcvid = NonManifoldMappingSupport.GetOriginalNonManifoldVertexID(vid); ActionData.ROIBefore.Add(AttribData.CurrentValues[srcvid]); ActionData.ROIAfter.Add(SetValue); DoneVertices.Add(vid); } } } } } void UMeshAttributePaintTool::ApplyAction(EMeshAttributePaintToolActions ActionType) { //switch (ActionType) //{ //} } void UMeshAttributePaintTool::UpdateSelectedAttribute(int32 NewSelectedIndex) { AttribProps->Initialize(AttributeSource->GetAttributeList(), false); AttribProps->Attribute = AttribProps->Attributes[FMath::Clamp(NewSelectedIndex, 0, AttribProps->Attributes.Num() - 1)]; bVisibleAttributeValid = false; } void UMeshAttributePaintTool::OnShutdown(EToolShutdownType ShutdownType) { BrushProperties->SaveProperties(this); BrushActionProps->SaveProperties(this); StoreCurrentAttribute(); if (ShutdownType == EToolShutdownType::Accept) { // this block bakes the modified DynamicMeshComponent back into the StaticMeshComponent inside an undo transaction GetToolManager()->BeginUndoTransaction(LOCTEXT("MeshAttributePaintTool", "Edit Attributes")); if (EditedMesh.IsValid()) { UE::ToolTarget::CommitMeshDescriptionUpdate(Target, EditedMesh.Get()); } else { UE::ToolTarget::CommitDynamicMeshUpdate(Target, *EditedDynamicMesh, false); } GetToolManager()->EndUndoTransaction(); } } void UMeshAttributePaintTool::BeginChange() { if (CurrentAttributeIndex < 0) { return; } if (! ActiveChangeBuilder) { ActiveChangeBuilder = MakeUnique>(); } ActiveChangeBuilder->BeginNewChange(); ActiveChangeBuilder->Change->CustomData = CurrentAttributeIndex; LongTransactions.Open(LOCTEXT("AttributeValuesChange", "Paint"), GetToolManager()); } TUniquePtr UMeshAttributePaintTool::EndChange() { if (!ActiveChangeBuilder) { return nullptr; } TUniquePtr Result = ActiveChangeBuilder->ExtractResult(); if (Result) { Result->ApplyFunction = [](UObject* Object, const int32& AttribIndex, const TArray& Indices, const TArray& Values) { UMeshAttributePaintTool* Tool = CastChecked(Object); Tool->ExternalUpdateValues(AttribIndex, Indices, Values); }; Result->RevertFunction = [](UObject* Object, const int32& AttribIndex, const TArray& Indices, const TArray& Values) { UMeshAttributePaintTool* Tool = CastChecked(Object); Tool->ExternalUpdateValues(AttribIndex, Indices, Values); }; return MoveTemp(Result); } return nullptr; } void UMeshAttributePaintTool::ExternalUpdateValues(int32 AttribIndex, const TArray& VertexIndices, const TArray& NewValues) { if (!ensure(Attributes.IsValidIndex(AttribIndex))) { return; } FAttributeData& AttribData = Attributes[AttribIndex]; int32 NumV = VertexIndices.Num(); for (int32 k = 0; k < NumV; ++k) { AttribData.CurrentValues[VertexIndices[k]] = NewValues[k]; } if (AttribIndex == CurrentAttributeIndex) { PreviewMesh->EditMesh([&](FDynamicMesh3& Mesh) { TArray ElIDs; FDynamicMeshColorOverlay* ColorOverlay = Mesh.Attributes()->PrimaryColors(); for (int32 vid : VertexIndices) { FVector4f NewColor( ToVector4(ColorMapper->ToColor(AttribData.CurrentValues[vid])) ); ColorOverlay->GetVertexElements(vid, ElIDs); for (int elid : ElIDs) { ColorOverlay->SetElement(elid, NewColor); } ElIDs.Reset(); } }); } } bool UMeshAttributePaintTool::ShouldFilterTriangles() const { return BrushActionProps->bIsolateGeometrySelection // Could check GeometrySelection.IsSet(), but might as well check SelectionTids instead // which will be nonempty if there was a selection, since that's what we'll actually use. && !SelectionTids.IsEmpty(); } #undef LOCTEXT_NAMESPACE