// 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; } /** * 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 ) { ensure(Editor->GetTopologyType() == EGeometryTopologyType::Triangle); ResultOut.bSelectionMissed = true; IMeshSpatial::FQueryOptions SpatialQueryOptions; if (!Editor->GetQueryConfig().bHitBackFaces) { SpatialQueryOptions.TriangleFilterF = [&](int32 Tid) { return ColliderMesh->GetTriNormal(Tid).Dot(LocalRay.Direction) < 0; }; } double RayHitT; int32 HitTriangleID; FVector3d HitBaryCoords; if (ColliderMesh->FindNearestHitTriangle(LocalRay, RayHitT, HitTriangleID, HitBaryCoords, SpatialQueryOptions)) { 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) { ensure(Editor->GetTopologyType() == EGeometryTopologyType::Polygroup); ResultOut.bSelectionMissed = true; IMeshSpatial::FQueryOptions SpatialQueryOptions; if (!Editor->GetQueryConfig().bHitBackFaces) { SpatialQueryOptions.TriangleFilterF = [&](int32 Tid) { return ColliderMesh->GetTriNormal(Tid).Dot(LocalRay.Direction) < 0; }; } double RayHitT; int32 HitTriangleID; FVector3d HitBaryCoords; if (ColliderMesh->FindNearestHitTriangle(LocalRay, RayHitT, HitTriangleID, HitBaryCoords, SpatialQueryOptions)) { 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) { return EnumerateTriangleSelectionVertices(MeshSelection, Mesh, &ApplyTransform, VertexFunc); } 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 (const uint64 TriangleID : MeshSelection.Selection) { if (Mesh.IsTriangle((int32)TriangleID)) { const FIndex3i Triangle = Mesh.GetTriangle((int32)TriangleID); FVector3d TriVertexA, TriVertexB, TriVertexC; TriVertexA = Mesh.GetVertex(Triangle.A); TriVertexB = Mesh.GetVertex(Triangle.B); TriVertexC = Mesh.GetVertex(Triangle.C); if (ApplyTransform) { TriVertexA = ApplyTransform->TransformPosition(TriVertexA); TriVertexB = ApplyTransform->TransformPosition(TriVertexB); TriVertexC = ApplyTransform->TransformPosition(TriVertexC); } VertexFunc((uint64)Triangle.A, TriVertexA); VertexFunc((uint64)Triangle.B, TriVertexB); VertexFunc((uint64)Triangle.C, TriVertexC); } } } else if (MeshSelection.ElementType == EGeometryElementType::Edge) { for (const uint64 EncodedID : MeshSelection.Selection) { const FMeshTriEdgeID TriEdgeID(FGeoSelectionID(EncodedID).GeometryID); const int32 EdgeID = Mesh.IsTriangle(TriEdgeID.TriangleID) ? Mesh.GetTriEdge(TriEdgeID.TriangleID, TriEdgeID.TriEdgeIndex) : IndexConstants::InvalidID; if (Mesh.IsEdge(EdgeID)) { const FIndex2i EdgeV = Mesh.GetEdgeV(EdgeID); FVector3d EdgeVertexA, EdgeVertexB; EdgeVertexA = Mesh.GetVertex(EdgeV.A); EdgeVertexB = Mesh.GetVertex(EdgeV.B); if (ApplyTransform) { EdgeVertexA = ApplyTransform->TransformPosition(EdgeVertexA); EdgeVertexB = ApplyTransform->TransformPosition(EdgeVertexB); } VertexFunc((uint64)EdgeV.A, EdgeVertexA); VertexFunc((uint64)EdgeV.B, EdgeVertexB); } } } else if (MeshSelection.ElementType == EGeometryElementType::Vertex) { for (const uint64 VertexID : MeshSelection.Selection) { if (Mesh.IsVertex((int32)VertexID)) { FVector3d Vertex = Mesh.GetVertex((int32)VertexID); if (ApplyTransform) { Vertex = ApplyTransform->TransformPosition(Vertex); } VertexFunc(VertexID, Vertex); } } } 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 (const uint64 EncodedID : MeshSelection.Selection) { const FGeoSelectionID GroupTriID(EncodedID); const int32 SeedTriangleID = (int32)GroupTriID.GeometryID, GroupID = (int32)GroupTriID.TopologyID; if (Mesh.IsTriangle(SeedTriangleID)) { for (const int32 TriangleID : GroupTopology->GetGroupFaces(GroupID)) { const 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 (const uint64 EncodedID : MeshSelection.Selection) { const FMeshTriEdgeID TriEdgeID( FGeoSelectionID(EncodedID).GeometryID ); const int32 SeedEdgeID = Mesh.IsTriangle(TriEdgeID.TriangleID) ? Mesh.GetTriEdge(TriEdgeID.TriangleID, TriEdgeID.TriEdgeIndex) : IndexConstants::InvalidID; if (Mesh.IsEdge(SeedEdgeID)) { const int32 GroupEdgeID = GroupTopology->FindGroupEdgeID(SeedEdgeID); for (const int32 VertexID : GroupTopology->GetGroupEdgeVertices(GroupEdgeID)) { FVector3d V = Mesh.GetVertex(VertexID); VertexFunc((uint64)VertexID, ApplyTransform.TransformPosition(V)); } } } } else if (MeshSelection.ElementType == EGeometryElementType::Vertex) { for (const uint64 EncodedID : MeshSelection.Selection) { const 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 (const uint64 TriangleID : MeshSelection.Selection) { if (Mesh.IsTriangle((int32)TriangleID)) { TriangleFunc((int32)TriangleID); } } } else if (MeshSelection.ElementType == EGeometryElementType::Edge) { for (const uint64 EncodedID : MeshSelection.Selection) { const FMeshTriEdgeID TriEdgeID( FGeoSelectionID(EncodedID).GeometryID ); const int32 EdgeID = Mesh.IsTriangle(TriEdgeID.TriangleID) ? Mesh.GetTriEdge(TriEdgeID.TriangleID, TriEdgeID.TriEdgeIndex) : IndexConstants::InvalidID; Mesh.EnumerateEdgeTriangles(EdgeID, [&TriangleFunc](const int32 TriangleID) { TriangleFunc(TriangleID); }); } } else if (MeshSelection.ElementType == EGeometryElementType::Vertex) { for (const uint64 VertexID : MeshSelection.Selection) { Mesh.EnumerateVertexTriangles((int32)VertexID, [&TriangleFunc](const 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 (const uint64 EncodedID : MeshSelection.Selection) { const FGeoSelectionID SelectionID(EncodedID); const int32 SeedTriangleID = (int32)SelectionID.GeometryID; if (Mesh.IsTriangle(SeedTriangleID)) { const int32 GroupID = GroupSet.GetGroup(SeedTriangleID); // TODO: [TopologyMismatch] We don't have the ensure below because the selection system currently can have a // different view of a converted target than a tool might after making some edits, and the group ID's can // differ. For instance, a modified volume dynamic mesh may differ from the dynamic mesh that would result // from converting to a volume and back to dynamic mesh again. Someday we may fix this, but for now we accept // that the selection system and tools may disagree, and tools may end up setting incorrect selections after // doing some edits. // ensure(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 (const uint64 EncodedID : MeshSelection.Selection) { const FMeshTriEdgeID TriEdgeID( FGeoSelectionID(EncodedID).GeometryID ); const int32 SeedEdgeID = Mesh.IsTriangle(TriEdgeID.TriangleID) ? Mesh.GetTriEdge(TriEdgeID.TriangleID, TriEdgeID.TriEdgeIndex) : IndexConstants::InvalidID; Mesh.EnumerateEdgeTriangles(SeedEdgeID, [&GroupSet, &UniqueSeedGroups, &SeedGroups, &SeedTriangles](const int32 TriangleID) { const 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 (const uint64 EncodedID : MeshSelection.Selection) { const int32 VertexID = (int32)FGeoSelectionID(EncodedID).GeometryID; Mesh.EnumerateVertexTriangles(VertexID, [&GroupSet, &UniqueSeedGroups, &SeedGroups, &SeedTriangles](const int32 TriangleID) { const 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; const int32 NumGroups = SeedGroups.Num(); for (int32 k = 0; k < NumGroups; ++k) { ensure(GroupSet.GetGroup(SeedTriangles[k]) == SeedGroups[k]); const int32 GroupID = SeedGroups[k]; FMeshConnectedComponents::GrowToConnectedTriangles(&Mesh, TArray{SeedTriangles[k]}, TempROI, &QueueBuffer, [&GroupSet, &GroupID](const int32 T1, const int32 T2) { return GroupSet.GetGroup(T2) == GroupID; }); for (const int32 TID : TempROI) { TriangleFunc(TID); } } return true; } bool UE::Geometry::EnumerateSelectionEdges( const FGeometrySelection& MeshSelection, const UE::Geometry::FDynamicMesh3& Mesh, TFunctionRef EdgeFunc, const UE::Geometry::FPolygroupSet* UseGroupSet ) { if (MeshSelection.TopologyType == EGeometryTopologyType::Triangle) { return EnumerateTriangleSelectionEdges(MeshSelection, Mesh, EdgeFunc); } else if (MeshSelection.TopologyType == EGeometryTopologyType::Polygroup) { return EnumeratePolygroupSelectionEdges(MeshSelection, Mesh, (UseGroupSet != nullptr) ? *UseGroupSet : FPolygroupSet(&Mesh), EdgeFunc); } return false; } bool UE::Geometry::EnumerateTriangleSelectionEdges( const FGeometrySelection& MeshSelection, const UE::Geometry::FDynamicMesh3& Mesh, TFunctionRef EdgeFunc) { if ( ensure( MeshSelection.TopologyType == EGeometryTopologyType::Triangle ) == false ) { return false; } if (MeshSelection.ElementType == EGeometryElementType::Face) { for (const uint64 TriangleID : MeshSelection.Selection) { if (Mesh.IsTriangle((int32)TriangleID)) { const FIndex3i TriEdges = Mesh.GetTriEdges((int32)TriangleID); EdgeFunc(TriEdges[0]); EdgeFunc(TriEdges[1]); EdgeFunc(TriEdges[2]); } } } else if (MeshSelection.ElementType == EGeometryElementType::Edge) { for (const uint64 EncodedID : MeshSelection.Selection) { const FMeshTriEdgeID TriEdgeID( FGeoSelectionID(EncodedID).GeometryID ); const int32 EdgeID = Mesh.IsTriangle(TriEdgeID.TriangleID) ? Mesh.GetTriEdge(TriEdgeID.TriangleID, TriEdgeID.TriEdgeIndex) : IndexConstants::InvalidID; if (Mesh.IsEdge((int32)EdgeID)) { EdgeFunc((int32)EdgeID); } } } else if (MeshSelection.ElementType == EGeometryElementType::Vertex) { for (const uint64 VertexID : MeshSelection.Selection) { Mesh.EnumerateVertexEdges((int32)VertexID, [&EdgeFunc](const int32 EdgeID) { EdgeFunc(EdgeID); }); } } else { return false; } return true; } bool UE::Geometry::EnumeratePolygroupSelectionEdges( const FGeometrySelection& MeshSelection, const UE::Geometry::FDynamicMesh3& Mesh, const UE::Geometry::FPolygroupSet& GroupSet, TFunctionRef EdgeFunc ) { if ( ensure( MeshSelection.TopologyType == EGeometryTopologyType::Polygroup ) == false ) { return false; } TArray SeedTriGroups; TArray SeedTriangles; TArray SeedEdges; // TODO: the face 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) { TSet UniqueSeedTriGroups; for (const uint64 EncodedID : MeshSelection.Selection) { const FGeoSelectionID SelectionID(EncodedID); const int32 SeedTriangleID = (int32)SelectionID.GeometryID; if (Mesh.IsTriangle(SeedTriangleID)) { const int32 GroupID = GroupSet.GetGroup(SeedTriangleID); // See TODO: [TopologyMismatch] above //ensure(GroupID == (int32)SelectionID.TopologyID); // sanity-check that we are using the right group if ( GroupID >= 0 && UniqueSeedTriGroups.Contains(GroupID) == false) { UniqueSeedTriGroups.Add(GroupID); SeedTriGroups.Add(GroupID); SeedTriangles.Add(SeedTriangleID); } } } } else if (MeshSelection.ElementType == EGeometryElementType::Edge) { for (const uint64 EncodedID : MeshSelection.Selection) { // record selected edges, need to find other edges that are part of the same polygroup edge to include in selection const FMeshTriEdgeID TriEdgeID( FGeoSelectionID(EncodedID).GeometryID ); const int32 SeedEdgeID = Mesh.IsTriangle(TriEdgeID.TriangleID) ? Mesh.GetTriEdge(TriEdgeID.TriangleID, TriEdgeID.TriEdgeIndex) : IndexConstants::InvalidID; if (Mesh.IsEdge(SeedEdgeID)) { SeedEdges.Add(SeedEdgeID); } } } else if (MeshSelection.ElementType == EGeometryElementType::Vertex) { for (const uint64 EncodedID : MeshSelection.Selection) { const int32 VertexID = (int32)FGeoSelectionID(EncodedID).GeometryID; if (Mesh.IsVertex(VertexID)) { Mesh.EnumerateVertexEdges(VertexID, [&Mesh, &SeedEdges](const int32 EdgeID) { if (Mesh.IsEdge(EdgeID)) { SeedEdges.Add(EdgeID); } }); } } } else { return false; } TSet TempROI; // if we could provide this as input we would not need a temporary roi... TArray QueueBuffer; // Edge Type: currently enumerates all edges that are part of selected poly edge(s). // Vertex Type: currently enumerates all edges that are a part of a polyedge which contains the selected vertex/vertices // neither includes non poly-group edges if (MeshSelection.ElementType == EGeometryElementType::Vertex || MeshSelection.ElementType == EGeometryElementType::Edge) { const int32 NumEdges = SeedEdges.Num(); for (int32 j = 0; j < NumEdges; j++) { TArray EdgeGroups; // the 1 or 2 groups which an edge belongs to // retrieves the 1 or 2 triangles and groups to which a selected edge belongs, and finds which group(s) the triangles belong to Mesh.EnumerateEdgeTriangles(SeedEdges[j], [&GroupSet, &EdgeGroups](const int32 TriangleID) { const int32 GroupID = GroupSet.GetGroup(TriangleID); if (GroupID >= 0) { EdgeGroups.Add(GroupID); } }); EdgeGroups.Sort(); // if an edge's 2 triangles are from the same group, it is not a Polygroup boundary edge and can be disregarded const bool bIsInnerEdge = EdgeGroups.Num() == 2 && EdgeGroups[0] == EdgeGroups[1]; if (!bIsInnerEdge) { // finds connected edges by looking at all edges in a mesh and retrieving which 2 (or 1) groups they belong to // if an edge's 2 groups are the same as the selected edges' 2 groups, we know they are part of the same poly edge FMeshConnectedComponents::GrowToConnectedEdges(Mesh, TArray{SeedEdges[j]}, TempROI, &QueueBuffer, [&Mesh, &GroupSet, &EdgeGroups](const int32 E1, const int32 E2) { TArray OtherEdgeGroups; Mesh.EnumerateEdgeTriangles(E2, [&GroupSet, &OtherEdgeGroups](const int32 TriangleID) { const int GrpID = GroupSet.GetGroup(TriangleID); if (GrpID >= 0) { OtherEdgeGroups.Add(GrpID); } }); OtherEdgeGroups.Sort(); // if selected edge groups and current edge groups are the same, they belong to the same poly edge // note: currently if a border edge is selected (i.e an edge has only 1 triangle and 1 group), function will include // all other border edges of the polygroup return OtherEdgeGroups.Num() == EdgeGroups.Num() && OtherEdgeGroups == EdgeGroups; }); // apply edge function to all edges in poly edge for (const int32 EdgeID : TempROI) { EdgeFunc(EdgeID); } } } } else if (MeshSelection.ElementType == EGeometryElementType::Face) { const int32 NumGroups = SeedTriGroups.Num(); for (int32 k = 0; k < NumGroups; ++k) { ensure(GroupSet.GetGroup(SeedTriangles[k]) == SeedTriGroups[k]); const int32 GroupID = SeedTriGroups[k]; FMeshConnectedComponents::GrowToConnectedTriangles(&Mesh, TArray{SeedTriangles[k]}, TempROI, &QueueBuffer, [&GroupSet, &GroupID](const int32 T1, const int32 T2) { return GroupSet.GetGroup(T2) == GroupID; }); for (const int32 TID : TempROI) { FIndex3i TriEdges = Mesh.GetTriEdges(TID); EdgeFunc(TriEdges[0]); EdgeFunc(TriEdges[1]); EdgeFunc(TriEdges[2]); } } } else { return false; } return true; } bool UE::Geometry::EnumeratePolygroupSelectionEdges( const FGeometrySelection& MeshSelection, const UE::Geometry::FDynamicMesh3& Mesh, const UE::Geometry::FGroupTopology& GroupTopology, TFunctionRef EdgeFunc ) { if ( ensure( MeshSelection.TopologyType == EGeometryTopologyType::Polygroup ) == false ) { return false; } TArray GroupEdgeIDs; if (MeshSelection.ElementType == EGeometryElementType::Face) { TSet SeedGroups; for (const uint64 EncodedID : MeshSelection.Selection) { const FGeoSelectionID SelectionID(EncodedID); const int32 SeedTriangleID = (int32)SelectionID.GeometryID; if (Mesh.IsTriangle(SeedTriangleID)) { const int32 GroupID = GroupTopology.GetGroupID(SeedTriangleID); // See TODO: [TopologyMismatch] above //ensure(GroupID == (int32)SelectionID.TopologyID); // sanity-check that we are using the right group if ( GroupID >= 0) { SeedGroups.Add(GroupID); } } } for (const int32 GroupID : SeedGroups) { TArray GroupTriangles = GroupTopology.GetGroupTriangles(GroupID); for (const int32 TriangleID : GroupTriangles) { FIndex3i TriEdges = Mesh.GetTriEdges(TriangleID); EdgeFunc(TriEdges[0]); EdgeFunc(TriEdges[1]); EdgeFunc(TriEdges[2]); } } } else if (MeshSelection.ElementType == EGeometryElementType::Edge) { for (const uint64 EncodedID : MeshSelection.Selection) { const FMeshTriEdgeID TriEdgeID( FGeoSelectionID(EncodedID).GeometryID ); const int32 SeedEdgeID = Mesh.IsTriangle(TriEdgeID.TriangleID) ? Mesh.GetTriEdge(TriEdgeID.TriangleID, TriEdgeID.TriEdgeIndex) : IndexConstants::InvalidID; if (Mesh.IsEdge(SeedEdgeID)) { const int32 GroupEdgeID = GroupTopology.FindGroupEdgeID(SeedEdgeID); if (GroupEdgeID != IndexConstants::InvalidID) { GroupEdgeIDs.Add (GroupEdgeID); } } } } else if (MeshSelection.ElementType == EGeometryElementType::Vertex) { for (const uint64 VertexID : MeshSelection.Selection) { if (Mesh.IsVertex((int32)VertexID)) { Mesh.EnumerateVertexEdges((int32)VertexID, [&GroupTopology, &GroupEdgeIDs](const int32 EdgeID) { const int32 GroupEdgeID = GroupTopology.FindGroupEdgeID(EdgeID); if (GroupEdgeID != IndexConstants::InvalidID) { GroupEdgeIDs.Add(GroupEdgeID); } }); } } } else { return false; } if (MeshSelection.ElementType == EGeometryElementType::Vertex || MeshSelection.ElementType == EGeometryElementType::Edge) { for (const int32 GroupEdgeID : GroupEdgeIDs) { const FGroupTopology::FGroupEdge& GroupEdge = GroupTopology.Edges[GroupEdgeID]; for (const int32 EID : GroupEdge.Span.Edges) { EdgeFunc(EID); } } } return true; } bool UE::Geometry::EnumerateTriangleSelectionElements( const FGeometrySelection& MeshSelection, const UE::Geometry::FDynamicMesh3& Mesh, TFunctionRef VertexFunc, TFunctionRef EdgeFunc, TFunctionRef TriangleFunc, const FTransform* ApplyTransform, const bool bMapFacesToEdgeLoops ) { return EnumerateTriangleSelectionElements( MeshSelection, Mesh, VertexFunc, EdgeFunc, TriangleFunc, ApplyTransform, EEnumerateSelectionMapping::Default | (bMapFacesToEdgeLoops ? EEnumerateSelectionMapping::FacesToEdges : EEnumerateSelectionMapping::None) ); } bool UE::Geometry::EnumerateTriangleSelectionElements( const FGeometrySelection& MeshSelection, const UE::Geometry::FDynamicMesh3& Mesh, TFunctionRef VertexFunc, TFunctionRef EdgeFunc, TFunctionRef TriangleFunc, const FTransform* ApplyTransform, const EEnumerateSelectionMapping Flags ) { if ( ensure( MeshSelection.TopologyType == EGeometryTopologyType::Triangle ) == false ) { return false; } auto ApplyTriangleFunc = [&ApplyTransform, &TriangleFunc, &Mesh](const int32 TriangleID) { FVector3d VertA, VertB, VertC; Mesh.GetTriVertices((int32)TriangleID, VertA, VertB, VertC); if (ApplyTransform) { VertA = ApplyTransform->TransformPosition(VertA); VertB = ApplyTransform->TransformPosition(VertB); VertC = ApplyTransform->TransformPosition(VertC); } TriangleFunc(TriangleID, FTriangle3d(VertA, VertB, VertC)); }; auto ApplyEdgeFunc = [&ApplyTransform, &EdgeFunc, &Mesh](const int32 EdgeID) { FVector3d VertA, VertB; Mesh.GetEdgeV(EdgeID, VertA, VertB); if (ApplyTransform) { VertA = ApplyTransform->TransformPosition(VertA); VertB = ApplyTransform->TransformPosition(VertB); } EdgeFunc(EdgeID, FSegment3d(VertA, VertB)); }; auto ApplyVertexFunc = [&VertexFunc](const uint64 VertexID, const FVector3d& VertA) { VertexFunc((int32)VertexID, VertA); }; if (MeshSelection.ElementType == EGeometryElementType::Face) { // Call TriangleFunc if we are mapping Faces to Faces if ((Flags & EEnumerateSelectionMapping::FacesToFaces) != EEnumerateSelectionMapping::None) { EnumerateTriangleSelectionTriangles(MeshSelection, Mesh, ApplyTriangleFunc); } // Call EdgeFunc if we are mapping Faces to Edges if ((Flags & EEnumerateSelectionMapping::FacesToEdges) != EEnumerateSelectionMapping::None) { EnumerateTriangleSelectionEdges(MeshSelection, Mesh, ApplyEdgeFunc); } } else if (MeshSelection.ElementType == EGeometryElementType::Edge) { EnumerateTriangleSelectionEdges(MeshSelection, Mesh, ApplyEdgeFunc); } else if (MeshSelection.ElementType == EGeometryElementType::Vertex) { EnumerateTriangleSelectionVertices(MeshSelection, Mesh, ApplyTransform, ApplyVertexFunc); } 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, const bool bMapFacesToEdgeLoops ) { return EnumeratePolygroupSelectionElements( MeshSelection, Mesh, GroupTopology, VertexFunc, EdgeFunc, TriangleFunc, ApplyTransform, EEnumerateSelectionMapping::Default | (bMapFacesToEdgeLoops ? EEnumerateSelectionMapping::FacesToEdges : EEnumerateSelectionMapping::None) ); } bool UE::Geometry::EnumeratePolygroupSelectionElements( const FGeometrySelection& MeshSelection, const UE::Geometry::FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, TFunctionRef VertexFunc, TFunctionRef EdgeFunc, TFunctionRef TriangleFunc, const FTransform* ApplyTransform, const EEnumerateSelectionMapping Flags ) { if ( ensure( MeshSelection.TopologyType == EGeometryTopologyType::Polygroup ) == false ) { return false; } auto ProcessGroupEdgeID = [GroupTopology, &Mesh, ApplyTransform, &EdgeFunc](const int32 GroupEdgeID) { for (const int32 EdgeID : GroupTopology->GetGroupEdgeEdges(GroupEdgeID)) { FVector3d A, B; Mesh.GetEdgeV(EdgeID, A, B); if (ApplyTransform) { A = ApplyTransform->TransformPosition(A); B = ApplyTransform->TransformPosition(B); } EdgeFunc(EdgeID, FSegment3d(A, B)); } }; if (MeshSelection.ElementType == EGeometryElementType::Face) { for (const uint64 EncodedID : MeshSelection.Selection) { const FGeoSelectionID SelectionID(EncodedID); const int32 SeedTriangleID = (int32)SelectionID.GeometryID, GroupID = (int32)SelectionID.TopologyID; if (Mesh.IsTriangle(SeedTriangleID)) { // Call TriangleFunc if we are mapping Faces to Faces // Note: While EnumeratePolygroupSelectionTriangles would also return all faces, implementing it this way minimizes redundancy in the // MeshSelection.Selection loop and more consistent with the rest of the Polygroup selection elements enumeration if ((Flags & EEnumerateSelectionMapping::FacesToFaces) != EEnumerateSelectionMapping::None) { for (const int32 TriangleID : GroupTopology->GetGroupFaces(GroupID)) { FVector3d A, B, C; Mesh.GetTriVertices(TriangleID, A, B, C); if (ApplyTransform) { A = ApplyTransform->TransformPosition(A); B = ApplyTransform->TransformPosition(B); C = ApplyTransform->TransformPosition(C); } TriangleFunc(TriangleID, FTriangle3d(A, B, C)); } } // Process the polygroup edges if we are mapping Faces to Edges if ((Flags & EEnumerateSelectionMapping::FacesToEdges) != EEnumerateSelectionMapping::None) { TArray GroupEdgeIDs; if ( const FGroupTopology::FGroup* Group = GroupTopology->FindGroupByID(GroupID) ) { for (const FGroupTopology::FGroupBoundary& Boundary : Group->Boundaries) { for (const int32 GroupEdgeID : Boundary.GroupEdges) { GroupEdgeIDs.AddUnique(GroupEdgeID); } } } for (const int32 GroupEdgeID : GroupEdgeIDs) { ProcessGroupEdgeID(GroupEdgeID); } } } } } else if (MeshSelection.ElementType == EGeometryElementType::Edge) { for (const uint64 EncodedID : MeshSelection.Selection) { const FMeshTriEdgeID TriEdgeID( FGeoSelectionID(EncodedID).GeometryID ); const int32 SeedEdgeID = Mesh.IsTriangle(TriEdgeID.TriangleID) ? Mesh.GetTriEdge(TriEdgeID.TriangleID, TriEdgeID.TriEdgeIndex) : IndexConstants::InvalidID; if (Mesh.IsEdge(SeedEdgeID)) { const int32 GroupEdgeID = GroupTopology->FindGroupEdgeID(SeedEdgeID); if (GroupEdgeID != INDEX_NONE) { ProcessGroupEdgeID(GroupEdgeID); } } } } else if (MeshSelection.ElementType == EGeometryElementType::Vertex) { for (const uint64 EncodedID : MeshSelection.Selection) { const int32 VertexID = (int32)FGeoSelectionID(EncodedID).GeometryID; if (Mesh.IsVertex(VertexID)) { const FVector3d A = Mesh.GetVertex(VertexID); VertexFunc(VertexID, (ApplyTransform) ? 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 (const int32 TID : Triangles) { if (Mesh.IsTriangle(TID)) { const 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 (const int32 TID : Triangles) { if (Mesh.IsTriangle(TID)) { Mesh.EnumerateTriEdgeIDsFromTriID(TID, [&SelectionOut](const FMeshTriEdgeID TriEdgeID) { SelectionOut.Selection.Add(FGeoSelectionID::MeshEdge(TriEdgeID).Encoded()); }); } } } else if (SelectionOut.ElementType == EGeometryElementType::Face) { for (const 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 (const int32 VID : VertSelection) { const int32 CornerID = GroupTopology->GetCornerIDFromVertexID(VID); if (CornerID != IndexConstants::InvalidID) { const FGroupTopology::FCorner& Corner = GroupTopology->Corners[CornerID]; const FGeoSelectionID ID = FGeoSelectionID(Corner.VertexID, CornerID); SelectionOut.Selection.Add(ID.Encoded()); } } } else if (SelectionOut.ElementType == EGeometryElementType::Edge) { FMeshEdgeSelection EdgeSelection(&Mesh); EdgeSelection.SelectTriangleEdges(Triangles); for (const int32 EID : EdgeSelection) { const int32 GroupEdgeID = GroupTopology->FindGroupEdgeID(EID); if (GroupEdgeID != IndexConstants::InvalidID) { const FGroupTopology::FGroupEdge& GroupEdge = GroupTopology->Edges[GroupEdgeID]; const FMeshTriEdgeID MeshEdgeID = Mesh.GetTriEdgeIDFromEdgeID(GroupEdge.Span.Edges[0]); const FGeoSelectionID ID = FGeoSelectionID(MeshEdgeID.Encoded(), GroupEdgeID); SelectionOut.Selection.Add(ID.Encoded()); } } } else if (SelectionOut.ElementType == EGeometryElementType::Face) { for (const int32 TID : Triangles) { if (Mesh.IsTriangle(TID)) { const int32 GroupID = GroupTopology->GetGroupID(TID); const FGroupTopology::FGroup* GroupFace = GroupTopology->FindGroupByID(GroupID); if ( GroupFace ) { const 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) { return ConvertSelection(Mesh, GroupTopology, FromSelectionIn, ToSelectionOut, EEnumerateSelectionConversionParams::ContainSelection); } bool UE::Geometry::ConvertSelection( const UE::Geometry::FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, const FGeometrySelection& FromSelectionIn, FGeometrySelection& ToSelectionOut, const EEnumerateSelectionConversionParams ConversionParams) { using namespace GeometrySelectionUtilLocals; const auto FromTypeToSame = []( const FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, const FGeometrySelection& FromSelectionIn, FGeometrySelection& ToSelectionOut, const EEnumerateSelectionConversionParams ConversionParams) -> bool { checkSlow(FromSelectionIn.IsSameType(ToSelectionOut)); ToSelectionOut.Selection = FromSelectionIn.Selection; return true; }; const auto ToTriFace = []( const FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, const FGeometrySelection& FromSelectionIn, FGeometrySelection& ToSelectionOut, const EEnumerateSelectionConversionParams ConversionParams) -> bool { checkSlow(ToSelectionOut.TopologyType == EGeometryTopologyType::Triangle); checkSlow(ToSelectionOut.ElementType == EGeometryElementType::Face); const FPolygroupSet GroupSet = FPolygroupSet(&Mesh); // TODO: these if statements could be consolidated to minimize minor repeat code; however its currently set up this way for readability and // scalability for if/when more EEnumerateSelectionConversionParams are added if (ConversionParams == EEnumerateSelectionConversionParams::ExpandSelection) { EnumerateSelectionTriangles( FromSelectionIn, Mesh, [&ToSelectionOut](const int32 TID) { ToSelectionOut.Selection.Add(FGeoSelectionID::MeshTriangle(TID).Encoded()); }, &GroupSet); } else if (ConversionParams == EEnumerateSelectionConversionParams::ContainSelection) { // where FromSelection type is either PolyVerts, PolyEdges, PolyFaces, TriFaces -> ensuring stable selection is not applicable and can do same as in ExpandSelection if (FromSelectionIn.TopologyType == EGeometryTopologyType::Polygroup || ( FromSelectionIn.ElementType == EGeometryElementType::Face && FromSelectionIn.TopologyType == EGeometryTopologyType::Triangle)) { EnumerateSelectionTriangles( FromSelectionIn, Mesh, [&ToSelectionOut](const int32 TID) { ToSelectionOut.Selection.Add(FGeoSelectionID::MeshTriangle(TID).Encoded()); }, &GroupSet); } // where FromSelection type is either TriEdge or TriVert else { TArray AllTriIDsConnectedToSelection; // retrieve all the triangles connected to the selected verts/edges but they will not all be included in final selection EnumerateSelectionTriangles( FromSelectionIn, Mesh, [&AllTriIDsConnectedToSelection](const int32 TID) { return AllTriIDsConnectedToSelection.Add(TID); }, &GroupSet); if (FromSelectionIn.ElementType == EGeometryElementType::Edge) { // retrieve all the edges currently in the selection TSet SelectedEdges; for (const uint64 EncodedID : FromSelectionIn.Selection) { const FMeshTriEdgeID TriEdgeID( FGeoSelectionID(EncodedID).GeometryID ); const int32 EdgeID = Mesh.IsTriangle(TriEdgeID.TriangleID) ? Mesh.GetTriEdge(TriEdgeID.TriangleID, TriEdgeID.TriEdgeIndex) : IndexConstants::InvalidID; SelectedEdges.Add(EdgeID); } // for each triangle that's connected to the selection, determine if it is formed by 3 selected edges or not for (const int TriangleID : AllTriIDsConnectedToSelection) //-V1078 { const FIndex3i TriEdges = Mesh.GetTriEdges(TriangleID); if (SelectedEdges.Contains(TriEdges.A) && SelectedEdges.Contains(TriEdges.B) && SelectedEdges.Contains(TriEdges.C)) { ToSelectionOut.Selection.Add(FGeoSelectionID::MeshTriangle(TriangleID).Encoded()); } } } if (FromSelectionIn.ElementType == EGeometryElementType::Vertex) { // retrieve all the vertices currently in the selection TSet SelectedVerts; for (const uint64 VertexID : FromSelectionIn.Selection) { SelectedVerts.Add((int32)VertexID); } // for each triangle that's connected to the selection, determine if it is formed by 3 selected vertices or not for (const int TriangleID : AllTriIDsConnectedToSelection) //-V1078 { const FIndex3i TriVerts = Mesh.GetTriangle(TriangleID); if (SelectedVerts.Contains(TriVerts.A) && SelectedVerts.Contains(TriVerts.B) && SelectedVerts.Contains(TriVerts.C)) { ToSelectionOut.Selection.Add(FGeoSelectionID::MeshTriangle(TriangleID).Encoded()); } } } } } return true; }; const auto ToTriEdge = []( const FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, const FGeometrySelection& FromSelectionIn, FGeometrySelection& ToSelectionOut, const EEnumerateSelectionConversionParams ConversionParams) -> bool { checkSlow(ToSelectionOut.TopologyType == EGeometryTopologyType::Triangle); checkSlow(ToSelectionOut.ElementType == EGeometryElementType::Edge); auto SelectEdgesFunc = [&Mesh, &GroupTopology, &ToSelectionOut] (const int32 EdgeID) { Mesh.EnumerateTriEdgeIDsFromEdgeID(EdgeID, [&ToSelectionOut](const FMeshTriEdgeID TriEdgeID) { ToSelectionOut.Selection.Add(FGeoSelectionID::MeshEdge(TriEdgeID).Encoded()); }); }; // TODO: these if statements could be consolidated to minimize repeat code; however its currently set up this way for readability and // scalability for if/when more EEnumerateSelectionConversionParams are added if (ConversionParams == EEnumerateSelectionConversionParams::ExpandSelection) { if (FromSelectionIn.TopologyType == EGeometryTopologyType::Triangle) { EnumerateTriangleSelectionEdges(FromSelectionIn, Mesh, SelectEdgesFunc); } else if (FromSelectionIn.TopologyType == EGeometryTopologyType::Polygroup) { EnumeratePolygroupSelectionEdges(FromSelectionIn, Mesh, *GroupTopology, SelectEdgesFunc); } } else if (ConversionParams == EEnumerateSelectionConversionParams::ContainSelection) { if (FromSelectionIn.TopologyType == EGeometryTopologyType::Triangle) { // in the case of TriVert->TriEdge, ensure that only edges where both verts are in the initial selection are included if (FromSelectionIn.ElementType == EGeometryElementType::Vertex) { TSet SelectedVerts; TArray AllEdgesConnectedToVerts; for (const uint64 VertexID : FromSelectionIn.Selection) { SelectedVerts.Add(VertexID); Mesh.EnumerateVertexEdges((int32)VertexID, [&SelectEdgesFunc, &AllEdgesConnectedToVerts](const int32 EdgeID) { AllEdgesConnectedToVerts.Add(EdgeID); }); } // ensure stable selection by only selecting the edges where BOTH its verts were in init selection // however a selection which includes a single vert or any verts without any of their adjacent verts selected will be lost in conversion for (const int32 EdgeID : AllEdgesConnectedToVerts) //-V1078 { const FIndex2i EdgeVerts = Mesh.GetEdgeV(EdgeID); if (SelectedVerts.Contains(EdgeVerts.A) && SelectedVerts.Contains(EdgeVerts.B)) { SelectEdgesFunc(EdgeID); } } } else // case of converting from TriEdge or TriFace -> ensuring stable selection is not applicable and can do same as in ExpandSelection { EnumerateTriangleSelectionEdges(FromSelectionIn, Mesh, SelectEdgesFunc); } } // case of converting from PolyVert, PolyEdge, PolyFace -> ensuring stable selection is not applicable and can do same as in ExpandSelection else if (FromSelectionIn.TopologyType == EGeometryTopologyType::Polygroup) { EnumeratePolygroupSelectionEdges(FromSelectionIn, Mesh, *GroupTopology, SelectEdgesFunc); } } return true; }; const auto ToTriVtx = []( const FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, const FGeometrySelection& FromSelectionIn, FGeometrySelection& ToSelectionOut, const EEnumerateSelectionConversionParams ConversionParams) -> bool { checkSlow(ToSelectionOut.TopologyType == EGeometryTopologyType::Triangle); checkSlow(ToSelectionOut.ElementType == EGeometryElementType::Vertex); if (FromSelectionIn.TopologyType == EGeometryTopologyType::Triangle) { return EnumerateTriangleSelectionVertices(FromSelectionIn, Mesh, nullptr, [&ToSelectionOut](const uint64 Vid, const FVector3d& Unused) { ToSelectionOut.Selection.Add( FGeoSelectionID::MeshVertex((int32)Vid).Encoded() ); }); } else if (FromSelectionIn.TopologyType == EGeometryTopologyType::Polygroup) { // 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](const uint64 VID, const FVector3d& Unused) { ToSelectionOut.Selection.Add( FGeoSelectionID::MeshVertex((int32)VID).Encoded() ); }); } return true; }; const auto ToPolyFace = []( const FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, const FGeometrySelection& FromSelectionIn, FGeometrySelection& ToSelectionOut, const EEnumerateSelectionConversionParams ConversionParams) -> bool { checkSlow(ToSelectionOut.TopologyType == EGeometryTopologyType::Polygroup); checkSlow(ToSelectionOut.ElementType == EGeometryElementType::Face); const FPolygroupSet GroupSet = FPolygroupSet(&Mesh); EnumerateSelectionTriangles(FromSelectionIn, Mesh, [&ToSelectionOut, &GroupTopology](const int32 TID) { const int GroupID = GroupTopology->GetGroupID(TID); const TArray& GroupTris = GroupTopology->GetGroupTriangles(GroupID); for (const int GroupTri : GroupTris) { ToSelectionOut.Selection.Add(FGeoSelectionID(GroupTri, GroupID).Encoded()); } }, &GroupSet); return true; }; const auto ToPolyEdge = []( const FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, const FGeometrySelection& FromSelectionIn, FGeometrySelection& ToSelectionOut, const EEnumerateSelectionConversionParams ConversionParams) { checkSlow(ToSelectionOut.TopologyType == EGeometryTopologyType::Polygroup); checkSlow(ToSelectionOut.ElementType == EGeometryElementType::Edge); bool bConverted = false; auto SelectEdgesFunc = [&Mesh, &GroupTopology, &ToSelectionOut, &bConverted] (const int32 EdgeID) { // similar but simplified version of code in GroupTopology->IsGroupEdge() const FIndex2i EdgeT = Mesh.GetEdgeT(EdgeID); if (EdgeT.B == IndexConstants::InvalidID) { bConverted = true; } const bool bIsGroupEdge = GroupTopology->GetGroupID(EdgeT.A) != GroupTopology->GetGroupID(EdgeT.B); bConverted = bConverted || bIsGroupEdge; if (bIsGroupEdge) { const int32 GroupEdgeID = GroupTopology->FindGroupEdgeID(EdgeID); for (const int32 EID : GroupTopology->Edges[GroupEdgeID].Span.Edges) { Mesh.EnumerateTriEdgeIDsFromEdgeID(EID, [&ToSelectionOut, &GroupEdgeID](const FMeshTriEdgeID TriEdgeID) { ToSelectionOut.Selection.Add(FGeoSelectionID(TriEdgeID.Encoded(), GroupEdgeID).Encoded()); }); } } }; if (FromSelectionIn.TopologyType == EGeometryTopologyType::Triangle) { EnumerateTriangleSelectionEdges(FromSelectionIn, Mesh, SelectEdgesFunc); } else if (FromSelectionIn.TopologyType == EGeometryTopologyType::Polygroup) { EnumeratePolygroupSelectionEdges(FromSelectionIn, Mesh, *GroupTopology, SelectEdgesFunc); } return bConverted; }; const auto ToPolyVtx = []( const FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, const FGeometrySelection& FromSelectionIn, FGeometrySelection& ToSelectionOut, const EEnumerateSelectionConversionParams ConversionParams) -> bool { checkSlow(ToSelectionOut.TopologyType == EGeometryTopologyType::Polygroup); checkSlow(ToSelectionOut.ElementType == EGeometryElementType::Vertex); auto ApplyPolyVertFunc = [&Mesh, GroupTopology, &ToSelectionOut](const uint64 VID, bool &bConverted) { if (Mesh.IsVertex((int32)VID)) { const int32 CornerID = GroupTopology->GetCornerIDFromVertexID((int32)VID); if (CornerID != IndexConstants::InvalidID) { bConverted = true; const FGeoSelectionID ID = FGeoSelectionID((int32)VID, CornerID); ToSelectionOut.Selection.Add(ID.Encoded()); } } }; bool bConverted = false; if (FromSelectionIn.TopologyType == EGeometryTopologyType::Triangle) { EnumerateTriangleSelectionVertices(FromSelectionIn, Mesh, nullptr, [&bConverted, &ApplyPolyVertFunc](const uint64 VID, const FVector& Unused) { ApplyPolyVertFunc(VID, bConverted); }); } else if (FromSelectionIn.TopologyType == EGeometryTopologyType::Polygroup) { const FTransform Transform = FTransform::Identity; EnumeratePolygroupSelectionVertices(FromSelectionIn, Mesh, GroupTopology, Transform, [&bConverted, &ApplyPolyVertFunc](const uint64 VID, const FVector& Unused) { ApplyPolyVertFunc(VID, bConverted); }); } return bConverted; }; typedef bool (*ConvertSelectionFunc)( const FDynamicMesh3& Mesh, const FGroupTopology* GroupTopology, const FGeometrySelection& FromSelectionIn, FGeometrySelection& ToSelectionOut, const EEnumerateSelectionConversionParams ConversionParams); constexpr ConvertSelectionFunc ConvertFuncs[6][6] = { {FromTypeToSame, ToTriEdge, ToTriFace, ToPolyVtx, ToPolyEdge, ToPolyFace }, {ToTriVtx, FromTypeToSame, ToTriFace, ToPolyVtx, ToPolyEdge, ToPolyFace }, {ToTriVtx, ToTriEdge, FromTypeToSame, ToPolyVtx, ToPolyEdge, ToPolyFace }, {ToTriVtx, ToTriEdge, ToTriFace, FromTypeToSame,ToPolyEdge, ToPolyFace }, {ToTriVtx, ToTriEdge, ToTriFace, ToPolyVtx, FromTypeToSame, ToPolyFace }, {ToTriVtx, ToTriEdge, ToTriFace, ToPolyVtx, ToPolyEdge, FromTypeToSame } }; const int FromIndex = GetSelectionTypeAsIndex(FromSelectionIn); const int ToIndex = GetSelectionTypeAsIndex(ToSelectionOut); return ConvertFuncs[FromIndex][ToIndex](Mesh, GroupTopology, FromSelectionIn, ToSelectionOut, ConversionParams); } bool UE::Geometry::ConvertTriangleSelectionToOverlaySelection( const FDynamicMesh3& Mesh, const FGeometrySelection& MeshSelection, TSet& TrianglesOut, TSet& VerticesOut, FGeometrySelection* IncidentSelection) { 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 if (MeshSelection.ElementType == EGeometryElementType::Edge && IncidentSelection) { IncidentSelection->InitializeTypes(EGeometryElementType::Vertex, EGeometryTopologyType::Triangle); EnumerateTriangleSelectionTriangles(MeshSelection, Mesh, [&TrianglesOut](int32 ValidTid) { TrianglesOut.Add(ValidTid); }); EnumerateTriangleSelectionVertices(MeshSelection, Mesh, nullptr, [&VerticesOut, IncidentSelection](uint64 Vid, const FVector3d& Unused) { IncidentSelection->Selection.Add(FGeoSelectionID::MeshVertex((int)Vid).Encoded() ); VerticesOut.Add((int)Vid); }); } else { EnumerateTriangleSelectionTriangles(MeshSelection, Mesh, [&TrianglesOut](int32 ValidTid) { TrianglesOut.Add(ValidTid); }); EnumerateTriangleSelectionVertices(MeshSelection, Mesh, nullptr, [&VerticesOut](uint64 Vid, const FVector3d& Unused) { VerticesOut.Add((int)Vid); }); } return true; } bool UE::Geometry::ConvertPolygroupSelectionToOverlaySelection( const FDynamicMesh3& Mesh, const FPolygroupSet& GroupSet, const FGeometrySelection& MeshSelection, TSet& TrianglesOut, TSet& VerticesOut) { return EnumeratePolygroupSelectionTriangles(MeshSelection, Mesh, GroupSet, [&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); }); } bool UE::Geometry::ConvertPolygroupSelectionToIncidentOverlaySelection( const FDynamicMesh3& Mesh, const FGroupTopology& GroupTopology, const FGeometrySelection& MeshSelection, TSet& TrianglesOut, TSet& VerticesOut, FGeometrySelection* IncidentSelection) { if (!ensure(MeshSelection.TopologyType == EGeometryTopologyType::Polygroup)) { return false; } if (MeshSelection.ElementType == EGeometryElementType::Face) { if (MeshSelection.TopologyType == EGeometryTopologyType::Polygroup) { // TODO This uses the polygroup set stored directly in the mesh, this is a source of potential inconsistency // with the given GroupTopology const FPolygroupSet GroupSet = FPolygroupSet(&Mesh); return EnumeratePolygroupSelectionTriangles(MeshSelection, Mesh, GroupSet, [&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 { return ConvertTriangleSelectionToOverlaySelection(Mesh, MeshSelection, TrianglesOut, VerticesOut); } } else { FGeometrySelection TempIncidentSelection; if (IncidentSelection == nullptr) { IncidentSelection = &TempIncidentSelection; } IncidentSelection->InitializeTypes(EGeometryElementType::Vertex, EGeometryTopologyType::Triangle); // GroupTopology argument is ignored if MeshSelection has Triangle topology bool bSuccess = ConvertSelection(Mesh, &GroupTopology, MeshSelection, *IncidentSelection, EEnumerateSelectionConversionParams::ContainSelection); ensure(bSuccess == true); ensure(!IncidentSelection->IsEmpty()); return ConvertTriangleSelectionToOverlaySelection(Mesh, *IncidentSelection, TrianglesOut, VerticesOut); } } 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()) { // Test if both half-edges pass the edge selection predicate bool bShouldSelect = true; Mesh.EnumerateTriEdgeIDsFromEdgeID(eid, [&SelectionIDPredicate, &bShouldSelect](FMeshTriEdgeID TriEdgeID) { bShouldSelect = bShouldSelect && SelectionIDPredicate(FGeoSelectionID::MeshEdge(TriEdgeID)); }); if (bShouldSelect) { // Select both half-edges Mesh.EnumerateTriEdgeIDsFromEdgeID(eid, [&AllSelection](FMeshTriEdgeID TriEdgeID) { AllSelection.Selection.Add(FGeoSelectionID::MeshEdge(TriEdgeID).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); // Grow if both half-edges pass the predicate bool bToEdgeID_SelectionPredicate = true; Mesh.EnumerateTriEdgeIDsFromEdgeID(ToEdgeID, [&SelectionIDPredicate, &bToEdgeID_SelectionPredicate](FMeshTriEdgeID TestTriEdgeID) { bToEdgeID_SelectionPredicate = bToEdgeID_SelectionPredicate && SelectionIDPredicate(FGeoSelectionID::MeshEdge(TestTriEdgeID)); }); return bToEdgeID_SelectionPredicate && IsConnectedPredicate( FGeoSelectionID::MeshEdge(FromTriEdgeID), FGeoSelectionID::MeshEdge(ToTriEdgeID) ); }); for (int32 EdgeID : ConnectedEdges) { Mesh.EnumerateTriEdgeIDsFromEdgeID(EdgeID, [&AllConnectedSelection](FMeshTriEdgeID TriEdgeID) { AllConnectedSelection.Selection.Add(FGeoSelectionID::MeshEdge(TriEdgeID).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(EAllowShrinking::No)); 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(EAllowShrinking::No)); 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(EAllowShrinking::No)); 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) { // Test if both half-edges pass the edge selection predicate bool bShouldSelect = true; Mesh.EnumerateTriEdgeIDsFromEdgeID(EdgeID, [&SelectionIDPredicate, &bShouldSelect](FMeshTriEdgeID TriEdgeID) { bShouldSelect = bShouldSelect && SelectionIDPredicate(FGeoSelectionID::MeshEdge(TriEdgeID)); }); if (bShouldSelect) { // Select both half-edges Mesh.EnumerateTriEdgeIDsFromEdgeID(EdgeID, [&BoundaryConnectedSelection](FMeshTriEdgeID TriEdgeID) { BoundaryConnectedSelection.Selection.Add(FGeoSelectionID::MeshEdge(TriEdgeID).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; }