// Copyright Epic Games, Inc. All Rights Reserved. #include "Commands/RetriangulateGeometrySelectionCommand.h" #include "ToolContextInterfaces.h" #include "UDynamicMesh.h" #include "DynamicMesh/DynamicMesh3.h" #include "DynamicMesh/DynamicMeshChangeTracker.h" #include "DynamicMeshEditor.h" #include "Changes/MeshChange.h" #include "Selections/GeometrySelectionUtil.h" #include "Operations/PolygroupRemesh.h" #include "ConstrainedDelaunay2.h" #include "Operations/SimpleHoleFiller.h" #include "MeshRegionBoundaryLoops.h" #include "DynamicMesh/MeshIndexUtil.h" #include "Selection/DynamicMeshSelector.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(RetriangulateGeometrySelectionCommand) using namespace UE::Geometry; #define LOCTEXT_NAMESPACE "URetriangulateGeometrySelectionCommand" FText URetriangulateGeometrySelectionCommand::GetCommandShortString() const { return LOCTEXT("ShortString", "Retriangulate Selection"); } bool URetriangulateGeometrySelectionCommand::CanExecuteCommandForSelection(UGeometrySelectionEditCommandArguments* SelectionArgs) { return SelectionArgs->IsMatchingType(FGeometryIdentifier::ETargetType::MeshContainer, FGeometryIdentifier::EObjectType::DynamicMesh) && (SelectionArgs->SelectionHandle.Selection->ElementType == EGeometryElementType::Face) && (SelectionArgs->SelectionHandle.Selection->TopologyType == EGeometryTopologyType::Polygroup); } void URetriangulateGeometrySelectionCommand::ExecuteCommandForSelection(UGeometrySelectionEditCommandArguments* SelectionArgs, UInteractiveCommandResult** Result) { IGeometrySelector* BaseSelector = SelectionArgs->SelectionHandle.Selector; if (!ensure(BaseSelector != nullptr)) { UE_LOG(LogGeometry, Warning, TEXT("URetriangulateGeometrySelectionCommand: Retriangulate Selection requires Selector be provided in Selection Arguments")); return; } if (Result != nullptr) { *Result = nullptr; } // should have been verified by CanExecute check(SelectionArgs->IsMatchingType(FGeometryIdentifier::ETargetType::MeshContainer, FGeometryIdentifier::EObjectType::DynamicMesh)); // TODO: extremely hardcoded behavior right here. Need a way to make this more generic, however // having the UpdateAfterGeometryEdit function in the base GeometrySelector does not make sense as // it is specific to meshes. Probably this Command needs to be specialized for Mesh Edits. FBaseDynamicMeshSelector* BaseDynamicMeshSelector = static_cast(BaseSelector); // collect up all our inputs UDynamicMesh* MeshObject = SelectionArgs->SelectionHandle.Identifier.GetAsObjectType(); check(MeshObject != nullptr); const FGeometrySelection* Selection = SelectionArgs->SelectionHandle.Selection; bool bTrackChanges = SelectionArgs->HasTransactionsAPI(); TUniquePtr DynamicMeshChange; // only initialized if bTrackChanges == true // apply the Retriangulate operation MeshObject->EditMesh([&](FDynamicMesh3& EditMesh) { // build list of triangles from whatever the selection contains TSet TriangleList; //UE::Geometry::FPolygroupSet UsePolygroupSet = ...; // need to support this eventually UE::Geometry::EnumerateSelectionTriangles(*Selection, EditMesh, [&](int32 TriangleID) { TriangleList.Add(TriangleID); }); if (TriangleList.Num() == 0) { for (int32 tid : EditMesh.TriangleIndicesItr()) { TriangleList.Add(tid); } } // mark triangles for change FDynamicMeshChangeTracker ChangeTracker(&EditMesh); FGroupTopology Topology(&EditMesh, true); if (bTrackChanges) { ChangeTracker.BeginChange(); } if ( TriangleList.Num() == EditMesh.TriangleCount() ) { if (bTrackChanges) { ChangeTracker.SaveTriangles(TriangleList, true); } FPolygroupRemesh Remesh(&EditMesh, &Topology, ConstrainedDelaunayTriangulate); bool bSuccess = Remesh.Compute(); } else { // ported from UEditMeshPolygonsTool::ApplyRetriangulate(), should be moved to a standalone geometry op const FDynamicMeshAttributeSet* Attributes = EditMesh.HasAttributes() ? EditMesh.Attributes() : nullptr; TSet SelectedGroupIDs; for (int32 tid : TriangleList) { SelectedGroupIDs.Add( Topology.GetGroupID(tid) ); } int32 nCompleted = 0; FDynamicMeshEditor Editor(&EditMesh); for (int32 GroupID : SelectedGroupIDs) { const TArray& Triangles = Topology.GetGroupTriangles(GroupID); ChangeTracker.SaveTriangles(Triangles, true); FMeshRegionBoundaryLoops RegionLoops(&EditMesh, Triangles, true); if (!RegionLoops.bFailed && RegionLoops.Loops.Num() == 1 && Triangles.Num() > 1) { TArray> VidUVMaps; if (Attributes) { for (int i = 0; i < Attributes->NumUVLayers(); ++i) { VidUVMaps.Emplace(); RegionLoops.GetLoopOverlayMap(RegionLoops.Loops[0], *Attributes->GetUVLayer(i), VidUVMaps.Last()); } } // We don't want to remove isolated vertices while removing triangles because we don't // want to throw away boundary verts. However, this means that we'll have to go back // through these vertices later to throw away isolated internal verts. TArray OldVertices; UE::Geometry::TriangleToVertexIDs(&EditMesh, Triangles, OldVertices); Editor.RemoveTriangles(Topology.GetGroupTriangles(GroupID), false); RegionLoops.Loops[0].Reverse(); FSimpleHoleFiller Filler(&EditMesh, RegionLoops.Loops[0]); Filler.FillType = FSimpleHoleFiller::EFillType::PolygonEarClipping; Filler.Fill(GroupID); // Throw away any of the old verts that are still isolated (they were in the interior of the group) for (int32 Vid : OldVertices) { if ( !EditMesh.IsReferencedVertex(Vid) ) { constexpr bool bPreserveManifold = false; EditMesh.RemoveVertex(Vid, bPreserveManifold); } } if (Attributes) { for (int i = 0; i < Attributes->NumUVLayers(); ++i) { RegionLoops.UpdateLoopOverlayMapValidity(VidUVMaps[i], *Attributes->GetUVLayer(i)); } Filler.UpdateAttributes(VidUVMaps); } nCompleted++; } } } // extract the change record if (bTrackChanges) { DynamicMeshChange = ChangeTracker.EndChange(); } }, EDynamicMeshChangeType::GeneralEdit, EDynamicMeshAttributeChangeFlags::Unknown, false); // emit change if ( bTrackChanges && DynamicMeshChange.IsValid() ) { SelectionArgs->GetTransactionsAPI()->BeginUndoTransaction(GetCommandShortString()); BaseDynamicMeshSelector->UpdateAfterGeometryEdit( SelectionArgs->GetTransactionsAPI(), true, MoveTemp(DynamicMeshChange), GetCommandShortString()); SelectionArgs->GetTransactionsAPI()->EndUndoTransaction(); } if (Result != nullptr) { UGeometrySelectionEditCommandResult* NewResult = NewObject(); NewResult->SourceHandle = SelectionArgs->SelectionHandle; // todo... //NewResult->OutputSelection = *NewResult->SourceHandle.Selection; *Result = NewResult; } } #undef LOCTEXT_NAMESPACE