You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#rb Ryan.Schmidt #jira UE-182166 #preflight 6470bf745d3ca2dfd9a093f4 [CL 25647926 by semion piskarev in ue5-main branch]
2293 lines
66 KiB
C++
2293 lines
66 KiB
C++
// 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<void(int32)> 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<void(uint32)> 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<void(uint32)> 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>{(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>{(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>{(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<uint64>{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<double>::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<uint64>{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<double>::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<uint64>{SelectionID.Encoded()}, & ResultOut.SelectionDelta);
|
|
ResultOut.bSelectionMissed = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool UE::Geometry::UpdateSelectionWithNewElements(
|
|
FGeometrySelectionEditor* Editor,
|
|
EGeometrySelectionChangeType ChangeType,
|
|
const TArray<uint64>& 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<void(uint64, const FVector3d&)> 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<void(uint64, const FVector3d&)> 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<void(int32)> 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<void(int32)> 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<void(int32)> TriangleFunc
|
|
)
|
|
{
|
|
if ( ensure( MeshSelection.TopologyType == EGeometryTopologyType::Polygroup ) == false )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TArray<int32> SeedGroups;
|
|
TArray<int32> SeedTriangles;
|
|
TSet<int32> 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<int> TempROI; // if we could provide this as input we would not need a temporary roi...
|
|
TArray<int32> 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<int>{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<void(int32, const FVector3d&)> VertexFunc,
|
|
TFunctionRef<void(int32, const FSegment3d&)> EdgeFunc,
|
|
TFunctionRef<void(int32, const FTriangle3d&)> 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<void(int32, const FVector3d&)> VertexFunc,
|
|
TFunctionRef<void(int32, const FSegment3d&)> EdgeFunc,
|
|
TFunctionRef<void(int32, const FTriangle3d&)> 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<int32> 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<const int> 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<int32> 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<int>& TrianglesOut,
|
|
TSet<int>& 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<bool(FGeoSelectionID)> 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<bool(FGeoSelectionID)> SelectionIDPredicate,
|
|
TFunctionRef<bool(FGeoSelectionID, FGeoSelectionID)> IsConnectedPredicate,
|
|
FGeometrySelection& AllConnectedSelection)
|
|
{
|
|
if ( ! ensure(ReferenceSelection.IsSameType(AllConnectedSelection)) ) return false;
|
|
|
|
if (AllConnectedSelection.TopologyType == EGeometryTopologyType::Triangle)
|
|
{
|
|
TArray<int32> CurIndices;
|
|
CurIndices.Reserve(ReferenceSelection.Num());
|
|
|
|
if (AllConnectedSelection.ElementType == EGeometryElementType::Vertex)
|
|
{
|
|
for (uint64 ElementID : ReferenceSelection.Selection)
|
|
{
|
|
CurIndices.Add( FGeoSelectionID(ElementID).GeometryID );
|
|
}
|
|
TSet<int32> 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<int32> 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<int32> 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<uint64> Queue;
|
|
for (uint64 ID : ReferenceSelection.Selection)
|
|
{
|
|
Queue.Add( ID );
|
|
}
|
|
|
|
if (AllConnectedSelection.ElementType == EGeometryElementType::Vertex)
|
|
{
|
|
TArray<int32> 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<int32> 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<int32> 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<int32>& BorderVidsOut, TSet<int32>& 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<int32> 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<int32> 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<int32>& BorderCornerIDsOut, TSet<int32>& 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<int32> 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<int32> 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<int32> 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<bool(FGeoSelectionID)> SelectionIDPredicate,
|
|
FGeometrySelection& BoundaryConnectedSelection)
|
|
{
|
|
using namespace GeometrySelectionUtilLocals;
|
|
|
|
if (BoundaryConnectedSelection.TopologyType == EGeometryTopologyType::Triangle)
|
|
{
|
|
TSet<int32> BorderVertices;
|
|
TSet<int32> CurVertices;
|
|
if (!GetSelectionBoundaryVertices(Mesh, GroupTopology, ReferenceSelection, BorderVertices, CurVertices))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Now select elements connected to the border vertices.
|
|
if (BoundaryConnectedSelection.ElementType == EGeometryElementType::Vertex)
|
|
{
|
|
TSet<int32> 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<int32> 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<int32> 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<int32> CurCornerIDs;
|
|
TSet<int32> BorderCorners;
|
|
|
|
if (!GetSelectionBoundaryCorners(Mesh, GroupTopology, ReferenceSelection, BorderCorners, CurCornerIDs))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TArray<int32> NbrArray;
|
|
|
|
// now that we have boundary corners, iterate over them and select connected elements
|
|
if (BoundaryConnectedSelection.ElementType == EGeometryElementType::Vertex)
|
|
{
|
|
TSet<int32> 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<int32> 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<int32> 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<uint64, TInlineAllocator<32>> 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<uint64, TInlineAllocator<32>> 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;
|
|
} |