Files
UnrealEngine/Engine/Plugins/Runtime/MeshModelingToolset/Source/ModelingComponents/Private/Selection/PolygonSelectionMechanic.cpp
2025-05-18 13:04:45 +08:00

572 lines
19 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Selection/PolygonSelectionMechanic.h"
#include "Engine/World.h"
#include "InteractiveToolManager.h"
#include "Selection/GroupTopologySelector.h"
#include "Selection/PersistentMeshSelection.h"
#include "Selections/GeometrySelection.h"
#include "Selections/GeometrySelectionUtil.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(PolygonSelectionMechanic)
using namespace UE::Geometry;
#define LOCTEXT_NAMESPACE "UPolygonSelectionMechanic"
namespace PolygonSelectionMechanicLocals
{
using namespace UE::Geometry;
EGeometryElementType ToGeometryElementType(const FGroupTopologySelection& Selection)
{
if (!Selection.SelectedCornerIDs.IsEmpty())
{
return EGeometryElementType::Vertex;
}
else if (!Selection.SelectedEdgeIDs.IsEmpty())
{
return EGeometryElementType::Edge;
}
return EGeometryElementType::Face;
}
}
void UPolygonSelectionMechanic::Initialize(
const FDynamicMesh3* MeshIn,
FTransform3d TargetTransformIn,
UWorld* WorldIn,
const FGroupTopology* TopologyIn,
TFunction<FDynamicMeshAABBTree3* ()> GetSpatialSourceFuncIn)
{
Topology = TopologyIn;
TopoSelector = MakeShared<FGroupTopologySelector>(MeshIn, TopologyIn);
UMeshTopologySelectionMechanic::Initialize(MeshIn, TargetTransformIn, WorldIn, GetSpatialSourceFuncIn);
}
void UPolygonSelectionMechanic::Initialize(
UDynamicMeshComponent* MeshComponentIn,
const FGroupTopology* TopologyIn,
TFunction<FDynamicMeshAABBTree3* ()> GetSpatialSourceFuncIn)
{
Initialize(MeshComponentIn->GetMesh(),
(FTransform3d)MeshComponentIn->GetComponentTransform(),
MeshComponentIn->GetWorld(),
TopologyIn,
GetSpatialSourceFuncIn);
}
void UPolygonSelectionMechanic::GetSelection_AsGroupTopology(UE::Geometry::FGeometrySelection& SelectionOut, const FCompactMaps* CompactMapsToApply) const
{
const FGroupTopologySelection& CurSelection = PersistentSelection;
if (SelectionOut.TopologyType != EGeometryTopologyType::Polygroup)
{
return;
}
if (SelectionOut.ElementType == EGeometryElementType::Vertex)
{
for (int32 CornerID : CurSelection.SelectedCornerIDs)
{
int32 VertexID = Topology->GetCornerVertexID(CornerID);
if (CompactMapsToApply != nullptr)
{
VertexID = CompactMapsToApply->GetVertexMapping(VertexID);
}
SelectionOut.Selection.Add( FGeoSelectionID(VertexID, CornerID).Encoded() );
}
}
else if (SelectionOut.ElementType == EGeometryElementType::Edge)
{
if ( CompactMapsToApply == nullptr )
{
for (int32 GroupEdgeID : CurSelection.SelectedEdgeIDs)
{
const TArray<int>& GroupEdge = Topology->GetGroupEdgeEdges(GroupEdgeID);
FMeshTriEdgeID TriEdgeID = Topology->GetMesh()->GetTriEdgeIDFromEdgeID(GroupEdge[0]);
SelectionOut.Selection.Add( FGeoSelectionID(TriEdgeID.Encoded(), GroupEdgeID).Encoded() );
}
}
else
{
for (int32 GroupEdgeID : CurSelection.SelectedEdgeIDs)
{
const TArray<int>& GroupEdgeVerts = Topology->GetGroupEdgeVertices(GroupEdgeID);
if (GroupEdgeVerts.Num() > 1)
{
int32 VID0 = CompactMapsToApply->GetVertexMapping(GroupEdgeVerts[0]);
int32 VID1 = CompactMapsToApply->GetVertexMapping(GroupEdgeVerts[1]);
int32 FoundEID = Topology->GetMesh()->FindEdge(VID0, VID1);
if (FoundEID != IndexConstants::InvalidID)
{
FMeshTriEdgeID TriEdgeID = Topology->GetMesh()->GetTriEdgeIDFromEdgeID(FoundEID);
SelectionOut.Selection.Add(FGeoSelectionID(TriEdgeID.Encoded(), GroupEdgeID).Encoded());
}
}
}
}
}
else if (SelectionOut.ElementType == EGeometryElementType::Face)
{
for (int32 GroupID : CurSelection.SelectedGroupIDs)
{
const FGroupTopology::FGroup* GroupFace = Topology->FindGroupByID(GroupID);
if ( GroupFace )
{
FGeoSelectionID ID = FGeoSelectionID(GroupFace->Triangles[0], GroupFace->GroupID);
SelectionOut.Selection.Add(ID.Encoded());
}
}
}
}
void UPolygonSelectionMechanic::GetSelection_AsTriangleTopology(UE::Geometry::FGeometrySelection& SelectionOut, const FCompactMaps* CompactMapsToApply) const
{
// note: this is currently the same code as GetSelection_AsGroupTopology() except for the topology-type verification check, and the topology type of the selected elements
const FGroupTopologySelection& CurSelection = PersistentSelection;
if (SelectionOut.TopologyType != EGeometryTopologyType::Triangle)
{
return;
}
if (SelectionOut.ElementType == EGeometryElementType::Vertex)
{
for (int32 CornerID : CurSelection.SelectedCornerIDs)
{
int32 VertexID = Topology->GetCornerVertexID(CornerID);
if (CompactMapsToApply != nullptr)
{
VertexID = CompactMapsToApply->GetVertexMapping(VertexID);
}
SelectionOut.Selection.Add( FGeoSelectionID::MeshVertex(VertexID).Encoded() );
}
}
else if (SelectionOut.ElementType == EGeometryElementType::Edge)
{
if ( CompactMapsToApply == nullptr )
{
for (int32 GroupEdgeID : CurSelection.SelectedEdgeIDs)
{
const TArray<int>& GroupEdge = Topology->GetGroupEdgeEdges(GroupEdgeID);
FMeshTriEdgeID TriEdgeID = Topology->GetMesh()->GetTriEdgeIDFromEdgeID(GroupEdge[0]);
SelectionOut.Selection.Add( FGeoSelectionID::MeshEdge(TriEdgeID).Encoded() );
}
}
else
{
for (int32 GroupEdgeID : CurSelection.SelectedEdgeIDs)
{
const TArray<int>& GroupEdgeVerts = Topology->GetGroupEdgeVertices(GroupEdgeID);
if (GroupEdgeVerts.Num() > 1)
{
int32 VID0 = CompactMapsToApply->GetVertexMapping(GroupEdgeVerts[0]);
int32 VID1 = CompactMapsToApply->GetVertexMapping(GroupEdgeVerts[1]);
int32 FoundEID = Topology->GetMesh()->FindEdge(VID0, VID1);
if (FoundEID != IndexConstants::InvalidID)
{
FMeshTriEdgeID TriEdgeID = Topology->GetMesh()->GetTriEdgeIDFromEdgeID(FoundEID);
SelectionOut.Selection.Add(FGeoSelectionID::MeshEdge(TriEdgeID).Encoded());
}
}
}
}
}
else if (SelectionOut.ElementType == EGeometryElementType::Face)
{
for (int32 GroupID : CurSelection.SelectedGroupIDs)
{
const FGroupTopology::FGroup* GroupFace = Topology->FindGroupByID(GroupID);
if ( GroupFace )
{
SelectionOut.Selection.Add( FGeoSelectionID::MeshTriangle(GroupFace->Triangles[0]).Encoded() );
}
}
}
}
void UPolygonSelectionMechanic::SetSelection_AsGroupTopology(const UE::Geometry::FGeometrySelection& Selection)
{
if (Selection.TopologyType != EGeometryTopologyType::Polygroup)
{
return;
}
PersistentSelection.Clear();
if (Selection.ElementType == EGeometryElementType::Vertex)
{
for (uint64 ElementID : Selection.Selection)
{
int32 VertexID = FGeoSelectionID(ElementID).GeometryID;
int32 CornerID = Topology->GetCornerIDFromVertexID(VertexID);
if (CornerID != IndexConstants::InvalidID)
{
PersistentSelection.SelectedCornerIDs.Add(CornerID);
}
}
}
else if (Selection.ElementType == EGeometryElementType::Edge)
{
for (uint64 ElementID : Selection.Selection)
{
FMeshTriEdgeID TriEdgeID(FGeoSelectionID(ElementID).GeometryID);
int32 GroupEdgeID = Topology->FindGroupEdgeID(TriEdgeID);
if (GroupEdgeID != IndexConstants::InvalidID)
{
PersistentSelection.SelectedEdgeIDs.Add(GroupEdgeID);
}
}
}
else if (Selection.ElementType == EGeometryElementType::Face)
{
for (uint64 ElementID : Selection.Selection)
{
int32 TriangleID = FGeoSelectionID(ElementID).GeometryID;
int32 GroupID = Topology->GetGroupID(TriangleID);
if (Topology->FindGroupByID(GroupID) != nullptr)
{
PersistentSelection.SelectedGroupIDs.Add(GroupID);
}
}
}
}
void UPolygonSelectionMechanic::SetSelection_AsTriangleTopology(const UE::Geometry::FGeometrySelection& Selection)
{
if (Selection.TopologyType != EGeometryTopologyType::Triangle)
{
return;
}
PersistentSelection.Clear();
if (Selection.ElementType == EGeometryElementType::Vertex)
{
for (uint64 ElementID : Selection.Selection)
{
int32 VertexID = FGeoSelectionID(ElementID).GeometryID;
int32 CornerID = Topology->GetCornerIDFromVertexID(VertexID);
if (CornerID != IndexConstants::InvalidID)
{
PersistentSelection.SelectedCornerIDs.Add(CornerID);
}
}
}
else if (Selection.ElementType == EGeometryElementType::Edge)
{
for (uint64 ElementID : Selection.Selection)
{
FMeshTriEdgeID TriEdgeID(FGeoSelectionID(ElementID).GeometryID);
int32 GroupEdgeID = Topology->FindGroupEdgeID(TriEdgeID);
if (GroupEdgeID != IndexConstants::InvalidID)
{
PersistentSelection.SelectedEdgeIDs.Add(GroupEdgeID);
}
}
}
else if (Selection.ElementType == EGeometryElementType::Face)
{
for (uint64 ElementID : Selection.Selection)
{
int32 TriangleID = FGeoSelectionID(ElementID).GeometryID;
int32 GroupID = Topology->GetGroupID(TriangleID);
if (Topology->FindGroupByID(GroupID) != nullptr)
{
PersistentSelection.SelectedGroupIDs.Add(GroupID);
}
}
}
}
bool UPolygonSelectionMechanic::ExecuteActionThroughGeometrySelection(bool bAsTriangleTopology, const FText& TransactionName,
TFunctionRef<bool(UE::Geometry::FGeometrySelection& SelectionToModifyInPlace)> SelectionProcessor)
{
using namespace PolygonSelectionMechanicLocals;
using namespace UE::Geometry;
if (!Topology || !Topology->GetMesh())
{
return false;
}
FGeometrySelection GeometrySelection;
GeometrySelection.InitializeTypes(ToGeometryElementType(PersistentSelection),
bAsTriangleTopology ? EGeometryTopologyType::Triangle : EGeometryTopologyType::Polygroup);
if (bAsTriangleTopology)
{
GetSelection_AsTriangleTopology(GeometrySelection);
}
else
{
GetSelection_AsGroupTopology(GeometrySelection);
}
bool bSuccess = SelectionProcessor(GeometrySelection);
if (!bSuccess)
{
return false;
}
ParentTool->GetToolManager()->BeginUndoTransaction(TransactionName);
BeginChange();
if (bAsTriangleTopology)
{
SetSelection_AsTriangleTopology(GeometrySelection);
}
else
{
SetSelection_AsGroupTopology(GeometrySelection);
}
SelectionTimestamp++;
OnSelectionChanged.Broadcast();
EndChangeAndEmitIfModified();
ParentTool->GetToolManager()->EndUndoTransaction();
return true;
}
void UPolygonSelectionMechanic::GrowSelection(bool bAsTriangleTopology)
{
using namespace UE::Geometry;
ExecuteActionThroughGeometrySelection(bAsTriangleTopology, LOCTEXT("GrowSelectionChange", "Grow Selection"),
[this](FGeometrySelection& GeometrySelection)
{
FGeometrySelection BoundaryConnectedSelection;
BoundaryConnectedSelection.InitializeTypes(GeometrySelection);
bool bSuccess = MakeBoundaryConnectedSelection(*Topology->GetMesh(), Topology, GeometrySelection,
[](FGeoSelectionID) { return true; }, BoundaryConnectedSelection);
bSuccess = bSuccess && CombineSelectionInPlace(GeometrySelection, BoundaryConnectedSelection,
UE::Geometry::EGeometrySelectionCombineModes::Add);
return bSuccess;
});
}
void UPolygonSelectionMechanic::ShrinkSelection(bool bAsTriangleTopology)
{
using namespace UE::Geometry;
ExecuteActionThroughGeometrySelection(bAsTriangleTopology, LOCTEXT("ShrinkSelectionChange", "Shrink Selection"),
[this](FGeometrySelection& GeometrySelection)
{
FGeometrySelection BoundaryConnectedSelection;
BoundaryConnectedSelection.InitializeTypes(GeometrySelection);
bool bSuccess = MakeBoundaryConnectedSelection(*Topology->GetMesh(), Topology, GeometrySelection,
[](FGeoSelectionID) { return true; }, BoundaryConnectedSelection);
bSuccess = bSuccess && CombineSelectionInPlace(GeometrySelection, BoundaryConnectedSelection,
UE::Geometry::EGeometrySelectionCombineModes::Subtract);
return bSuccess;
});
}
void UPolygonSelectionMechanic::ConvertSelectionToBorderVertices(bool bAsTriangleTopology)
{
using namespace UE::Geometry;
ExecuteActionThroughGeometrySelection(bAsTriangleTopology, LOCTEXT("BorderSelectionChange", "Select Border"),
[this, bAsTriangleTopology](FGeometrySelection& GeometrySelection)
{
TSet<int32> Unused;
if (bAsTriangleTopology)
{
TSet<int32> BoundaryVertices;
bool bSuccess = GetSelectionBoundaryVertices(*Topology->GetMesh(), Topology, GeometrySelection,
BoundaryVertices, Unused);
if (!bSuccess) { return false; }
GeometrySelection.Selection.Reset();
GeometrySelection.InitializeTypes(EGeometryElementType::Vertex, EGeometryTopologyType::Triangle);
for (int32 Vid : BoundaryVertices)
{
GeometrySelection.Selection.Add(FGeoSelectionID::MeshVertex(Vid).Encoded());
}
}
else
{
TSet<int32> BoundaryCorners;
bool bSuccess = GetSelectionBoundaryCorners(*Topology->GetMesh(), Topology, GeometrySelection,
BoundaryCorners, Unused);
if (!bSuccess) { return false; }
GeometrySelection.Selection.Reset();
GeometrySelection.InitializeTypes(EGeometryElementType::Vertex, EGeometryTopologyType::Polygroup);
for (int32 CornerID : BoundaryCorners)
{
GeometrySelection.Selection.Add(FGeoSelectionID(Topology->GetCornerVertexID(CornerID), CornerID).Encoded());
}
}
return true;
});
}
void UPolygonSelectionMechanic::FloodSelection()
{
using namespace UE::Geometry;
ExecuteActionThroughGeometrySelection(true, LOCTEXT("FloodSelectionChange", "Flood Selection"),
[this](FGeometrySelection& GeometrySelection)
{
FGeometrySelection NewSelection;
NewSelection.InitializeTypes(GeometrySelection);
bool bSuccess = MakeSelectAllConnectedSelection(*Topology->GetMesh(), Topology, GeometrySelection,
[](FGeoSelectionID) { return true; }, [](FGeoSelectionID, FGeoSelectionID) { return true; },
NewSelection);
if (!bSuccess) { return false; }
GeometrySelection = NewSelection;
return true;
});
}
bool UPolygonSelectionMechanic::UpdateHighlight(const FRay& WorldRay)
{
checkf(DrawnTriangleSetComponent != nullptr, TEXT("Initialize() not called on UMeshTopologySelectionMechanic."));
FRay3d LocalRay(TargetTransform.InverseTransformPosition((FVector3d)WorldRay.Origin),
TargetTransform.InverseTransformVector((FVector3d)WorldRay.Direction));
UE::Geometry::Normalize(LocalRay.Direction);
HilightSelection.Clear();
FVector3d LocalPosition, LocalNormal;
FGroupTopologySelector::FSelectionSettings TopoSelectorSettings = GetTopoSelectorSettings(CameraState.bIsOrthographic);
bool bHit = TopoSelector->FindSelectedElement(TopoSelectorSettings, LocalRay, HilightSelection, LocalPosition, LocalNormal);
TSharedPtr<FGroupTopologySelector> GroupTopoSelector = StaticCastSharedPtr<FGroupTopologySelector>(TopoSelector);
if (HilightSelection.SelectedEdgeIDs.Num() > 0 && Properties->bSelectEdgeRings && ShouldSelectEdgeRingsFunc())
{
GroupTopoSelector->ExpandSelectionByEdgeRings(HilightSelection);
}
if (HilightSelection.SelectedEdgeIDs.Num() > 0 && Properties->bSelectEdgeLoops && ShouldSelectEdgeLoopsFunc())
{
GroupTopoSelector->ExpandSelectionByEdgeLoops(HilightSelection);
GroupTopoSelector->ExpandSelectionByBoundaryLoops(HilightSelection);
}
// Don't hover highlight a selection that we already selected, because people didn't like that
if (PersistentSelection.Contains(HilightSelection))
{
HilightSelection.Clear();
}
// Currently we draw highlighted edges/vertices differently from highlighted faces. Edges/vertices
// get drawn in the Render() call, so it is sufficient to just update HighlightSelection above.
// Faces, meanwhile, get placed into a Component that is rendered through the normal rendering system.
// So, we need to update the component when the highlighted selection changes.
// Put hovered groups in a set to easily compare to current
TSet<int> NewlyHighlightedGroups;
NewlyHighlightedGroups.Append(HilightSelection.SelectedGroupIDs);
// See if we're currently highlighting any groups that we're not supposed to
if (!NewlyHighlightedGroups.Includes(CurrentlyHighlightedGroups))
{
DrawnTriangleSetComponent->Clear();
CurrentlyHighlightedGroups.Empty();
}
// See if we need to add any groups
if (!CurrentlyHighlightedGroups.Includes(NewlyHighlightedGroups))
{
// Add triangles for each new group
for (int Gid : HilightSelection.SelectedGroupIDs)
{
if (!CurrentlyHighlightedGroups.Contains(Gid))
{
for (int32 Tid : Topology->GetGroupTriangles(Gid))
{
// We use the triangle normals because the normal overlay isn't guaranteed to be valid as we edit the mesh
FVector3d TriangleNormal = Mesh->GetTriNormal(Tid);
// The UV's and colors here don't currently get used by HighlightedFaceMaterial, but we set them anyway
FIndex3i VertIndices = Mesh->GetTriangle(Tid);
DrawnTriangleSetComponent->AddTriangle(FRenderableTriangle(HighlightedFaceMaterial,
FRenderableTriangleVertex((FVector)Mesh->GetVertex(VertIndices.A), (FVector2D)Mesh->GetVertexUV(VertIndices.A), (FVector)TriangleNormal, FLinearColor(Mesh->GetVertexColor(VertIndices.A)).ToFColor(true)),
FRenderableTriangleVertex((FVector)Mesh->GetVertex(VertIndices.B), (FVector2D)Mesh->GetVertexUV(VertIndices.B), (FVector)TriangleNormal, FLinearColor(Mesh->GetVertexColor(VertIndices.B)).ToFColor(true)),
FRenderableTriangleVertex((FVector)Mesh->GetVertex(VertIndices.C), (FVector2D)Mesh->GetVertexUV(VertIndices.C), (FVector)TriangleNormal, FLinearColor(Mesh->GetVertexColor(VertIndices.C)).ToFColor(true))));
}
CurrentlyHighlightedGroups.Add(Gid);
}
}//end iterating through groups
}//end if groups need to be added
return bHit;
}
bool UPolygonSelectionMechanic::UpdateSelection(const FRay& WorldRay, FVector3d& LocalHitPositionOut, FVector3d& LocalHitNormalOut)
{
FRay3d LocalRay(TargetTransform.InverseTransformPosition((FVector3d)WorldRay.Origin),
TargetTransform.InverseTransformVector((FVector3d)WorldRay.Direction));
UE::Geometry::Normalize(LocalRay.Direction);
const FGroupTopologySelection PreviousSelection = PersistentSelection;
FVector3d LocalPosition, LocalNormal;
FGroupTopologySelection Selection;
FGroupTopologySelector::FSelectionSettings TopoSelectorSettings = GetTopoSelectorSettings(CameraState.bIsOrthographic);
if (TopoSelector->FindSelectedElement(TopoSelectorSettings, LocalRay, Selection, LocalPosition, LocalNormal))
{
LocalHitPositionOut = LocalPosition;
LocalHitNormalOut = LocalNormal;
TSharedPtr<FGroupTopologySelector> GroupTopoSelector = StaticCastSharedPtr<FGroupTopologySelector>(TopoSelector);
if (Selection.SelectedEdgeIDs.Num() > 0 && Properties->bSelectEdgeRings && ShouldSelectEdgeRingsFunc())
{
GroupTopoSelector->ExpandSelectionByEdgeRings(Selection);
}
if (Selection.SelectedEdgeIDs.Num() > 0 && Properties->bSelectEdgeLoops && ShouldSelectEdgeLoopsFunc())
{
GroupTopoSelector->ExpandSelectionByEdgeLoops(Selection);
GroupTopoSelector->ExpandSelectionByBoundaryLoops(Selection);
}
}
if (ShouldAddToSelectionFunc())
{
if (ShouldRemoveFromSelectionFunc())
{
PersistentSelection.Toggle(Selection);
}
else
{
PersistentSelection.Append(Selection);
}
}
else if (ShouldRemoveFromSelectionFunc())
{
PersistentSelection.Remove(Selection);
}
else
{
PersistentSelection = Selection;
}
if (PersistentSelection != PreviousSelection)
{
SelectionTimestamp++;
OnSelectionChanged.Broadcast();
return true;
}
return false;
}
#undef LOCTEXT_NAMESPACE