// Copyright Epic Games, Inc. All Rights Reserved. #include "Selections/GeometrySelectionUtil.h" #include "DynamicMesh/DynamicMesh3.h" #include "DynamicMesh/ColliderMesh.h" #include "DynamicMesh/MeshNormals.h" #include "TriangleTypes.h" #include "SegmentTypes.h" #include "GroupTopology.h" #include "Selections/MeshConnectedComponents.h" #include "Selections/MeshEdgeSelection.h" #include "Selections/MeshVertexSelection.h" #include "Algo/Find.h" #include "Selections/MeshFaceSelection.h" namespace GeometrySelectionUtilLocals { // Return an integer in the range [0,5] which can be used to look up a handling function based on the Selection type int GetSelectionTypeAsIndex(const UE::Geometry::FGeometrySelection& Selection) { const int Index = ((int)Selection.ElementType / 2) + ((int)Selection.TopologyType / 2) * 3; checkSlow(Index >= 0); checkSlow(Index <= 5); return Index; } // We don't currently have an overload of EnumerateSelectionTriangles that takes a FGroupTopology // instead of FPolygroupSet. If we build out the below version to support the other selection // types (vertex, edge), we might want to expose it. /** * Given a selection with elements of type EGeometryElementType::Face, call TriangleFunc on * each triangle of the selection. * * @param GroupTopology must not be null if Selection is EGeometryTopologyType::Polygroup */ bool EnumerateFaceElementSelectionTriangles( const UE::Geometry::FGeometrySelection& Selection, const UE::Geometry::FDynamicMesh3& Mesh, const UE::Geometry::FGroupTopology* GroupTopology, TFunctionRef TriangleFunc) { using namespace UE::Geometry; if (!ensure(Selection.ElementType == EGeometryElementType::Face)) { return false; } if (Selection.TopologyType == EGeometryTopologyType::Polygroup) { if (!ensure(GroupTopology)) { return false; } for (uint64 EncodedID : Selection.Selection) { FGeoSelectionID GroupTriID(EncodedID); int32 SeedTriangleID = (int32)GroupTriID.GeometryID; int32 GroupID = (int32)GroupTriID.TopologyID; if (Mesh.IsTriangle(SeedTriangleID)) { for (int32 Tid : GroupTopology->GetGroupTriangles(GroupID)) { TriangleFunc(Tid); } } } } else if (Selection.TopologyType == EGeometryTopologyType::Triangle) { for (uint64 TriangleID : Selection.Selection) { if (Mesh.IsTriangle((int32)TriangleID)) { TriangleFunc((int32)TriangleID); } } } else { return ensure(false); } return true; } // We don't currently have an EnumerateSelectionEdges. If we build out the below to handle // other selection types (vertex, face), we might want to expose it. /** * Given a selection with elements of type EGeometryElementType::Edge, call EdgeFunc on * each mesh edge (with the Eid passed in) of the selection. * * @param GroupTopology must not be null if Selection is EGeometryTopologyType::Polygroup */ bool EnumerateEdgeElementSelectionEdges( const UE::Geometry::FGeometrySelection& Selection, const UE::Geometry::FDynamicMesh3& Mesh, const UE::Geometry::FGroupTopology* GroupTopology, TFunctionRef EdgeFunc) { using namespace UE::Geometry; if (!ensure(Selection.ElementType == EGeometryElementType::Edge)) { return false; } if (Selection.TopologyType == EGeometryTopologyType::Polygroup) { if (!ensure(GroupTopology)) { return false; } for (uint64 EncodedID : Selection.Selection) { FMeshTriEdgeID TriEdgeID(FGeoSelectionID(EncodedID).GeometryID); int32 SeedEdgeID = Mesh.IsTriangle(TriEdgeID.TriangleID) ? Mesh.GetTriEdge(TriEdgeID.TriangleID, TriEdgeID.TriEdgeIndex) : IndexConstants::InvalidID; if (Mesh.IsEdge(SeedEdgeID)) { int32 GroupEdgeID = GroupTopology->FindGroupEdgeID(SeedEdgeID); for (int32 Eid : GroupTopology->GetGroupEdgeEdges(GroupEdgeID)) { EdgeFunc(Eid); } } } } else if (Selection.TopologyType == EGeometryTopologyType::Triangle) { for (uint64 EncodedID : Selection.Selection) { FMeshTriEdgeID TriEdgeID(FGeoSelectionID(EncodedID).GeometryID); int32 EdgeID = Mesh.IsTriangle(TriEdgeID.TriangleID) ? Mesh.GetTriEdge(TriEdgeID.TriangleID, TriEdgeID.TriEdgeIndex) : IndexConstants::InvalidID; if (Mesh.IsEdge(EdgeID)) { EdgeFunc(EdgeID); } } } else { return ensure(false); } return true; } bool EnumerateVertexElementSelectionVertices( const UE::Geometry::FGeometrySelection& Selection, const UE::Geometry::FDynamicMesh3& Mesh, const UE::Geometry::FGroupTopology* GroupTopology, TFunctionRef VertexFunc) { using namespace UE::Geometry; if (!ensure(Selection.ElementType == EGeometryElementType::Vertex)) { return false; } if (Selection.TopologyType == EGeometryTopologyType::Polygroup) { if (!ensure(GroupTopology)) { return false; } for (uint64 EncodedID : Selection.Selection) { int32 VertexID = (int32)FGeoSelectionID(EncodedID).GeometryID; if (Mesh.IsVertex(VertexID)) { VertexFunc(VertexID); } } } else if (Selection.TopologyType == EGeometryTopologyType::Triangle) { for (uint64 VertexID : Selection.Selection) { if (Mesh.IsVertex((int32)VertexID)) { VertexFunc((int32)VertexID); } } } else { return ensure(false); } return true; } }//end GeometrySelectionUtilLocals bool UE::Geometry::AreSelectionsIdentical( const FGeometrySelection& SelectionA, const FGeometrySelection& SelectionB) { if ((SelectionA.ElementType != SelectionB.ElementType) || (SelectionA.TopologyType != SelectionB.TopologyType)) { return false; } int32 Num = SelectionA.Num(); if (Num != SelectionB.Num()) { return false; } if (SelectionA.TopologyType == EGeometryTopologyType::Polygroup) { // for polygroup topology we may have stored an arbitrary geometry ID and so we cannot rely on TSet Contains for (uint64 ItemA : SelectionA.Selection) { uint32 TopologyID = FGeoSelectionID(ItemA).TopologyID; const uint64* Found = Algo::FindByPredicate(SelectionB.Selection, [&](uint64 Item) { return FGeoSelectionID(Item).TopologyID == TopologyID; }); if (Found == nullptr) { return false; } } } else { for (uint64 ItemA : SelectionA.Selection) { if (SelectionB.Selection.Contains(ItemA) == false) { return false; } } } return true; } bool UE::Geometry::FindInSelectionByTopologyID( const FGeometrySelection& GeometrySelection, uint32 TopologyID, uint64& FoundValue) { const uint64* Found = Algo::FindByPredicate(GeometrySelection.Selection, [&](uint64 Item) { return FGeoSelectionID(Item).TopologyID == TopologyID; }); if (Found != nullptr) { FoundValue = *Found; return true; } FoundValue = FGeoSelectionID().Encoded(); return false; } void UE::Geometry::UpdateTriangleSelectionViaRaycast( const FColliderMesh* ColliderMesh, FGeometrySelectionEditor* Editor, const FRay3d& LocalRay, const FGeometrySelectionUpdateConfig& UpdateConfig, FGeometrySelectionUpdateResult& ResultOut ) { check(Editor->GetTopologyType() == EGeometryTopologyType::Triangle); ResultOut.bSelectionMissed = true; double RayHitT; int32 HitTriangleID; FVector3d HitBaryCoords; if (ColliderMesh->FindNearestHitTriangle(LocalRay, RayHitT, HitTriangleID, HitBaryCoords)) { HitTriangleID = ColliderMesh->GetSourceTriangleID(HitTriangleID); if (HitTriangleID == IndexConstants::InvalidID) { return; } if (Editor->GetElementType() == EGeometryElementType::Face) { ResultOut.bSelectionModified = UpdateSelectionWithNewElements(Editor, UpdateConfig.ChangeType, TArray{(uint64)HitTriangleID}, &ResultOut.SelectionDelta); ResultOut.bSelectionMissed = false; } else if (Editor->GetElementType() == EGeometryElementType::Vertex) { FVector3d HitPos = LocalRay.PointAt(RayHitT); FIndex3i TriVerts = ColliderMesh->GetTriangle(HitTriangleID); int32 NearestIdx = 0; double NearestDistSqr = DistanceSquared(ColliderMesh->GetVertex(TriVerts[0]), HitPos); for (int32 k = 1; k < 3; ++k) { double DistSqr = DistanceSquared(ColliderMesh->GetVertex(TriVerts[k]), HitPos); if (DistSqr < NearestDistSqr) { NearestDistSqr = DistSqr; NearestIdx = k; } } ResultOut.bSelectionModified = UpdateSelectionWithNewElements(Editor, UpdateConfig.ChangeType, TArray{(uint64)TriVerts[NearestIdx]}, &ResultOut.SelectionDelta); ResultOut.bSelectionMissed = false; } else if (Editor->GetElementType() == EGeometryElementType::Edge) { FVector3d HitPos = LocalRay.PointAt(RayHitT); FIndex3i TriVerts = ColliderMesh->GetTriangle(HitTriangleID); FVector3d Positions[3]; ColliderMesh->GetTriVertices(HitTriangleID, Positions[0], Positions[1], Positions[2]); int32 NearestIdx = 0; double NearestDistSqr = FSegment3d(Positions[0], Positions[1]).DistanceSquared(HitPos); for (int32 k = 1; k < 3; ++k) { double DistSqr = FSegment3d(Positions[k], Positions[(k+1)%3]).DistanceSquared(HitPos); if (DistSqr < NearestDistSqr) { NearestDistSqr = DistSqr; NearestIdx = k; } } FMeshTriEdgeID TriEdgeID(HitTriangleID, NearestIdx); ResultOut.bSelectionModified = UpdateSelectionWithNewElements(Editor, UpdateConfig.ChangeType, TArray{(uint64)TriEdgeID.Encoded()}, &ResultOut.SelectionDelta); ResultOut.bSelectionMissed = false; } } } void UE::Geometry::UpdateGroupSelectionViaRaycast( const FColliderMesh* ColliderMesh, const FGroupTopology* GroupTopology, FGeometrySelectionEditor* Editor, const FRay3d& LocalRay, const FGeometrySelectionUpdateConfig& UpdateConfig, FGeometrySelectionUpdateResult& ResultOut) { check(Editor->GetTopologyType() == EGeometryTopologyType::Polygroup); ResultOut.bSelectionMissed = true; double RayHitT; int32 HitTriangleID; FVector3d HitBaryCoords; if (ColliderMesh->FindNearestHitTriangle(LocalRay, RayHitT, HitTriangleID, HitBaryCoords)) { HitTriangleID = ColliderMesh->GetSourceTriangleID(HitTriangleID); if (HitTriangleID == IndexConstants::InvalidID) { return; } int32 GroupID = GroupTopology->GetGroupID(HitTriangleID); if (Editor->GetElementType() == EGeometryElementType::Face) { FGeoSelectionID GroupTriID((uint32)HitTriangleID, (uint32)GroupID); ResultOut.bSelectionModified = UpdateSelectionWithNewElements(Editor, UpdateConfig.ChangeType, TArray{GroupTriID.Encoded()}, &ResultOut.SelectionDelta); ResultOut.bSelectionMissed = false; } else if (Editor->GetElementType() == EGeometryElementType::Vertex) { FVector3d HitPos = LocalRay.PointAt(RayHitT); FIndex3i TriVerts = ColliderMesh->GetTriangle(HitTriangleID); int32 NearestIdx = -1; int32 NearestCornerID = IndexConstants::InvalidID; double NearestDistSqr = TNumericLimits::Max(); for (int32 k = 0; k < 3; ++k) { int32 FoundCornerID = GroupTopology->GetCornerIDFromVertexID(TriVerts[k]); if (FoundCornerID != IndexConstants::InvalidID) { double DistSqr = DistanceSquared(ColliderMesh->GetVertex(TriVerts[k]), HitPos); if (DistSqr < NearestDistSqr) { NearestDistSqr = DistSqr; NearestIdx = k; NearestCornerID = FoundCornerID; } } } if (NearestCornerID != IndexConstants::InvalidID) { // do we need a group here? int32 VertexID = TriVerts[NearestIdx]; FGeoSelectionID SelectionID(VertexID, NearestCornerID); ResultOut.bSelectionModified = UpdateSelectionWithNewElements(Editor, UpdateConfig.ChangeType, TArray{SelectionID.Encoded()}, &ResultOut.SelectionDelta); ResultOut.bSelectionMissed = false; } } else if (Editor->GetElementType() == EGeometryElementType::Edge) { FVector3d HitPos = LocalRay.PointAt(RayHitT); FIndex3i TriVerts = ColliderMesh->GetTriangle(HitTriangleID); FVector3d Positions[3]; ColliderMesh->GetTriVertices(HitTriangleID, Positions[0], Positions[1], Positions[2]); int32 NearestIdx = -1; double NearestDistSqr = TNumericLimits::Max(); for (int32 k = 0; k < 3; ++k) { if ( GroupTopology->IsGroupEdge(FMeshTriEdgeID(HitTriangleID, k), true) ) { double DistSqr = FSegment3d(Positions[k], Positions[(k + 1) % 3]).DistanceSquared(HitPos); if (DistSqr < NearestDistSqr) { NearestDistSqr = DistSqr; NearestIdx = k; } } } if ( NearestIdx >= 0 ) { // do we need a group here? FMeshTriEdgeID TriEdgeID(HitTriangleID, NearestIdx); int32 GroupEdgeID = GroupTopology->FindGroupEdgeID(TriEdgeID); checkSlow(GroupEdgeID >= 0); // should never fail... if (GroupEdgeID >= 0) { FGeoSelectionID SelectionID(TriEdgeID.Encoded(), GroupEdgeID); ResultOut.bSelectionModified = UpdateSelectionWithNewElements(Editor, UpdateConfig.ChangeType, TArray{SelectionID.Encoded()}, & ResultOut.SelectionDelta); ResultOut.bSelectionMissed = false; } } } } } bool UE::Geometry::UpdateSelectionWithNewElements( FGeometrySelectionEditor* Editor, EGeometrySelectionChangeType ChangeType, const TArray& NewIDs, FGeometrySelectionDelta* Delta) { FGeometrySelectionDelta LocalDelta; FGeometrySelectionDelta& UseDelta = (Delta != nullptr) ? (*Delta) : LocalDelta; if (ChangeType == EGeometrySelectionChangeType::Replace) { // [TODO] this could be optimized... Editor->ClearSelection(UseDelta); return Editor->Select(NewIDs, UseDelta); } else if (ChangeType == EGeometrySelectionChangeType::Add) { return Editor->Select(NewIDs, UseDelta); } else if (ChangeType == EGeometrySelectionChangeType::Remove) { return Editor->Deselect(NewIDs, UseDelta); } else { ensure(false); return false; } } bool UE::Geometry::EnumerateTriangleSelectionVertices( const FGeometrySelection& MeshSelection, const UE::Geometry::FDynamicMesh3& Mesh, const FTransform& ApplyTransform, TFunctionRef VertexFunc) { if ( ensure( MeshSelection.TopologyType == EGeometryTopologyType::Triangle ) == false ) { return false; } if (MeshSelection.ElementType == EGeometryElementType::Face) { for (uint64 TriangleID : MeshSelection.Selection) { if (Mesh.IsTriangle((int32)TriangleID)) { FIndex3i Triangle = Mesh.GetTriangle((int32)TriangleID); VertexFunc((uint64)Triangle.A, ApplyTransform.TransformPosition(Mesh.GetVertex(Triangle.A))); VertexFunc((uint64)Triangle.B, ApplyTransform.TransformPosition(Mesh.GetVertex(Triangle.B))); VertexFunc((uint64)Triangle.C, ApplyTransform.TransformPosition(Mesh.GetVertex(Triangle.C))); } } } else if (MeshSelection.ElementType == EGeometryElementType::Edge) { for (uint64 EncodedID : MeshSelection.Selection) { FMeshTriEdgeID TriEdgeID(FGeoSelectionID(EncodedID).GeometryID); int32 EdgeID = Mesh.IsTriangle(TriEdgeID.TriangleID) ? Mesh.GetTriEdge(TriEdgeID.TriangleID, TriEdgeID.TriEdgeIndex) : IndexConstants::InvalidID; if (Mesh.IsEdge(EdgeID)) { FIndex2i EdgeV = Mesh.GetEdgeV(EdgeID); VertexFunc((uint64)EdgeV.A, ApplyTransform.TransformPosition(Mesh.GetVertex(EdgeV.A))); VertexFunc((uint64)EdgeV.B, ApplyTransform.TransformPosition(Mesh.GetVertex(EdgeV.B))); } } } else if (MeshSelection.ElementType == EGeometryElementType::Vertex) { for (uint64 VertexID : MeshSelection.Selection) { if (Mesh.IsVertex((int32)VertexID)) { VertexFunc((uint64)VertexID, ApplyTransform.TransformPosition(Mesh.GetVertex((int32)VertexID))); } } } else { return false; } return true; } bool UE::Geometry::EnumeratePolygroupSelectionVertices( const FGeometrySelection& MeshSelection, const UE::Geometry::FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, const FTransform& ApplyTransform, TFunctionRef VertexFunc) { if ( ensure( MeshSelection.TopologyType == EGeometryTopologyType::Polygroup ) == false ) { return false; } if (MeshSelection.ElementType == EGeometryElementType::Face) { for (uint64 EncodedID : MeshSelection.Selection) { FGeoSelectionID GroupTriID(EncodedID); int32 SeedTriangleID = (int32)GroupTriID.GeometryID, GroupID = (int32)GroupTriID.TopologyID; if (Mesh.IsTriangle(SeedTriangleID)) { for (int32 TriangleID : GroupTopology->GetGroupFaces(GroupID)) { FIndex3i Triangle = Mesh.GetTriangle((int32)TriangleID); VertexFunc((uint64)Triangle.A, ApplyTransform.TransformPosition(Mesh.GetVertex(Triangle.A))); VertexFunc((uint64)Triangle.B, ApplyTransform.TransformPosition(Mesh.GetVertex(Triangle.B))); VertexFunc((uint64)Triangle.C, ApplyTransform.TransformPosition(Mesh.GetVertex(Triangle.C))); } } } } else if (MeshSelection.ElementType == EGeometryElementType::Edge) { for (uint64 EncodedID : MeshSelection.Selection) { FMeshTriEdgeID TriEdgeID( FGeoSelectionID(EncodedID).GeometryID ); int32 SeedEdgeID = Mesh.IsTriangle(TriEdgeID.TriangleID) ? Mesh.GetTriEdge(TriEdgeID.TriangleID, TriEdgeID.TriEdgeIndex) : IndexConstants::InvalidID; if (Mesh.IsEdge(SeedEdgeID)) { int32 GroupEdgeID = GroupTopology->FindGroupEdgeID(SeedEdgeID); for (int32 VertexID : GroupTopology->GetGroupEdgeVertices(GroupEdgeID)) { FVector3d V = Mesh.GetVertex(VertexID); VertexFunc((uint64)VertexID, ApplyTransform.TransformPosition(V)); } } } } else if (MeshSelection.ElementType == EGeometryElementType::Vertex) { for (uint64 EncodedID : MeshSelection.Selection) { int32 VertexID = (int32)FGeoSelectionID(EncodedID).GeometryID; if (Mesh.IsVertex(VertexID)) { VertexFunc((uint64)VertexID, ApplyTransform.TransformPosition(Mesh.GetVertex(VertexID))); } } } else { return false; } return true; } bool UE::Geometry::EnumerateSelectionTriangles( const FGeometrySelection& MeshSelection, const UE::Geometry::FDynamicMesh3& Mesh, TFunctionRef TriangleFunc, const UE::Geometry::FPolygroupSet* UseGroupSet ) { if (MeshSelection.TopologyType == EGeometryTopologyType::Triangle) { return EnumerateTriangleSelectionTriangles(MeshSelection, Mesh, TriangleFunc); } else if (MeshSelection.TopologyType == EGeometryTopologyType::Polygroup) { return EnumeratePolygroupSelectionTriangles(MeshSelection, Mesh, (UseGroupSet != nullptr) ? *UseGroupSet : FPolygroupSet(&Mesh), TriangleFunc); } return false; } bool UE::Geometry::EnumerateTriangleSelectionTriangles( const FGeometrySelection& MeshSelection, const UE::Geometry::FDynamicMesh3& Mesh, TFunctionRef TriangleFunc) { if ( ensure( MeshSelection.TopologyType == EGeometryTopologyType::Triangle ) == false ) { return false; } if (MeshSelection.ElementType == EGeometryElementType::Face) { for (uint64 TriangleID : MeshSelection.Selection) { if (Mesh.IsTriangle((int32)TriangleID)) { TriangleFunc((int32)TriangleID); } } } else if (MeshSelection.ElementType == EGeometryElementType::Edge) { for (uint64 EncodedID : MeshSelection.Selection) { FMeshTriEdgeID TriEdgeID( FGeoSelectionID(EncodedID).GeometryID ); int32 EdgeID = Mesh.IsTriangle(TriEdgeID.TriangleID) ? Mesh.GetTriEdge(TriEdgeID.TriangleID, TriEdgeID.TriEdgeIndex) : IndexConstants::InvalidID; Mesh.EnumerateEdgeTriangles(EdgeID, [&](int32 TriangleID) { TriangleFunc(TriangleID); }); } } else if (MeshSelection.ElementType == EGeometryElementType::Vertex) { for (uint64 VertexID : MeshSelection.Selection) { Mesh.EnumerateVertexTriangles((int32)VertexID, [&](int32 TriangleID) { TriangleFunc(TriangleID); }); } } else { return false; } return true; } bool UE::Geometry::EnumeratePolygroupSelectionTriangles( const FGeometrySelection& MeshSelection, const UE::Geometry::FDynamicMesh3& Mesh, const UE::Geometry::FPolygroupSet& GroupSet, TFunctionRef TriangleFunc ) { if ( ensure( MeshSelection.TopologyType == EGeometryTopologyType::Polygroup ) == false ) { return false; } TArray SeedGroups; TArray SeedTriangles; TSet UniqueSeedGroups; // TODO: the code below will not work correctly if the selection contains // multiple disconnected-components with the same GroupID. They will be // filtered out by the UniqueSeedGroups test. Seems like it will be necessary // to detect this case up-front and do something more expensive, like filtering // out duplicates inside the connected-components loop instead of up-front if (MeshSelection.ElementType == EGeometryElementType::Face) { for (uint64 EncodedID : MeshSelection.Selection) { FGeoSelectionID SelectionID(EncodedID); int32 SeedTriangleID = (int32)SelectionID.GeometryID; if (Mesh.IsTriangle(SeedTriangleID)) { int32 GroupID = GroupSet.GetGroup(SeedTriangleID); check(GroupID == (int32)SelectionID.TopologyID); // sanity-check that we are using the right group if ( GroupID >= 0 && UniqueSeedGroups.Contains(GroupID) == false) { UniqueSeedGroups.Add(GroupID); SeedGroups.Add(GroupID); SeedTriangles.Add(SeedTriangleID); } } } } else if (MeshSelection.ElementType == EGeometryElementType::Edge) { for (uint64 EncodedID : MeshSelection.Selection) { FMeshTriEdgeID TriEdgeID( FGeoSelectionID(EncodedID).GeometryID ); int32 SeedEdgeID = Mesh.IsTriangle(TriEdgeID.TriangleID) ? Mesh.GetTriEdge(TriEdgeID.TriangleID, TriEdgeID.TriEdgeIndex) : IndexConstants::InvalidID; if (Mesh.IsEdge(SeedEdgeID)) { Mesh.EnumerateEdgeTriangles(SeedEdgeID, [&](int32 TriangleID) { int32 GroupID = GroupSet.GetGroup(TriangleID); if (GroupID >= 0 && UniqueSeedGroups.Contains(GroupID) == false) { UniqueSeedGroups.Add(GroupID); SeedGroups.Add(GroupID); SeedTriangles.Add(TriangleID); } }); } } } else if (MeshSelection.ElementType == EGeometryElementType::Vertex) { for (uint64 EncodedID : MeshSelection.Selection) { int32 VertexID = (int32)FGeoSelectionID(EncodedID).GeometryID; if (Mesh.IsVertex(VertexID)) { Mesh.EnumerateVertexTriangles(VertexID, [&](int32 TriangleID) { int32 GroupID = GroupSet.GetGroup(TriangleID); if (GroupID >= 0 && UniqueSeedGroups.Contains(GroupID) == false) { UniqueSeedGroups.Add(GroupID); SeedGroups.Add(GroupID); SeedTriangles.Add(TriangleID); } }); } } } else { return false; } TSet TempROI; // if we could provide this as input we would not need a temporary roi... TArray QueueBuffer; int32 NumGroups = SeedGroups.Num(); for (int32 k = 0; k < NumGroups; ++k) { check(GroupSet.GetGroup(SeedTriangles[k]) == SeedGroups[k]); int32 GroupID = SeedGroups[k]; FMeshConnectedComponents::GrowToConnectedTriangles(&Mesh, TArray{SeedTriangles[k]}, TempROI, &QueueBuffer, [&](int32 T1, int32 T2) { return GroupSet.GetGroup(T2) == GroupID; }); for (int32 tid : TempROI) { TriangleFunc(tid); } } return true; } bool UE::Geometry::EnumerateTriangleSelectionElements( const FGeometrySelection& MeshSelection, const UE::Geometry::FDynamicMesh3& Mesh, TFunctionRef VertexFunc, TFunctionRef EdgeFunc, TFunctionRef TriangleFunc, const FTransform* ApplyTransform, bool bMapFacesToEdgeLoops ) { if ( ensure( MeshSelection.TopologyType == EGeometryTopologyType::Triangle ) == false ) { return false; } if (MeshSelection.ElementType == EGeometryElementType::Face) { for (uint64 EncodedID : MeshSelection.Selection) { int32 TriangleID = (int32)FGeoSelectionID(EncodedID).GeometryID; if (Mesh.IsTriangle(TriangleID)) { FVector3d A, B, C; Mesh.GetTriVertices((int32)TriangleID, A, B, C); if (ApplyTransform != nullptr) { A = ApplyTransform->TransformPosition(A); B = ApplyTransform->TransformPosition(B); C = ApplyTransform->TransformPosition(C); } if (bMapFacesToEdgeLoops) { FIndex3i Edges = Mesh.GetTriEdges((int32)TriangleID); EdgeFunc(Edges.A, FSegment3d(A, B)); EdgeFunc(Edges.B, FSegment3d(B, C)); EdgeFunc(Edges.C, FSegment3d(C, A)); } else { TriangleFunc(TriangleID, FTriangle3d(A, B, C)); } } } } else if (MeshSelection.ElementType == EGeometryElementType::Edge) { for (uint64 EncodedID : MeshSelection.Selection) { FMeshTriEdgeID TriEdgeID( FGeoSelectionID(EncodedID).GeometryID ); int32 EdgeID = Mesh.IsTriangle(TriEdgeID.TriangleID) ? Mesh.GetTriEdge(TriEdgeID.TriangleID, TriEdgeID.TriEdgeIndex) : IndexConstants::InvalidID; if (Mesh.IsEdge(EdgeID)) { FVector3d A, B; Mesh.GetEdgeV(EdgeID, A, B); if (ApplyTransform != nullptr) { A = ApplyTransform->TransformPosition(A); B = ApplyTransform->TransformPosition(B); } EdgeFunc(EdgeID, FSegment3d(A, B)); } } } else if (MeshSelection.ElementType == EGeometryElementType::Vertex) { for (uint64 EncodedID : MeshSelection.Selection) { int32 VertexID = (int32)FGeoSelectionID(EncodedID).GeometryID; if (Mesh.IsVertex(VertexID)) { FVector3d A = Mesh.GetVertex(VertexID); VertexFunc(VertexID, (ApplyTransform != nullptr) ? ApplyTransform->TransformPosition(A) : A); } } } else { return false; } return true; } bool UE::Geometry::EnumeratePolygroupSelectionElements( const FGeometrySelection& MeshSelection, const UE::Geometry::FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, TFunctionRef VertexFunc, TFunctionRef EdgeFunc, TFunctionRef TriangleFunc, const FTransform* ApplyTransform, bool bMapFacesToEdgeLoops ) { if ( ensure( MeshSelection.TopologyType == EGeometryTopologyType::Polygroup ) == false ) { return false; } auto ProcessGroupEdgeID = [GroupTopology, &Mesh, ApplyTransform, &EdgeFunc](int32 GroupEdgeID) { for (int32 EdgeID : GroupTopology->GetGroupEdgeEdges(GroupEdgeID)) { FVector3d A, B; Mesh.GetEdgeV(EdgeID, A, B); if (ApplyTransform != nullptr) { A = ApplyTransform->TransformPosition(A); B = ApplyTransform->TransformPosition(B); } EdgeFunc(EdgeID, FSegment3d(A, B)); } }; if (MeshSelection.ElementType == EGeometryElementType::Face) { if (bMapFacesToEdgeLoops) { TArray GroupEdgeIDs; for (uint64 EncodedID : MeshSelection.Selection) { int32 GroupID = FGeoSelectionID(EncodedID).TopologyID; if ( const FGroupTopology::FGroup* Group = GroupTopology->FindGroupByID(GroupID) ) { for (const FGroupTopology::FGroupBoundary& Boundary : Group->Boundaries) { for (int32 GroupEdgeID : Boundary.GroupEdges) { GroupEdgeIDs.AddUnique(GroupEdgeID); } } } } for (int32 GroupEdgeID : GroupEdgeIDs) { ProcessGroupEdgeID(GroupEdgeID); } } else { for (uint64 EncodedID : MeshSelection.Selection) { FGeoSelectionID SelectionID(EncodedID); int32 SeedTriangleID = (int32)SelectionID.GeometryID, GroupID = (int32)SelectionID.TopologyID; if (Mesh.IsTriangle(SeedTriangleID)) { for (int32 TriangleID : GroupTopology->GetGroupFaces(GroupID)) { FVector3d A, B, C; Mesh.GetTriVertices((int32)TriangleID, A, B, C); if (ApplyTransform != nullptr) { A = ApplyTransform->TransformPosition(A); B = ApplyTransform->TransformPosition(B); C = ApplyTransform->TransformPosition(C); } TriangleFunc(TriangleID, FTriangle3d(A, B, C)); } } } } } else if (MeshSelection.ElementType == EGeometryElementType::Edge) { for (uint64 EncodedID : MeshSelection.Selection) { FMeshTriEdgeID TriEdgeID( FGeoSelectionID(EncodedID).GeometryID ); int32 SeedEdgeID = Mesh.IsTriangle(TriEdgeID.TriangleID) ? Mesh.GetTriEdge(TriEdgeID.TriangleID, TriEdgeID.TriEdgeIndex) : IndexConstants::InvalidID; if (Mesh.IsEdge(SeedEdgeID)) { int32 GroupEdgeID = GroupTopology->FindGroupEdgeID(SeedEdgeID); ProcessGroupEdgeID(GroupEdgeID); } } } else if (MeshSelection.ElementType == EGeometryElementType::Vertex) { for (uint64 EncodedID : MeshSelection.Selection) { int32 VertexID = (int32)FGeoSelectionID(EncodedID).GeometryID; if (Mesh.IsVertex(VertexID)) { FVector3d A = Mesh.GetVertex(VertexID); VertexFunc(VertexID, (ApplyTransform != nullptr) ? ApplyTransform->TransformPosition(A) : A); } } } else { return false; } return true; } bool UE::Geometry::ConvertPolygroupSelectionToTopologySelection( const FGeometrySelection& MeshSelection, const UE::Geometry::FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, FGroupTopologySelection& TopologySelectionOut) { if ( ensure( MeshSelection.TopologyType == EGeometryTopologyType::Polygroup ) == false ) { return false; } if (MeshSelection.ElementType == EGeometryElementType::Face) { for (uint64 EncodedID : MeshSelection.Selection) { int32 GroupID = FGeoSelectionID(EncodedID).TopologyID; if (GroupTopology->FindGroupByID(GroupID) != nullptr) { TopologySelectionOut.SelectedGroupIDs.Add(GroupID); } } } else if (MeshSelection.ElementType == EGeometryElementType::Edge) { for (uint64 EncodedID : MeshSelection.Selection) { FMeshTriEdgeID MeshEdgeID( FGeoSelectionID(EncodedID).GeometryID ); if ( Mesh.IsTriangle((int32)MeshEdgeID.TriangleID) ) { int32 GroupEdgeID = GroupTopology->FindGroupEdgeID(MeshEdgeID); if (GroupEdgeID >= 0) { TopologySelectionOut.SelectedEdgeIDs.Add(GroupEdgeID); } } } } else if (MeshSelection.ElementType == EGeometryElementType::Vertex) { for (uint64 EncodedID : MeshSelection.Selection) { int32 VertexID = (int32)FGeoSelectionID(EncodedID).GeometryID; if ( Mesh.IsVertex(VertexID) ) { int32 CornerID = GroupTopology->GetCornerIDFromVertexID(VertexID); if (CornerID >= 0) { TopologySelectionOut.SelectedCornerIDs.Add(CornerID); } } } } else { return false; } return true; } bool UE::Geometry::InitializeSelectionFromTriangles( const UE::Geometry::FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, TArrayView Triangles, FGeometrySelection& SelectionOut) { // TODO Refactor this to use GetSelectionTypeAsIndex if (SelectionOut.TopologyType == EGeometryTopologyType::Triangle) { if (SelectionOut.ElementType == EGeometryElementType::Vertex) { for (int32 tid : Triangles) { if (Mesh.IsTriangle(tid)) { FIndex3i TriVertices = Mesh.GetTriangle(tid); SelectionOut.Selection.Add(FGeoSelectionID::MeshVertex(TriVertices.A).Encoded() ); SelectionOut.Selection.Add(FGeoSelectionID::MeshVertex(TriVertices.B).Encoded()); SelectionOut.Selection.Add(FGeoSelectionID::MeshVertex(TriVertices.C).Encoded()); } } } else if (SelectionOut.ElementType == EGeometryElementType::Edge) { for (int32 tid : Triangles) { if (Mesh.IsTriangle(tid)) { FIndex3i TriEdges = Mesh.GetTriEdges(tid); SelectionOut.Selection.Add(FGeoSelectionID::MeshEdge(Mesh.GetTriEdgeIDFromEdgeID(TriEdges.A)).Encoded()); SelectionOut.Selection.Add(FGeoSelectionID::MeshEdge(Mesh.GetTriEdgeIDFromEdgeID(TriEdges.B)).Encoded()); SelectionOut.Selection.Add(FGeoSelectionID::MeshEdge(Mesh.GetTriEdgeIDFromEdgeID(TriEdges.C)).Encoded()); } } } else if (SelectionOut.ElementType == EGeometryElementType::Face) { for (int32 tid : Triangles) { if (Mesh.IsTriangle(tid)) { SelectionOut.Selection.Add(FGeoSelectionID::MeshTriangle(tid).Encoded()); } } } else { return false; } return true; } else if (SelectionOut.TopologyType == EGeometryTopologyType::Polygroup) { if (!ensure(GroupTopology != nullptr)) { return false; } if (SelectionOut.ElementType == EGeometryElementType::Vertex) { FMeshVertexSelection VertSelection(&Mesh); VertSelection.SelectTriangleVertices(Triangles); for (int32 vid : VertSelection) { int32 CornerID = GroupTopology->GetCornerIDFromVertexID(vid); if (CornerID != IndexConstants::InvalidID) { const FGroupTopology::FCorner& Corner = GroupTopology->Corners[CornerID]; FGeoSelectionID ID = FGeoSelectionID(Corner.VertexID, CornerID); SelectionOut.Selection.Add(ID.Encoded()); } } } else if (SelectionOut.ElementType == EGeometryElementType::Edge) { FMeshEdgeSelection EdgeSelection(&Mesh); EdgeSelection.SelectTriangleEdges(Triangles); for (int32 eid : EdgeSelection) { int32 GroupEdgeID = GroupTopology->FindGroupEdgeID(eid); if (GroupEdgeID != IndexConstants::InvalidID) { const FGroupTopology::FGroupEdge& GroupEdge = GroupTopology->Edges[GroupEdgeID]; FMeshTriEdgeID MeshEdgeID = Mesh.GetTriEdgeIDFromEdgeID(GroupEdge.Span.Edges[0]); FGeoSelectionID ID = FGeoSelectionID(MeshEdgeID.Encoded(), GroupEdgeID); SelectionOut.Selection.Add(ID.Encoded()); } } } else if (SelectionOut.ElementType == EGeometryElementType::Face) { for (int32 tid : Triangles) { if (Mesh.IsTriangle(tid)) { int32 GroupID = GroupTopology->GetGroupID(tid); const FGroupTopology::FGroup* GroupFace = GroupTopology->FindGroupByID(GroupID); if ( GroupFace ) { FGeoSelectionID ID = FGeoSelectionID(GroupFace->Triangles[0], GroupFace->GroupID); SelectionOut.Selection.Add(ID.Encoded()); } } } } else { return false; } return true; } return false; } bool UE::Geometry::ConvertSelection( const UE::Geometry::FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, const FGeometrySelection& FromSelectionIn, FGeometrySelection& ToSelectionOut) { using namespace GeometrySelectionUtilLocals; const auto NotImplemented = []( const FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, const FGeometrySelection& FromSelectionIn, FGeometrySelection& ToSelectionOut) { return false; }; const auto FromTypeToSame = []( const FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, const FGeometrySelection& FromSelectionIn, FGeometrySelection& ToSelectionOut) { checkSlow(FromSelectionIn.IsSameType(ToSelectionOut)); ToSelectionOut.Selection = FromSelectionIn.Selection; return true; }; const auto FromTriFace = []( const FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, const FGeometrySelection& FromSelectionIn, FGeometrySelection& ToSelectionOut) { checkSlow(FromSelectionIn.TopologyType == EGeometryTopologyType::Triangle); checkSlow(FromSelectionIn.ElementType == EGeometryElementType::Face); TArray Triangles; for (uint64 ElemID : FromSelectionIn.Selection) { Triangles.Add( FGeoSelectionID(ElemID).GeometryID ); } return InitializeSelectionFromTriangles(Mesh, GroupTopology, Triangles, ToSelectionOut); }; const auto FromTriEdgeToTriVtx = []( const FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, const FGeometrySelection& FromSelectionIn, FGeometrySelection& ToSelectionOut) { checkSlow(FromSelectionIn.TopologyType == EGeometryTopologyType::Triangle); checkSlow(FromSelectionIn.ElementType == EGeometryElementType::Edge); checkSlow(ToSelectionOut.TopologyType == EGeometryTopologyType::Triangle); checkSlow(ToSelectionOut.ElementType == EGeometryElementType::Vertex); // TODO Add a function which gets the Vids only, to remove the matrix-vector multiplication we just ignore const FTransform Transform = FTransform::Identity; return EnumerateTriangleSelectionVertices(FromSelectionIn, Mesh, Transform, [&ToSelectionOut](uint64 Vid, const FVector3d& Unused) { ToSelectionOut.Selection.Add( FGeoSelectionID::MeshVertex((int32)Vid).Encoded() ); }); }; const auto FromPolyEdgeToTriVtx = []( const FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, const FGeometrySelection& FromSelectionIn, FGeometrySelection& ToSelectionOut) { checkSlow(FromSelectionIn.TopologyType == EGeometryTopologyType::Polygroup); checkSlow(FromSelectionIn.ElementType == EGeometryElementType::Edge); checkSlow(ToSelectionOut.TopologyType == EGeometryTopologyType::Triangle); checkSlow(ToSelectionOut.ElementType == EGeometryElementType::Vertex); // TODO Add a function which gets the Vids only, to remove the matrix-vector multiplication we just ignore const FTransform Transform = FTransform::Identity; return EnumeratePolygroupSelectionVertices(FromSelectionIn, Mesh, GroupTopology, Transform, [&ToSelectionOut](uint64 Vid, const FVector3d& Unused) { ToSelectionOut.Selection.Add( FGeoSelectionID::MeshVertex((int32)Vid).Encoded() ); }); }; const auto FromPolyVtxToTriVtx = []( const FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, const FGeometrySelection& FromSelectionIn, FGeometrySelection& ToSelectionOut) { checkSlow(FromSelectionIn.TopologyType == EGeometryTopologyType::Polygroup); checkSlow(FromSelectionIn.ElementType == EGeometryElementType::Vertex); checkSlow(ToSelectionOut.TopologyType == EGeometryTopologyType::Triangle); checkSlow(ToSelectionOut.ElementType == EGeometryElementType::Vertex); for (FGroupTopology::FCorner Corner : GroupTopology->Corners) { ToSelectionOut.Selection.Add( FGeoSelectionID::MeshVertex((int32)Corner.VertexID).Encoded() ); } return true; }; typedef bool (*ConvertSelectionFunc)( const FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, const FGeometrySelection& FromSelectionIn, FGeometrySelection& ToSelectionOut); constexpr ConvertSelectionFunc ConvertFuncs[6][6] = { {FromTypeToSame, NotImplemented, NotImplemented, NotImplemented, NotImplemented, NotImplemented}, {FromTriEdgeToTriVtx, FromTypeToSame, NotImplemented, NotImplemented, NotImplemented, NotImplemented}, {FromTriFace, FromTriFace, FromTypeToSame, FromTriFace, FromTriFace, FromTriFace }, {FromPolyVtxToTriVtx, NotImplemented, NotImplemented, FromTypeToSame, NotImplemented, NotImplemented}, {FromPolyEdgeToTriVtx, NotImplemented, NotImplemented, NotImplemented, FromTypeToSame, NotImplemented}, {NotImplemented, NotImplemented, NotImplemented, NotImplemented, NotImplemented, FromTypeToSame} }; const int FromIndex = GetSelectionTypeAsIndex(FromSelectionIn); const int ToIndex = GetSelectionTypeAsIndex(ToSelectionOut); return ConvertFuncs[FromIndex][ToIndex](Mesh, GroupTopology, FromSelectionIn, ToSelectionOut); } bool UE::Geometry::ConvertTriangleSelectionToOverlaySelection( const UE::Geometry::FDynamicMesh3& Mesh, const FGeometrySelection& MeshSelection, TSet& TrianglesOut, TSet& VerticesOut) { if (!ensure(MeshSelection.TopologyType == EGeometryTopologyType::Triangle)) { return false; } TrianglesOut.Reset(); VerticesOut.Reset(); if (MeshSelection.IsEmpty()) { return true; } if (MeshSelection.ElementType == EGeometryElementType::Face) { // In this case we get all the information by only visiting the triangles EnumerateTriangleSelectionTriangles(MeshSelection, Mesh, [&TrianglesOut, &VerticesOut, &Mesh](int32 ValidTid) { TrianglesOut.Add(ValidTid); const FIndex3i Verts = Mesh.GetTriangle(ValidTid); VerticesOut.Add(Verts.A); VerticesOut.Add(Verts.B); VerticesOut.Add(Verts.C); }); } else { EnumerateTriangleSelectionTriangles(MeshSelection, Mesh, [&TrianglesOut](int32 ValidTid) { TrianglesOut.Add(ValidTid); }); // TODO Add a function which gets the Vids only, to remove the matrix-vector multiplication we just ignore const FTransform Transform = FTransform::Identity; EnumerateTriangleSelectionVertices(MeshSelection, Mesh, Transform, [&VerticesOut](uint64 Vid, const FVector3d& Unused) { VerticesOut.Add((int)Vid); }); } return true; } bool UE::Geometry::MakeSelectAllSelection( const UE::Geometry::FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, TFunctionRef SelectionIDPredicate, FGeometrySelection& AllSelection) { if (AllSelection.TopologyType == EGeometryTopologyType::Triangle) { if (AllSelection.ElementType == EGeometryElementType::Vertex) { for (int32 vid : Mesh.VertexIndicesItr()) { FGeoSelectionID ID = FGeoSelectionID::MeshVertex(vid); if ( SelectionIDPredicate(ID) ) { AllSelection.Selection.Add(ID.Encoded()); } } } else if (AllSelection.ElementType == EGeometryElementType::Edge) { for (int32 eid : Mesh.EdgeIndicesItr()) { FGeoSelectionID ID = FGeoSelectionID::MeshEdge(Mesh.GetTriEdgeIDFromEdgeID(eid)); if ( SelectionIDPredicate(ID) ) { AllSelection.Selection.Add(ID.Encoded()); } } } else if (AllSelection.ElementType == EGeometryElementType::Face) { for (int32 tid : Mesh.TriangleIndicesItr()) { FGeoSelectionID ID = FGeoSelectionID::MeshTriangle(tid); if ( SelectionIDPredicate(ID) ) { AllSelection.Selection.Add(ID.Encoded()); } } } else { return false; } return true; } else if ( AllSelection.TopologyType == EGeometryTopologyType::Polygroup ) { if (!ensure(GroupTopology != nullptr)) { return false; } if (AllSelection.ElementType == EGeometryElementType::Vertex) { int32 NumCorners = GroupTopology->Corners.Num(); for ( int32 CornerID = 0; CornerID < NumCorners; ++CornerID) { const FGroupTopology::FCorner& Corner = GroupTopology->Corners[CornerID]; FGeoSelectionID ID = FGeoSelectionID(Corner.VertexID, CornerID); if ( SelectionIDPredicate(ID) ) { AllSelection.Selection.Add(ID.Encoded()); } } } else if (AllSelection.ElementType == EGeometryElementType::Edge) { int32 NumEdges = GroupTopology->Edges.Num(); for ( int32 EdgeID = 0; EdgeID < NumEdges; ++EdgeID) { const FGroupTopology::FGroupEdge& GroupEdge = GroupTopology->Edges[EdgeID]; FMeshTriEdgeID MeshEdgeID = Mesh.GetTriEdgeIDFromEdgeID(GroupEdge.Span.Edges[0]); FGeoSelectionID ID = FGeoSelectionID(MeshEdgeID.Encoded(), EdgeID); if ( SelectionIDPredicate(ID) ) { AllSelection.Selection.Add(ID.Encoded()); } } } else if (AllSelection.ElementType == EGeometryElementType::Face) { int32 NumFaces = GroupTopology->Groups.Num(); for ( int32 FaceID = 0; FaceID < NumFaces; ++FaceID) { const FGroupTopology::FGroup& GroupFace = GroupTopology->Groups[FaceID]; FGeoSelectionID ID = FGeoSelectionID(GroupFace.Triangles[0], GroupFace.GroupID); if ( SelectionIDPredicate(ID) ) { AllSelection.Selection.Add(ID.Encoded()); } } } else { return false; } return true; } return false; } bool UE::Geometry::MakeSelectAllConnectedSelection( const UE::Geometry::FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, const FGeometrySelection& ReferenceSelection, TFunctionRef SelectionIDPredicate, TFunctionRef IsConnectedPredicate, FGeometrySelection& AllConnectedSelection) { if ( ! ensure(ReferenceSelection.IsSameType(AllConnectedSelection)) ) return false; if (AllConnectedSelection.TopologyType == EGeometryTopologyType::Triangle) { TArray CurIndices; CurIndices.Reserve(ReferenceSelection.Num()); if (AllConnectedSelection.ElementType == EGeometryElementType::Vertex) { for (uint64 ElementID : ReferenceSelection.Selection) { CurIndices.Add( FGeoSelectionID(ElementID).GeometryID ); } TSet ConnectedVertices; FMeshConnectedComponents::GrowToConnectedVertices(Mesh, CurIndices, ConnectedVertices, nullptr, [&](int32 FromVertID, int32 ToVertID) { return SelectionIDPredicate(FGeoSelectionID::MeshVertex(ToVertID)) && IsConnectedPredicate( FGeoSelectionID::MeshVertex(FromVertID), FGeoSelectionID::MeshVertex(ToVertID) ); }); for (int32 vid : ConnectedVertices) { AllConnectedSelection.Selection.Add( FGeoSelectionID::MeshVertex(vid).Encoded() ); } } else if (AllConnectedSelection.ElementType == EGeometryElementType::Edge) { for (uint64 ElementID : ReferenceSelection.Selection) { FMeshTriEdgeID TriEdgeID( FGeoSelectionID(ElementID).GeometryID ); CurIndices.Add( Mesh.GetTriEdge(TriEdgeID.TriangleID, TriEdgeID.TriEdgeIndex) ); } TSet ConnectedEdges; FMeshConnectedComponents::GrowToConnectedEdges(Mesh, CurIndices, ConnectedEdges, nullptr, [&](int32 FromEdgeID, int32 ToEdgeID) { FMeshTriEdgeID ToTriEdgeID = Mesh.GetTriEdgeIDFromEdgeID(ToEdgeID), FromTriEdgeID = Mesh.GetTriEdgeIDFromEdgeID(FromEdgeID); return SelectionIDPredicate(FGeoSelectionID::MeshEdge(ToTriEdgeID)) && IsConnectedPredicate( FGeoSelectionID::MeshEdge(FromTriEdgeID), FGeoSelectionID::MeshEdge(ToTriEdgeID) ); }); for (int32 EdgeID : ConnectedEdges) { AllConnectedSelection.Selection.Add( FGeoSelectionID::MeshEdge(Mesh.GetTriEdgeIDFromEdgeID(EdgeID)).Encoded() ); } } else if (AllConnectedSelection.ElementType == EGeometryElementType::Face) { for (uint64 ElementID : ReferenceSelection.Selection) { CurIndices.Add(FGeoSelectionID(ElementID).GeometryID); } TSet ConnectedTriangles; FMeshConnectedComponents::GrowToConnectedTriangles(&Mesh, CurIndices, ConnectedTriangles, nullptr, [&](int32 FromTriID, int32 ToTriID) { return SelectionIDPredicate(FGeoSelectionID::MeshTriangle(ToTriID)) && IsConnectedPredicate( FGeoSelectionID::MeshTriangle(FromTriID), FGeoSelectionID::MeshTriangle(ToTriID) ); }); for (int32 tid : ConnectedTriangles) { AllConnectedSelection.Selection.Add( FGeoSelectionID::MeshTriangle(tid).Encoded() ); } } else { return false; } return true; } else if ( AllConnectedSelection.TopologyType == EGeometryTopologyType::Polygroup ) { if (!ensure(GroupTopology != nullptr)) { return false; } FGeometrySelectionEditor Editor; Editor.Initialize(&AllConnectedSelection, true); AllConnectedSelection = ReferenceSelection; TArray Queue; for (uint64 ID : ReferenceSelection.Selection) { Queue.Add( ID ); } if (AllConnectedSelection.ElementType == EGeometryElementType::Vertex) { TArray NbrCornerIDs; while (Queue.Num() > 0) { FGeoSelectionID CurCornerSelectionID = FGeoSelectionID(Queue.Pop(false)); const FGroupTopology::FCorner& Corner = GroupTopology->Corners[CurCornerSelectionID.TopologyID]; NbrCornerIDs.Reset(); GroupTopology->FindCornerNbrCorners(CurCornerSelectionID.TopologyID, NbrCornerIDs); for ( int32 NbrCornerID : NbrCornerIDs ) { FGeoSelectionID NbrCornerSelectionID( GroupTopology->Corners[NbrCornerID].VertexID, NbrCornerID ); if ( Editor.IsSelected(NbrCornerSelectionID.Encoded()) == false && SelectionIDPredicate(NbrCornerSelectionID) && IsConnectedPredicate(CurCornerSelectionID, NbrCornerSelectionID) ) { Queue.Add(NbrCornerSelectionID.Encoded()); Editor.Select(NbrCornerSelectionID.Encoded()); } } } } else if (AllConnectedSelection.ElementType == EGeometryElementType::Edge) { TArray NbrEdgeIDs; while (Queue.Num() > 0) { FGeoSelectionID CurEdgeSelectionID = FGeoSelectionID(Queue.Pop(false)); const FGroupTopology::FGroupEdge& Edge = GroupTopology->Edges[CurEdgeSelectionID.TopologyID]; NbrEdgeIDs.Reset(); GroupTopology->FindEdgeNbrEdges(CurEdgeSelectionID.TopologyID, NbrEdgeIDs); for ( int32 NbrEdgeID : NbrEdgeIDs ) { FMeshTriEdgeID MeshEdgeID = Mesh.GetTriEdgeIDFromEdgeID(GroupTopology->Edges[NbrEdgeID].Span.Edges[0]); FGeoSelectionID NbrEdgeSelectionID( MeshEdgeID.Encoded(), NbrEdgeID); if ( Editor.IsSelected(NbrEdgeSelectionID.Encoded()) == false && SelectionIDPredicate(NbrEdgeSelectionID) && IsConnectedPredicate(CurEdgeSelectionID, NbrEdgeSelectionID) ) { Queue.Add(NbrEdgeSelectionID.Encoded()); Editor.Select(NbrEdgeSelectionID.Encoded()); } } } } else if (AllConnectedSelection.ElementType == EGeometryElementType::Face) { TArray NbrGroupIDs; while (Queue.Num() > 0) { FGeoSelectionID CurGroupSelectionID = FGeoSelectionID(Queue.Pop(false)); NbrGroupIDs.Reset(); for ( int32 NbrGroupID : GroupTopology->GetGroupNbrGroups(CurGroupSelectionID.TopologyID) ) { const FGroupTopology::FGroup* NbrGroup = GroupTopology->FindGroupByID(NbrGroupID); FGeoSelectionID NbrGroupSelectionID( NbrGroup->Triangles[0], NbrGroupID); if ( Editor.IsSelected(NbrGroupSelectionID.Encoded()) == false && SelectionIDPredicate(NbrGroupSelectionID) && IsConnectedPredicate(CurGroupSelectionID, NbrGroupSelectionID) ) { Queue.Add(NbrGroupSelectionID.Encoded()); Editor.Select(NbrGroupSelectionID.Encoded()); } } } } else { return false; } return true; } return false; } bool UE::Geometry::GetSelectionBoundaryVertices( const FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, const FGeometrySelection& ReferenceSelection, TSet& BorderVidsOut, TSet& CurVerticesOut) { using namespace GeometrySelectionUtilLocals; BorderVidsOut.Reset(); CurVerticesOut.Reset(); switch (ReferenceSelection.ElementType) { case EGeometryElementType::Vertex: EnumerateVertexElementSelectionVertices(ReferenceSelection, Mesh, GroupTopology, [&CurVerticesOut](uint32 Vid) { CurVerticesOut.Add(Vid); }); // Border vertices are ones that have some adjacent vertices not in selection for (int32 VertexID : CurVerticesOut) { // a boundary vertex is always on the selection boundary (for this and other selection types) bool bIsBoundary = Mesh.IsBoundaryVertex(VertexID); if (!bIsBoundary) { Mesh.EnumerateVertexVertices(VertexID, [&](int32 NbrVertexID) { if (!CurVerticesOut.Contains(NbrVertexID)) { bIsBoundary = true; } }); } if (bIsBoundary) { BorderVidsOut.Add(VertexID); } } break; case EGeometryElementType::Edge: { // Border vertices are ones that have some adjacent edges that are not in selection, so // determine edges in selection. TSet EdgeIDsInSelection; EnumerateEdgeElementSelectionEdges(ReferenceSelection, Mesh, GroupTopology, [&EdgeIDsInSelection, &CurVerticesOut, &Mesh](uint32 Eid) { EdgeIDsInSelection.Add(Eid); FIndex2i EdgeV = Mesh.GetEdgeV(Eid); CurVerticesOut.Add(EdgeV.A); CurVerticesOut.Add(EdgeV.B); }); for (int32 VertexID : CurVerticesOut) { bool bIsBoundary = Mesh.IsBoundaryVertex(VertexID); if (!bIsBoundary) { Mesh.EnumerateVertexEdges(VertexID, [&EdgeIDsInSelection, &bIsBoundary](int32 EdgeID) { if (!EdgeIDsInSelection.Contains(EdgeID)) { bIsBoundary = true; } }); } if (bIsBoundary) { BorderVidsOut.Add(VertexID); } } } break; case EGeometryElementType::Face: { // Border vertices are ones that have some adjacent triangles that are not in selection. TSet TriangleIDsInSelection; EnumerateFaceElementSelectionTriangles(ReferenceSelection, Mesh, GroupTopology, [&TriangleIDsInSelection, &CurVerticesOut, &Mesh](uint32 Tid) { TriangleIDsInSelection.Add(Tid); FIndex3i Triangle = Mesh.GetTriangle(Tid); CurVerticesOut.Add(Triangle.A); CurVerticesOut.Add(Triangle.B); CurVerticesOut.Add(Triangle.C); }); for (int32 VertexID : CurVerticesOut) { bool bIsBoundary = Mesh.IsBoundaryVertex(VertexID); if (!bIsBoundary) { Mesh.EnumerateVertexTriangles(VertexID, [&TriangleIDsInSelection, &bIsBoundary](int32 TriangleID) { if (!TriangleIDsInSelection.Contains(TriangleID)) { bIsBoundary = true; } }); } if (bIsBoundary) { BorderVidsOut.Add(VertexID); } } } break; default: return ensure(false); } return true; } bool UE::Geometry::GetSelectionBoundaryCorners( const FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, const FGeometrySelection& ReferenceSelection, TSet& BorderCornerIDsOut, TSet& CurCornerIDsOut) { BorderCornerIDsOut.Reset(); CurCornerIDsOut.Reset(); if (!ensure(GroupTopology)) { return false; } if (!ensure(ReferenceSelection.TopologyType == EGeometryTopologyType::Polygroup)) { // We don't currently support triangle selections here in part because it's not clear what to do. The // proper thing is likely to convert to an equivalent polygroup selection and find border corners, but // we haven't yet defined some of those conversions. Alternatively we could find the border vertices and // keep whichever ones happen to be corners, but that gives the unintuitive result of not giving any corners // for selections that don't happen to line up with group boundaries. // There's also the fact that we don't yet have a use case for supporting this here. return false; } TArray NbrArray; switch (ReferenceSelection.ElementType) { case EGeometryElementType::Vertex: { // Assemble included corners for (uint64 ID : ReferenceSelection.Selection) { CurCornerIDsOut.Add(FGeoSelectionID(ID).TopologyID); // TODO: can we rely on TopologyID being stable here, or do we need to look up from VertexID? } // Border corners are ones that have a corner neighbor not in the selection for (int32 CornerID : CurCornerIDsOut) { // Boundary vertex corners are always considered to be on selection boundary (for this and other selection types) bool bIsBoundary = Mesh.IsBoundaryVertex(GroupTopology->GetCornerVertexID(CornerID)); if (!bIsBoundary) { NbrArray.Reset(); GroupTopology->FindCornerNbrCorners(CornerID, NbrArray); for (int32 NbrCornerID : NbrArray) { if (!CurCornerIDsOut.Contains(NbrCornerID)) { bIsBoundary = true; break; } } } if (bIsBoundary) { BorderCornerIDsOut.Add(CornerID); } } } break; case EGeometryElementType::Edge: { // Assemble the current group edge selection and the included corners. TSet GroupEdgeIDsInSelection; for (uint64 ID : ReferenceSelection.Selection) { int32 GroupEdgeID = FGeoSelectionID(ID).TopologyID; GroupEdgeIDsInSelection.Add(GroupEdgeID); const FGroupTopology::FGroupEdge& Edge = GroupTopology->Edges[GroupEdgeID]; if (Edge.EndpointCorners.A != IndexConstants::InvalidID) { CurCornerIDsOut.Add(Edge.EndpointCorners.A); } if (Edge.EndpointCorners.B != IndexConstants::InvalidID) { CurCornerIDsOut.Add(Edge.EndpointCorners.B); } } // Border corners are ones that have some attached group edges that are not in the current selection. for (int32 CornerID : CurCornerIDsOut) { bool bIsBoundary = Mesh.IsBoundaryVertex(GroupTopology->GetCornerVertexID(CornerID)); if (!bIsBoundary) { NbrArray.Reset(); GroupTopology->FindCornerNbrEdges(CornerID, NbrArray); for (int32 GroupEdgeID : NbrArray) { if (!GroupEdgeIDsInSelection.Contains(GroupEdgeID)) { bIsBoundary = true; break; } } } if (bIsBoundary) { BorderCornerIDsOut.Add(CornerID); } } } break; case EGeometryElementType::Face: { // Assemble current group selection and the included corners TSet GroupsInSelection; for (uint64 ID : ReferenceSelection.Selection) { int32 GroupID = FGeoSelectionID(ID).TopologyID; GroupsInSelection.Add(GroupID); GroupTopology->ForGroupEdges(GroupID, [&](const FGroupTopology::FGroupEdge& Edge, int) { if (Edge.EndpointCorners.A != IndexConstants::InvalidID) { CurCornerIDsOut.Add(Edge.EndpointCorners.A); } if (Edge.EndpointCorners.B != IndexConstants::InvalidID) { CurCornerIDsOut.Add(Edge.EndpointCorners.B); } }); } // Boundary corners are ones that have an attached group not in selection for (int32 CornerID : CurCornerIDsOut) { bool bIsBoundary = Mesh.IsBoundaryVertex(GroupTopology->GetCornerVertexID(CornerID)); if (!bIsBoundary) { NbrArray.Reset(); GroupTopology->FindCornerNbrGroups(CornerID, NbrArray); for (int32 Group : NbrArray) { if (!GroupsInSelection.Contains(Group)) { bIsBoundary = true; break; } } } if (bIsBoundary) { BorderCornerIDsOut.Add(CornerID); } } } break; default: return ensure(false); } return true; } bool UE::Geometry::MakeBoundaryConnectedSelection( const UE::Geometry::FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, const FGeometrySelection& ReferenceSelection, TFunctionRef SelectionIDPredicate, FGeometrySelection& BoundaryConnectedSelection) { using namespace GeometrySelectionUtilLocals; if (BoundaryConnectedSelection.TopologyType == EGeometryTopologyType::Triangle) { TSet BorderVertices; TSet CurVertices; if (!GetSelectionBoundaryVertices(Mesh, GroupTopology, ReferenceSelection, BorderVertices, CurVertices)) { return false; } // Now select elements connected to the border vertices. if (BoundaryConnectedSelection.ElementType == EGeometryElementType::Vertex) { TSet AdjacentVertices = BorderVertices; for (int32 VertexID : BorderVertices) { Mesh.EnumerateVertexVertices(VertexID, [&](int32 NbrVertexID) { // filter out interior vertices, maybe should be a parameter if ( CurVertices.Contains(NbrVertexID) == false ) { AdjacentVertices.Add(NbrVertexID); } }); } for (int32 VertexID : AdjacentVertices) { if (SelectionIDPredicate(FGeoSelectionID::MeshVertex(VertexID))) { BoundaryConnectedSelection.Selection.Add(FGeoSelectionID::MeshVertex(VertexID).Encoded()); } } } else if (BoundaryConnectedSelection.ElementType == EGeometryElementType::Edge) { TSet AdjacentEdges; for (int32 VertexID : BorderVertices) { for ( int32 EdgeID : Mesh.VtxEdgesItr(VertexID) ) { AdjacentEdges.Add(EdgeID); } } for (int32 EdgeID : AdjacentEdges) { FGeoSelectionID MeshEdgeID = FGeoSelectionID::MeshEdge(Mesh.GetTriEdgeIDFromEdgeID(EdgeID)); if (SelectionIDPredicate(MeshEdgeID)) { BoundaryConnectedSelection.Selection.Add(MeshEdgeID.Encoded()); } } } else if (BoundaryConnectedSelection.ElementType == EGeometryElementType::Face) { TSet AdjacentTriangles; for (int32 VertexID : BorderVertices) { Mesh.EnumerateVertexTriangles(VertexID, [&](int32 NbrTriangleID) { AdjacentTriangles.Add(NbrTriangleID); }); } for (int32 TriangleID : AdjacentTriangles) { if (SelectionIDPredicate(FGeoSelectionID::MeshTriangle(TriangleID))) { BoundaryConnectedSelection.Selection.Add(FGeoSelectionID::MeshTriangle(TriangleID).Encoded()); } } } else { return false; } return true; } else if ( BoundaryConnectedSelection.TopologyType == EGeometryTopologyType::Polygroup ) { if (!ensure(GroupTopology != nullptr)) { return false; } TSet CurCornerIDs; TSet BorderCorners; if (!GetSelectionBoundaryCorners(Mesh, GroupTopology, ReferenceSelection, BorderCorners, CurCornerIDs)) { return false; } TArray NbrArray; // now that we have boundary corners, iterate over them and select connected elements if (BoundaryConnectedSelection.ElementType == EGeometryElementType::Vertex) { TSet AdjacentCorners = BorderCorners; for (int32 CornerID : BorderCorners) { NbrArray.Reset(); GroupTopology->FindCornerNbrCorners(CornerID, NbrArray); for (int32 NbrCornerID : NbrArray) { // filter out interior corners, maybe should be a parameter if ( CurCornerIDs.Contains(NbrCornerID) == false ) { AdjacentCorners.Add(NbrCornerID); } } } for (int32 CornerID : AdjacentCorners) { FGeoSelectionID SelectionID( GroupTopology->GetCornerVertexID(CornerID), CornerID); if (SelectionIDPredicate(SelectionID) ) { BoundaryConnectedSelection.Selection.Add( SelectionID.Encoded() ); } } } else if (BoundaryConnectedSelection.ElementType == EGeometryElementType::Edge) { TSet AdjacentEdges; for (int32 CornerID : BorderCorners) { NbrArray.Reset(); GroupTopology->FindCornerNbrEdges(CornerID, NbrArray); for (int32 NbrEdgeID : NbrArray) { AdjacentEdges.Add(NbrEdgeID); } } for (int32 EdgeID : AdjacentEdges) { FMeshTriEdgeID MeshEdgeID = Mesh.GetTriEdgeIDFromEdgeID(GroupTopology->GetGroupEdgeEdges(EdgeID)[0]); FGeoSelectionID SelectionID( MeshEdgeID.Encoded(), EdgeID); if (SelectionIDPredicate(SelectionID) ) { BoundaryConnectedSelection.Selection.Add( SelectionID.Encoded() ); } } } else // already verified we are only vertex/edge/face above { TSet AdjacentGroups; for (int32 CornerID : BorderCorners) { NbrArray.Reset(); GroupTopology->FindCornerNbrGroups(CornerID, NbrArray); for (int32 NbrGroupID : NbrArray) { AdjacentGroups.Add(NbrGroupID); } } for (int32 GroupID : AdjacentGroups) { FGeoSelectionID SelectionID( GroupTopology->GetGroupTriangles(GroupID)[0], GroupID); if (SelectionIDPredicate(SelectionID) ) { BoundaryConnectedSelection.Selection.Add( SelectionID.Encoded() ); } } } return true; } return false; } bool UE::Geometry::CombineSelectionInPlace( FGeometrySelection& SelectionA, const FGeometrySelection& SelectionB, EGeometrySelectionCombineModes CombineMode) { if (SelectionA.IsSameType(SelectionB) == false) { return false; } if (SelectionA.TopologyType == EGeometryTopologyType::Triangle) { if (CombineMode == EGeometrySelectionCombineModes::Add) { for (uint64 ItemB : SelectionB.Selection) { SelectionA.Selection.Add(ItemB); } } else if (CombineMode == EGeometrySelectionCombineModes::Subtract) { if (SelectionB.IsEmpty() == false) { for (uint64 ItemB : SelectionB.Selection) { SelectionA.Selection.Remove(ItemB); } SelectionA.Selection.Compact(); } } else if (CombineMode == EGeometrySelectionCombineModes::Intersection) { TArray> ToRemove; for (uint64 ItemA : SelectionA.Selection) { if (!SelectionB.Selection.Contains(ItemA)) { ToRemove.Add(ItemA); } } if (ToRemove.Num() > 0) { for (uint64 ItemA : ToRemove) { SelectionA.Selection.Remove(ItemA); } SelectionA.Selection.Compact(); } } return true; } else if (SelectionA.TopologyType == EGeometryTopologyType::Polygroup) { // for Polygroup selections, we cannot rely on TSet operations because we have set an arbitrary Triangle ID // as the 'geometry' key. if (CombineMode == EGeometrySelectionCombineModes::Add) { for (uint64 ItemB : SelectionB.Selection) { uint64 FoundItemA; if ( UE::Geometry::FindInSelectionByTopologyID(SelectionA, FGeoSelectionID(ItemB).TopologyID, FoundItemA) == false) { SelectionA.Selection.Add(ItemB); } } } else if (CombineMode == EGeometrySelectionCombineModes::Subtract) { if (SelectionB.IsEmpty() == false) { for (uint64 ItemB : SelectionB.Selection) { uint64 FoundItemA; if (UE::Geometry::FindInSelectionByTopologyID(SelectionA, FGeoSelectionID(ItemB).TopologyID, FoundItemA)) { SelectionA.Selection.Remove(FoundItemA); } } SelectionA.Selection.Compact(); } } else if (CombineMode == EGeometrySelectionCombineModes::Intersection) { TArray> ToRemove; for (uint64 ItemA : SelectionA.Selection) { uint64 FoundItemB; if (UE::Geometry::FindInSelectionByTopologyID(SelectionA, FGeoSelectionID(ItemA).TopologyID, FoundItemB) == false) { ToRemove.Add(ItemA); } } if (ToRemove.Num() > 0) { for (uint64 ItemA : ToRemove) { SelectionA.Selection.Remove(ItemA); } SelectionA.Selection.Compact(); } } return true; } return false; } bool UE::Geometry::GetTriangleSelectionFrame( const FGeometrySelection& MeshSelection, const UE::Geometry::FDynamicMesh3& Mesh, FFrame3d& SelectionFrameOut) { if ( ensure( MeshSelection.TopologyType == EGeometryTopologyType::Triangle ) == false ) { return false; } FVector3d AccumulatedOrigin = FVector3d::Zero(); FVector3d AccumulatedNormal = FVector3d::Zero(); FVector3d AxisHint = FVector3d::Zero(); double AccumWeight = 0; if (MeshSelection.ElementType == EGeometryElementType::Face) { for (uint64 EncodedID : MeshSelection.Selection) { int32 TriangleID = (int32)FGeoSelectionID(EncodedID).GeometryID; if (Mesh.IsTriangle(TriangleID)) { FVector3d Normal, Centroid; double Area; Mesh.GetTriInfo(TriangleID, Normal, Area, Centroid); if (Normal.SquaredLength() > 0.9) { Area = FMath::Max(Area, 0.000001); AccumulatedOrigin += Area * Centroid; AccumulatedNormal += Area * Normal; AccumWeight += Area; } } } } else if (MeshSelection.ElementType == EGeometryElementType::Edge) { for (uint64 EncodedID : MeshSelection.Selection) { FMeshTriEdgeID TriEdgeID( FGeoSelectionID(EncodedID).GeometryID ); int32 EdgeID = Mesh.IsTriangle(TriEdgeID.TriangleID) ? Mesh.GetTriEdge(TriEdgeID.TriangleID, TriEdgeID.TriEdgeIndex) : IndexConstants::InvalidID; if ( Mesh.IsEdge(EdgeID) ) { FVector3d A, B; Mesh.GetEdgeV(EdgeID, A, B); AccumulatedOrigin += (A + B) * 0.5; AccumulatedNormal += Mesh.GetEdgeNormal(EdgeID); AxisHint += Normalized(B - A); AccumWeight += 1.0; } } } else if (MeshSelection.ElementType == EGeometryElementType::Vertex) { for (uint64 EncodedID : MeshSelection.Selection) { int32 VertexID = (int32)FGeoSelectionID(EncodedID).GeometryID; if (Mesh.IsVertex(VertexID)) { AccumulatedOrigin += Mesh.GetVertex(VertexID); AccumulatedNormal += FMeshNormals::ComputeVertexNormal(Mesh, VertexID); // this could return area! AccumWeight += 1.0; } } } else { return false; } // todo use AxisHint! SelectionFrameOut = FFrame3d(); if (AccumWeight > 0) { AccumulatedOrigin /= (double)AccumWeight; Normalize(AccumulatedNormal); // We set our frame Z to be accumulated normal, and the other two axes are unconstrained, so // we want to set them to something that will make our frame generally more useful. If the normal // is aligned with world Z, then the entire frame might as well be aligned with world. if (1 - AccumulatedNormal.Dot(FVector3d::UnitZ()) < KINDA_SMALL_NUMBER) { SelectionFrameOut = FFrame3d(AccumulatedOrigin, FQuaterniond::Identity()); } else { // Otherwise, let's place one of the other axes into the XY plane so that the frame is more // useful for translation. We somewhat arbitrarily choose Y for this. FVector3d FrameY = Normalized(AccumulatedNormal.Cross(FVector3d::UnitZ())); // orthogonal to world Z and frame Z FVector3d FrameX = FrameY.Cross(AccumulatedNormal); // safe to not normalize because already orthogonal SelectionFrameOut = FFrame3d(AccumulatedOrigin, FrameX, FrameY, AccumulatedNormal); } } return true; }