2019-12-27 09:26:59 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
2019-09-10 11:35:20 -04:00
# include "Operations/MeshPlaneCut.h"
2019-12-19 18:07:47 -05:00
2021-06-13 00:36:02 -04:00
# include "DynamicMesh/DynamicMesh3.h"
# include "DynamicMesh/DynamicMeshTriangleAttribute.h"
2019-12-19 18:07:47 -05:00
2019-09-10 11:35:20 -04:00
# include "Operations/SimpleHoleFiller.h"
# include "Operations/PlanarHoleFiller.h"
2020-04-18 18:42:59 -04:00
# include "Operations/MinimalHoleFiller.h"
2021-06-13 00:36:02 -04:00
# include "DynamicMesh/MeshNormals.h"
2019-09-10 11:35:20 -04:00
# include "DynamicMeshEditor.h"
# include "MathUtil.h"
2020-01-27 20:11:15 -05:00
# include "Selections/MeshConnectedComponents.h"
2019-09-10 11:35:20 -04:00
# include "Async/ParallelFor.h"
2021-03-09 19:33:56 -04:00
using namespace UE : : Geometry ;
2019-12-19 18:07:47 -05:00
2023-02-09 16:49:32 -05:00
void FMeshPlaneCut : : ComputeVertexSignedDistances ( TArray < double > & Signs , double InvalidDist )
2019-09-10 11:35:20 -04:00
{
int MaxVID = Mesh - > MaxVertexID ( ) ;
Signs . SetNum ( MaxVID ) ;
2023-02-09 16:49:32 -05:00
constexpr bool bNoParallel = false ;
2019-09-10 11:35:20 -04:00
ParallelFor ( MaxVID , [ & ] ( int32 VID )
{
if ( Mesh - > IsVertex ( VID ) )
{
Signs [ VID ] = ( Mesh - > GetVertex ( VID ) - PlaneOrigin ) . Dot ( PlaneNormal ) ;
}
else
{
Signs [ VID ] = InvalidDist ;
}
2020-01-27 20:11:15 -05:00
} , bNoParallel ) ;
2023-02-09 16:49:32 -05:00
}
void FMeshPlaneCut : : SplitCrossingEdges ( bool bDeleteTrisOnPlane , TArray < double > & Signs , TSet < int > & AlreadyOnPlaneEdges , TSet < int32 > & CutPlaneEdges , TSet < int > * OnSplitEdges , TSet < int > * OnPlaneVertices , TSet < int32 > * TriangleSelection )
{
AlreadyOnPlaneEdges . Reset ( ) ;
CutPlaneEdges . Reset ( ) ;
if ( OnSplitEdges )
{
OnSplitEdges - > Reset ( ) ;
}
if ( OnPlaneVertices )
{
OnPlaneVertices - > Reset ( ) ;
}
// Compute signed distances at all vertices. Set invalid dists to zero, because any vertex we add will be on the plane.
ComputeVertexSignedDistances ( Signs , 0.0 ) ;
2019-09-10 11:35:20 -04:00
2019-12-19 18:07:47 -05:00
if ( bDeleteTrisOnPlane )
{
for ( int TID = 0 ; TID < Mesh - > MaxTriangleID ( ) ; TID + + )
{
if ( ! Mesh - > IsTriangle ( TID ) )
{
continue ;
}
FIndex3i Tri = Mesh - > GetTriangle ( TID ) ;
FIndex3i TriEdges = Mesh - > GetTriEdges ( TID ) ;
2023-02-09 16:49:32 -05:00
if ( FMathd : : Abs ( Signs [ Tri . A ] ) < PlaneTolerance & &
FMathd : : Abs ( Signs [ Tri . B ] ) < PlaneTolerance & &
FMathd : : Abs ( Signs [ Tri . C ] ) < PlaneTolerance )
2019-12-19 18:07:47 -05:00
{
2023-02-09 16:49:32 -05:00
Mesh - > RemoveTriangle ( TID , true , false ) ;
if ( TriangleSelection )
2019-12-19 18:07:47 -05:00
{
2023-02-09 16:49:32 -05:00
// Remove triangle from the selection as well (does nothing if it was not in the selection already)
TriangleSelection - > Remove ( TID ) ;
2019-12-19 18:07:47 -05:00
}
}
}
}
2019-09-10 11:35:20 -04:00
// have to skip processing of new edges. If edge id
// is > max at start, is new. Otherwise if in NewEdges list, also new.
int MaxEID = Mesh - > MaxEdgeID ( ) ;
2023-02-09 16:49:32 -05:00
TSet < int > NewEdgesBeforeMaxID ;
auto AddNewEdge = [ & NewEdgesBeforeMaxID , MaxEID ] ( int32 NewEID )
{
if ( NewEID < MaxEID )
{
NewEdgesBeforeMaxID . Add ( NewEID ) ;
}
} ;
2019-09-10 11:35:20 -04:00
// cut existing edges with plane, using edge split
2023-02-09 16:49:32 -05:00
for ( int32 EID = 0 ; EID < MaxEID ; + + EID )
2019-09-10 11:35:20 -04:00
{
2023-02-09 16:49:32 -05:00
if ( ! Mesh - > IsEdge ( EID ) | | NewEdgesBeforeMaxID . Contains ( EID ) )
2019-09-10 11:35:20 -04:00
{
continue ;
}
2020-01-27 20:11:15 -05:00
if ( EdgeFilterFunc & & EdgeFilterFunc ( EID ) = = false )
{
continue ;
}
2019-09-10 11:35:20 -04:00
2023-02-09 16:49:32 -05:00
FIndex2i EdgeV = Mesh - > GetEdgeV ( EID ) ;
const double DistA = Signs [ EdgeV . A ] ;
const double DistB = Signs [ EdgeV . B ] ;
2019-09-10 11:35:20 -04:00
// If both Signs are 0, this edge is on-contour
// If one sign is 0, that vertex is on-contour
2023-02-09 16:49:32 -05:00
int AOnPlane = ( FMathd : : Abs ( DistA ) < PlaneTolerance ) ? 1 : 0 ;
int BOnPlane = ( FMathd : : Abs ( DistB ) < PlaneTolerance ) ? 1 : 0 ;
if ( AOnPlane | | BOnPlane )
2019-09-10 11:35:20 -04:00
{
2023-02-09 16:49:32 -05:00
if ( AOnPlane & & BOnPlane )
2019-09-10 11:35:20 -04:00
{
2023-02-09 16:49:32 -05:00
AlreadyOnPlaneEdges . Add ( EID ) ;
if ( OnPlaneVertices )
{
OnPlaneVertices - > Add ( EdgeV . A ) ;
OnPlaneVertices - > Add ( EdgeV . B ) ;
}
2020-01-27 20:11:15 -05:00
}
2023-02-09 16:49:32 -05:00
else if ( OnPlaneVertices )
2020-01-27 20:11:15 -05:00
{
2023-02-09 16:49:32 -05:00
// Note: if (!BOnPlane), then this will pick EdgeV.A, otherwise it will pick EdgeV.B
OnPlaneVertices - > Add ( EdgeV [ BOnPlane ] ) ;
2019-09-10 11:35:20 -04:00
}
continue ;
}
// no crossing
2023-02-09 16:49:32 -05:00
if ( DistA * DistB > 0 )
2019-09-10 11:35:20 -04:00
{
continue ;
}
2023-02-09 16:49:32 -05:00
FDynamicMesh3 : : FEdgeSplitInfo SplitInfo ;
double Param = DistA / ( DistA - DistB ) ;
EMeshResult SplitResult = Mesh - > SplitEdge ( EID , SplitInfo , Param ) ;
if ( ! ensureMsgf ( SplitResult = = EMeshResult : : Ok , TEXT ( " FMeshPlaneCut::Cut: failed to SplitEdge " ) ) )
2019-10-01 20:41:42 -04:00
{
continue ; // edge split really shouldn't fail; skip the edge if it somehow does
}
2019-09-10 11:35:20 -04:00
2023-02-09 16:49:32 -05:00
if ( TriangleSelection & & TriangleSelection - > Contains ( SplitInfo . OriginalTriangles . A ) )
2019-09-10 11:35:20 -04:00
{
2023-02-09 16:49:32 -05:00
TriangleSelection - > Add ( SplitInfo . NewTriangles . A ) ;
2019-09-10 11:35:20 -04:00
}
2020-01-27 20:11:15 -05:00
2023-02-09 16:49:32 -05:00
AddNewEdge ( SplitInfo . NewEdges . A ) ;
AddNewEdge ( SplitInfo . NewEdges . B ) ;
// If requested, track the edges we've split
if ( OnSplitEdges )
{
OnSplitEdges - > Add ( EID ) ;
OnSplitEdges - > Add ( SplitInfo . NewEdges . A ) ;
}
// We need to check whether the other vertices are on plane to decide if the connected edges are on the plane or not
int32 OtherVIDA = SplitInfo . OtherVertices . A ;
// Other vertex is on plane if it's newly created or within plane tolerance
if ( OtherVIDA > = Signs . Num ( ) | | FMath : : Abs ( Signs [ OtherVIDA ] ) < PlaneTolerance )
{
CutPlaneEdges . Add ( SplitInfo . NewEdges . B ) ;
}
if ( SplitInfo . NewEdges . C ! = FDynamicMesh3 : : InvalidID )
{
if ( TriangleSelection & & TriangleSelection - > Contains ( SplitInfo . OriginalTriangles . B ) )
{
TriangleSelection - > Add ( SplitInfo . NewTriangles . B ) ;
}
AddNewEdge ( SplitInfo . NewEdges . C ) ;
int32 OtherVIDB = SplitInfo . OtherVertices . B ;
if ( OtherVIDB > = Signs . Num ( ) | | FMath : : Abs ( Signs [ OtherVIDB ] ) < PlaneTolerance )
{
CutPlaneEdges . Add ( SplitInfo . NewEdges . C ) ;
}
}
if ( OnPlaneVertices )
{
OnPlaneVertices - > Add ( SplitInfo . NewVertex ) ;
}
2019-09-10 11:35:20 -04:00
}
2019-12-19 18:07:47 -05:00
}
2019-09-10 11:35:20 -04:00
2023-02-09 16:49:32 -05:00
void FMeshPlaneCut : : SplitCrossingEdges ( TArray < double > & Signs , TSet < int > & ZeroEdges , TSet < int > & OnCutEdges , TSet < int > & OnSplitEdges , bool bDeleteTrisOnPlane )
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
SplitCrossingEdges ( bDeleteTrisOnPlane , Signs , ZeroEdges , OnCutEdges , & OnSplitEdges , & OnCutVertices , nullptr ) ;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
void FMeshPlaneCut : : SplitCrossingEdges ( TArray < double > & Signs , TSet < int > & ZeroEdges , TSet < int > & OnCutEdges , bool bDeleteTrisOnPlane )
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
SplitCrossingEdges ( bDeleteTrisOnPlane , Signs , ZeroEdges , OnCutEdges , nullptr , & OnCutVertices , nullptr ) ;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
2019-12-19 18:07:47 -05:00
bool FMeshPlaneCut : : CutWithoutDelete ( bool bSplitVerticesAtPlane , float OffsetVertices , TDynamicMeshScalarTriangleAttribute < int > * TriLabels , int NewLabelStartID , bool bAddBoundariesFirstHalf , bool bAddBoundariesSecondHalf )
{
TArray < double > Signs ;
2023-02-09 16:49:32 -05:00
TSet < int > AlreadyOnPlaneEdges , OnCutEdges ;
SplitCrossingEdges ( bSplitVerticesAtPlane , Signs , AlreadyOnPlaneEdges , OnCutEdges , nullptr , nullptr ) ;
2019-12-19 18:07:47 -05:00
if ( ! bSplitVerticesAtPlane )
{
ensure ( OffsetVertices = = 0.0 ) ; // it would be weird to not split vertices and still request any offset of the 'other side' vertices; please don't do that
}
2023-02-09 16:49:32 -05:00
// collapse degenerate edges
2019-12-19 18:07:47 -05:00
if ( bCollapseDegenerateEdgesOnCut )
{
2023-02-09 16:49:32 -05:00
CollapseDegenerateEdges ( OnCutEdges , false ) ;
}
if ( bSimplifyAlongNewEdges )
{
SimplifySettings . SimplifyAlongEdges ( * Mesh , OnCutEdges ) ;
2019-12-19 18:07:47 -05:00
}
2022-08-23 19:25:08 -04:00
if ( ! bSplitVerticesAtPlane )
{
return true ;
}
2023-02-09 16:49:32 -05:00
if ( ! ensure ( TriLabels ) )
{
return false ; // need labels to split verts currently
}
2022-08-23 19:25:08 -04:00
2023-02-09 16:49:32 -05:00
// Update label IDs for triangles on the positive side of the plane, and shift vertices by the offset vector
2019-12-19 18:07:47 -05:00
TMap < int , int > OldLabelToNew ;
int AvailableID = NewLabelStartID ;
FVector3d VertexOffsetVec = ( double ) OffsetVertices * PlaneNormal ;
for ( int VID : Mesh - > VertexIndicesItr ( ) )
{
if ( VID < Signs . Num ( ) & & Signs [ VID ] > PlaneTolerance )
{
Mesh - > SetVertex ( VID , Mesh - > GetVertex ( VID ) + VertexOffsetVec ) ;
for ( int TID : Mesh - > VtxTrianglesItr ( VID ) )
{
int LabelID = TriLabels - > GetValue ( TID ) ;
if ( LabelID > = NewLabelStartID )
{
continue ;
}
if ( ! OldLabelToNew . Contains ( LabelID ) )
{
OldLabelToNew . Add ( LabelID , AvailableID + + ) ;
}
TriLabels - > SetValue ( TID , OldLabelToNew [ LabelID ] ) ;
}
}
}
if ( ! bSplitVerticesAtPlane )
{
return true ;
}
// split the mesh apart and add open boundary info
TMap < int , int > SplitVertices ;
TSet < int > BoundaryVertices ;
2023-02-09 16:49:32 -05:00
const TSet < int > * Sets [ 2 ] { & AlreadyOnPlaneEdges , & OnCutEdges } ;
2019-12-19 18:07:47 -05:00
for ( int SetIdx = 0 ; SetIdx < 2 ; SetIdx + + )
{
const TSet < int > & Set = * ( Sets [ SetIdx ] ) ;
for ( int EID : Set )
{
if ( ! Mesh - > IsEdge ( EID ) )
{
continue ;
}
2020-06-23 18:40:00 -04:00
const FDynamicMesh3 : : FEdge Edge = Mesh - > GetEdge ( EID ) ;
BoundaryVertices . Add ( Edge . Vert [ 0 ] ) ;
BoundaryVertices . Add ( Edge . Vert [ 1 ] ) ;
2019-12-19 18:07:47 -05:00
}
}
TArray < int > Triangles ;
DynamicMeshInfo : : FVertexSplitInfo SplitInfo ;
for ( int VID : BoundaryVertices )
{
if ( ! ensure ( Mesh - > IsVertex ( VID ) ) ) // should not have invalid vertices in BoundaryVertices
{
continue ;
}
Triangles . Reset ( ) ;
int NonSplitTriCount = 0 ;
for ( int TID : Mesh - > VtxTrianglesItr ( VID ) )
{
if ( TriLabels - > GetValue ( TID ) > = NewLabelStartID )
{
Triangles . Add ( TID ) ;
}
else
{
NonSplitTriCount + + ;
}
}
2023-02-09 16:49:32 -05:00
if ( NonSplitTriCount > 0 ) // connected to old labels
2019-12-19 18:07:47 -05:00
{
2023-02-09 16:49:32 -05:00
// if also connected to new labels, needs split
2020-01-27 20:11:15 -05:00
if ( Triangles . Num ( ) > 0 & & EMeshResult : : Ok = = Mesh - > SplitVertex ( VID , Triangles , SplitInfo ) )
2019-12-19 18:07:47 -05:00
{
SplitVertices . Add ( VID , SplitInfo . NewVertex ) ;
Mesh - > SetVertex ( SplitInfo . NewVertex , Mesh - > GetVertex ( SplitInfo . NewVertex ) + VertexOffsetVec ) ;
}
}
else if ( Signs [ VID ] < = PlaneTolerance ) // wasn't already offset and has no connections to 'old' labels -- needs offset
{
Mesh - > SetVertex ( VID , Mesh - > GetVertex ( VID ) + VertexOffsetVec ) ;
}
}
// if boundary loops are requested for either or both sides of the cut, extract + label them
bool AllExtractionsOk = true ;
if ( bAddBoundariesFirstHalf | | bAddBoundariesSecondHalf )
{
// organize edges by label and transfer ZeroEdges and OnCutEdges to newly split edges
TMap < int , TSet < int > > LabelToCutEdges ;
for ( int SetIdx = 0 ; SetIdx < 2 ; SetIdx + + )
{
const TSet < int > & Set = * ( Sets [ SetIdx ] ) ;
for ( int EID : Set )
{
if ( ! Mesh - > IsEdge ( EID ) )
{
continue ;
}
2020-06-23 18:40:00 -04:00
const FDynamicMesh3 : : FEdge Edge = Mesh - > GetEdge ( EID ) ;
if ( Edge . Tri [ 1 ] > = 0 ) // only care about boundary edges
2019-12-19 18:07:47 -05:00
{
continue ;
}
{
2020-06-23 18:40:00 -04:00
int LabelID = TriLabels - > GetValue ( Edge . Tri [ 0 ] ) ;
2019-12-19 18:07:47 -05:00
TSet < int > & LabelCutEdges = LabelToCutEdges . FindOrAdd ( LabelID ) ;
LabelCutEdges . Add ( EID ) ;
}
if ( bAddBoundariesSecondHalf )
{
// try to find and add the corresponding edge
2020-06-23 18:40:00 -04:00
const int * SplitA = SplitVertices . Find ( Edge . Vert [ 0 ] ) ;
const int * SplitB = SplitVertices . Find ( Edge . Vert [ 1 ] ) ;
2019-12-19 18:07:47 -05:00
if ( SplitA & & SplitB )
{
int CorrEID = Mesh - > FindEdge ( * SplitA , * SplitB ) ;
if ( CorrEID > = 0 ) // corresponding edge exists
{
2020-06-23 18:40:00 -04:00
FDynamicMesh3 : : FEdge CorrEdge = Mesh - > GetEdge ( CorrEID ) ;
if ( CorrEdge . Tri [ 1 ] < 0 ) // we only care if it's a boundary edge
2019-12-19 18:07:47 -05:00
{
2020-06-23 18:40:00 -04:00
int LabelID = TriLabels - > GetValue ( CorrEdge . Tri [ 0 ] ) ;
2019-12-19 18:07:47 -05:00
TSet < int > & LabelCutEdges = LabelToCutEdges . FindOrAdd ( LabelID ) ;
LabelCutEdges . Add ( CorrEID ) ;
}
}
}
}
2020-06-23 18:40:00 -04:00
BoundaryVertices . Add ( Edge . Vert [ 0 ] ) ;
BoundaryVertices . Add ( Edge . Vert [ 1 ] ) ;
2019-12-19 18:07:47 -05:00
}
}
2023-02-09 16:49:32 -05:00
TSet < int > UnusedZeroEdgesSet ; // This set is unused except to pass to ExtractBoundaryLoops, which expects two sets of edges
2019-12-19 18:07:47 -05:00
for ( TPair < int , TSet < int > > & LabelIDEdges : LabelToCutEdges )
{
int LabelID = LabelIDEdges . Key ;
if ( ! bAddBoundariesFirstHalf & & LabelID < NewLabelStartID )
{
continue ;
}
if ( ! bAddBoundariesSecondHalf & & LabelID > = NewLabelStartID )
{
continue ;
}
TSet < int > & Edges = LabelIDEdges . Value ;
FMeshPlaneCut : : FOpenBoundary & Boundary = OpenBoundaries . Emplace_GetRef ( ) ;
Boundary . Label = LabelID ;
if ( LabelID > = NewLabelStartID )
{
Boundary . NormalSign = - 1 ;
}
bool ExtractOk = ExtractBoundaryLoops ( Edges , UnusedZeroEdgesSet , Boundary ) ;
AllExtractionsOk = ExtractOk & & AllExtractionsOk ;
}
}
return AllExtractionsOk ;
}
bool FMeshPlaneCut : : Cut ( )
{
TArray < double > Signs ;
TSet < int > ZeroEdges , OnCutEdges ;
2023-02-09 16:49:32 -05:00
SplitCrossingEdges ( true , Signs , ZeroEdges , OnCutEdges , nullptr , nullptr ) ;
2019-12-19 18:07:47 -05:00
// remove one-rings of all positive-side vertices.
2023-02-09 16:49:32 -05:00
for ( int VID = 0 ; VID < Signs . Num ( ) ; + + VID )
2019-12-19 18:07:47 -05:00
{
2023-02-09 16:49:32 -05:00
if ( Signs [ VID ] > PlaneTolerance )
2019-09-10 11:35:20 -04:00
{
2021-10-12 21:21:22 -04:00
constexpr bool bPreserveManifold = false ;
Mesh - > RemoveVertex ( VID , bPreserveManifold ) ;
2019-09-10 11:35:20 -04:00
}
}
// collapse degenerate edges if we got em
if ( bCollapseDegenerateEdgesOnCut )
{
2023-02-09 16:49:32 -05:00
CollapseDegenerateEdges ( OnCutEdges , false ) ;
}
if ( bSimplifyAlongNewEdges )
{
SimplifySettings . SimplifyAlongEdges ( * Mesh , OnCutEdges ) ;
2019-09-10 11:35:20 -04:00
}
2019-12-19 18:07:47 -05:00
FMeshPlaneCut : : FOpenBoundary & Boundary = OpenBoundaries . Emplace_GetRef ( ) ;
return ExtractBoundaryLoops ( OnCutEdges , ZeroEdges , Boundary ) ;
2019-09-10 11:35:20 -04:00
2019-12-19 18:07:47 -05:00
}
2023-02-09 16:49:32 -05:00
bool FMeshPlaneCut : : SplitEdgesOnlyHelper ( bool bAssignNewGroups , TSet < int32 > * TriangleSelection , bool bAddDeprecatedResultSeedTriangles )
2020-01-27 20:11:15 -05:00
{
// split edges with current plane
TArray < double > Signs ;
2021-06-04 19:26:45 -04:00
TSet < int32 > ZeroEdges , OnCutEdges , OnSplitEdges ;
2023-02-09 16:49:32 -05:00
SplitCrossingEdges ( false , Signs , ZeroEdges , OnCutEdges , & OnSplitEdges , nullptr , TriangleSelection ) ;
2020-01-27 20:11:15 -05:00
if ( bAssignNewGroups = = false )
{
return true ;
}
2023-02-09 16:49:32 -05:00
if ( bSimplifyAlongNewEdges )
{
if ( ensureMsgf ( ! bAddDeprecatedResultSeedTriangles , TEXT ( " Deprecated SplitEdgesOnly without TriangleSelection parameter does not support simplification option " ) ) )
{
// TODO: Consider making the simplification also preserve the TriangleSelection boundary
CollapseDegenerateEdges ( OnCutEdges , false , TriangleSelection ) ;
if ( TriangleSelection )
{
SimplifySettings . SimplifyAlongEdges ( * Mesh , OnCutEdges , [ & TriangleSelection ] ( const DynamicMeshInfo : : FEdgeCollapseInfo & CollapseInfo )
{
TriangleSelection - > Remove ( CollapseInfo . RemovedTris . A ) ;
if ( CollapseInfo . RemovedTris . B ! = FDynamicMesh3 : : InvalidID )
{
TriangleSelection - > Remove ( CollapseInfo . RemovedTris . B ) ;
}
} ) ;
}
else
{
SimplifySettings . SimplifyAlongEdges ( * Mesh , OnCutEdges ) ;
}
}
}
TSet < int32 > OnPlaneEdges = ZeroEdges ;
OnPlaneEdges . Append ( OnCutEdges ) ;
// Use the edges along the cut (both pre-existing and newly added) to assign group IDs across both sides of the triangulation
2020-01-27 20:11:15 -05:00
TArray < int32 > CutEdgeTriangles ; // triangles connected to those edges
TSet < int32 > CutEdgeGroups ; // group IDs of those triangles (ie groups touching cut)
2023-02-09 16:49:32 -05:00
for ( int32 EID : OnPlaneEdges )
2020-01-27 20:11:15 -05:00
{
2023-02-09 16:49:32 -05:00
FIndex2i EdgeTris = Mesh - > GetEdgeT ( EID ) ;
for ( int32 j = 0 ; j < 2 ; + + j )
2020-01-27 20:11:15 -05:00
{
2023-02-09 16:49:32 -05:00
if ( EdgeTris [ j ] ! = FDynamicMesh3 : : InvalidID )
2020-01-27 20:11:15 -05:00
{
2023-02-09 16:49:32 -05:00
CutEdgeTriangles . Add ( EdgeTris [ j ] ) ;
int32 Group = Mesh - > GetTriangleGroup ( EdgeTris [ j ] ) ;
CutEdgeGroups . Add ( Group ) ;
2020-01-27 20:11:15 -05:00
}
}
}
// find group-connected-components touching cut, but split each group on either side of the cut into a separate component
FMeshConnectedComponents GroupRegions ( Mesh ) ;
GroupRegions . FindTrianglesConnectedToSeeds ( CutEdgeTriangles , [ & ] ( int32 t0 , int32 t1 ) {
int32 Group0 = Mesh - > GetTriangleGroup ( t0 ) ;
int32 Group1 = Mesh - > GetTriangleGroup ( t1 ) ;
if ( Group0 = = Group1 )
{
int32 SharedEdge = Mesh - > FindEdgeFromTriPair ( t0 , t1 ) ;
2023-02-09 16:49:32 -05:00
if ( OnPlaneEdges . Contains ( SharedEdge ) = = false )
2020-01-27 20:11:15 -05:00
{
return true ;
}
}
return false ;
} ) ;
2023-02-09 16:49:32 -05:00
// Assign a new group ID for each component
2020-01-27 20:11:15 -05:00
// Do we want to keep existing groups? possibly cleaner to assign new ones because one input group may
// be split into multiple child groups on each side of the cut.
// But perhaps should track group-mapping?
ResultRegions . Reset ( ) ;
ResultRegions . Reserve ( GroupRegions . Num ( ) ) ;
for ( FMeshConnectedComponents : : FComponent & Component : GroupRegions )
{
int32 NewGroup = Mesh - > AllocateTriangleGroup ( ) ;
for ( int tid : Component . Indices )
{
Mesh - > SetTriangleGroup ( tid , NewGroup ) ;
}
FCutResultRegion & Result = ResultRegions . Emplace_GetRef ( ) ;
Result . GroupID = NewGroup ;
Result . Triangles = MoveTemp ( Component . Indices ) ;
}
2023-02-09 16:49:32 -05:00
if ( ! bAddDeprecatedResultSeedTriangles )
{
return true ;
}
// Note: the below logic will be removed when ResultSeedTriangles is removed
PRAGMA_DISABLE_DEPRECATION_WARNINGS
2021-06-04 19:26:45 -04:00
// Compute the set of triangle IDs in the cut mesh that represent
// the original/seed triangle selection along the cut. This assumes
// that the edge filter function contains edges that originate
// from source triangles.
ResultSeedTriangles . Reset ( ) ;
for ( int tid : CutEdgeTriangles )
{
// TODO: We currently assume that all cut edges are on the interior of
// our seed triangles, thus all tris adjacent to the cut edge are seed
// triangles. This assumption fails in the following edge case.
//
// o---o---o
// |xx/ \xx|
// |x/ \x| <---> Cut plane
// |/ \|
// o-------o x = Seed tris
//
// CutEdges filters the OnCutEdges list by checking if both ends of the edge
// are OnCutVertices. In this scenario, the edge introduced across the non
// seed triangle on the bottom is included.
ResultSeedTriangles . Add ( tid ) ;
// Walk the edges of the CutEdgeTriangles, skipping seed, split & cut edges,
// to identify extra interior edges. Both triangles along that interior
// edge are also seed triangles.
FIndex3i TriEdges = Mesh - > GetTriEdges ( tid ) ;
for ( int j = 0 ; j < 3 ; j + + )
{
int eid = TriEdges [ j ] ;
FIndex2i ev = Mesh - > GetEdgeV ( eid ) ;
bool bIsSeedEdge = ( EdgeFilterFunc & & EdgeFilterFunc ( eid ) ) ;
2023-02-09 16:49:32 -05:00
if ( bIsSeedEdge | | OnCutEdges . Contains ( eid ) | | OnSplitEdges . Contains ( eid ) )
2021-06-04 19:26:45 -04:00
{
continue ;
}
FIndex2i EdgeTris = Mesh - > GetEdgeT ( eid ) ;
ResultSeedTriangles . Add ( EdgeTris . A ) ;
ResultSeedTriangles . Add ( EdgeTris . B ) ;
}
}
2023-02-09 16:49:32 -05:00
PRAGMA_ENABLE_DEPRECATION_WARNINGS
2020-01-27 20:11:15 -05:00
return true ;
}
2019-12-19 18:07:47 -05:00
bool FMeshPlaneCut : : ExtractBoundaryLoops ( const TSet < int > & OnCutEdges , const TSet < int > & ZeroEdges , FMeshPlaneCut : : FOpenBoundary & Boundary )
{
2019-09-10 11:35:20 -04:00
// ok now we extract boundary loops, but restricted
// to either the zero-edges we found, or the edges we created! bang!!
2019-12-19 18:07:47 -05:00
2019-09-10 11:35:20 -04:00
FMeshBoundaryLoops Loops ( Mesh , false ) ;
Loops . EdgeFilterFunc = [ & OnCutEdges , & ZeroEdges ] ( int EID )
{
return OnCutEdges . Contains ( EID ) | | ZeroEdges . Contains ( EID ) ;
} ;
bool bFoundLoops = Loops . Compute ( ) ;
if ( bFoundLoops )
{
2019-12-19 18:07:47 -05:00
Boundary . CutLoops = Loops . Loops ;
Boundary . CutSpans = Loops . Spans ;
Boundary . CutLoopsFailed = false ;
Boundary . FoundOpenSpans = Boundary . CutSpans . Num ( ) > 0 ;
2019-09-10 11:35:20 -04:00
}
else
{
2019-12-19 18:07:47 -05:00
Boundary . CutLoops . Empty ( ) ;
Boundary . CutLoopsFailed = true ;
2019-09-10 11:35:20 -04:00
}
2019-12-19 18:07:47 -05:00
return ! Boundary . CutLoopsFailed ;
2019-09-10 11:35:20 -04:00
}
2023-02-09 16:49:32 -05:00
PRAGMA_DISABLE_DEPRECATION_WARNINGS
2019-09-10 11:35:20 -04:00
void FMeshPlaneCut : : CollapseDegenerateEdges ( const TSet < int > & OnCutEdges , const TSet < int > & ZeroEdges )
{
2019-12-19 18:07:47 -05:00
const TSet < int > * Sets [ 2 ] { & OnCutEdges , & ZeroEdges } ;
2019-09-10 11:35:20 -04:00
double Tol2 = DegenerateEdgeTol * DegenerateEdgeTol ;
FVector3d A , B ;
int Collapsed = 0 ;
do
{
Collapsed = 0 ;
for ( int SetIdx = 0 ; SetIdx < 2 ; SetIdx + + )
{
2019-12-19 18:07:47 -05:00
const TSet < int > & Set = * ( Sets [ SetIdx ] ) ;
for ( int EID : Set )
2019-09-10 11:35:20 -04:00
{
if ( ! Mesh - > IsEdge ( EID ) )
{
continue ;
}
Mesh - > GetEdgeV ( EID , A , B ) ;
2021-03-30 21:25:22 -04:00
if ( DistanceSquared ( A , B ) > Tol2 )
2019-09-10 11:35:20 -04:00
{
continue ;
}
FIndex2i EV = Mesh - > GetEdgeV ( EID ) ;
2019-10-01 20:41:42 -04:00
// if the vertex we'd remove is on a seam, try removing the other one instead
if ( Mesh - > HasAttributes ( ) & & Mesh - > Attributes ( ) - > IsSeamVertex ( EV . B , false ) )
{
Swap ( EV . A , EV . B ) ;
// if they were both on seams, then collapse should not happen? (& would break OnCollapseEdge assumptions in overlay)
if ( Mesh - > HasAttributes ( ) & & Mesh - > Attributes ( ) - > IsSeamVertex ( EV . B , false ) )
{
continue ;
}
}
2019-09-10 11:35:20 -04:00
FDynamicMesh3 : : FEdgeCollapseInfo CollapseInfo ;
EMeshResult Result = Mesh - > CollapseEdge ( EV . A , EV . B , CollapseInfo ) ;
if ( Result = = EMeshResult : : Ok )
{
Collapsed + + ;
}
}
}
} while ( Collapsed ! = 0 ) ;
}
2023-02-09 16:49:32 -05:00
PRAGMA_ENABLE_DEPRECATION_WARNINGS
2019-09-10 11:35:20 -04:00
2023-02-09 16:49:32 -05:00
void FMeshPlaneCut : : CollapseDegenerateEdges ( TSet < int32 > & Edges , bool bRemoveAllDegenerateFromInputSet , TSet < int > * TriangleSelection )
{
double Tol2 = DegenerateEdgeTol * DegenerateEdgeTol ;
FVector3d A , B ;
TArray < int32 > ToRemove ;
int Collapsed = 0 ;
do
{
Collapsed = 0 ;
for ( auto EdgesIter = Edges . CreateIterator ( ) ; EdgesIter ; + + EdgesIter )
{
int32 EID = * EdgesIter ;
if ( ! Mesh - > IsEdge ( EID ) )
{
continue ;
}
Mesh - > GetEdgeV ( EID , A , B ) ;
if ( DistanceSquared ( A , B ) > Tol2 )
{
continue ;
}
FIndex2i EV = Mesh - > GetEdgeV ( EID ) ;
// if the vertex we'd remove is on a seam, try removing the other one instead
if ( Mesh - > HasAttributes ( ) & & Mesh - > Attributes ( ) - > IsSeamVertex ( EV . B , false ) )
{
Swap ( EV . A , EV . B ) ;
// if they were both on seams, then collapse should not happen? (& would break OnCollapseEdge assumptions in overlay)
if ( Mesh - > HasAttributes ( ) & & Mesh - > Attributes ( ) - > IsSeamVertex ( EV . B , false ) )
{
continue ;
}
}
FDynamicMesh3 : : FEdgeCollapseInfo CollapseInfo ;
EMeshResult Result = Mesh - > CollapseEdge ( EV . A , EV . B , CollapseInfo ) ;
if ( Result = = EMeshResult : : Ok )
{
Collapsed + + ;
EdgesIter . RemoveCurrent ( ) ;
if ( TriangleSelection )
{
TriangleSelection - > Remove ( CollapseInfo . RemovedEdges . A ) ;
if ( CollapseInfo . RemovedEdges . B ! = FDynamicMesh3 : : InvalidID )
{
TriangleSelection - > Remove ( CollapseInfo . RemovedEdges . B ) ;
}
}
// Add the other edge for later removal, so we don't potentially invalidate our iterator
// (but only if we really need to remove invalid edges here; some algorithms may be ok with leaving invalid EIDs in the set)
if ( bRemoveAllDegenerateFromInputSet & & CollapseInfo . RemovedEdges . B ! = FDynamicMesh3 : : InvalidID )
{
ToRemove . Add ( CollapseInfo . RemovedEdges . B ) ;
}
}
}
} while ( Collapsed ! = 0 ) ;
if ( bRemoveAllDegenerateFromInputSet )
{
for ( int32 EID : ToRemove )
{
Edges . Remove ( EID ) ;
}
}
}
2019-09-10 11:35:20 -04:00
bool FMeshPlaneCut : : SimpleHoleFill ( int ConstantGroupID )
{
bool bAllOk = true ;
HoleFillTriangles . Empty ( ) ;
2019-12-19 18:07:47 -05:00
for ( FOpenBoundary & Boundary : OpenBoundaries )
2019-09-10 11:35:20 -04:00
{
2020-04-18 18:42:59 -04:00
TArray < int > & BoundaryFillTriangles = HoleFillTriangles . Emplace_GetRef ( ) ;
2019-12-19 18:07:47 -05:00
FFrame3d ProjectionFrame ( PlaneOrigin , PlaneNormal ) ;
2019-09-10 11:35:20 -04:00
2019-12-19 18:07:47 -05:00
for ( const FEdgeLoop & Loop : Boundary . CutLoops )
2019-09-10 11:35:20 -04:00
{
2019-12-19 18:07:47 -05:00
FSimpleHoleFiller Filler ( Mesh , Loop ) ;
int GID = ConstantGroupID > = 0 ? ConstantGroupID : Mesh - > AllocateTriangleGroup ( ) ;
bAllOk = Filler . Fill ( GID ) & & bAllOk ;
BoundaryFillTriangles . Append ( Filler . NewTriangles ) ;
2019-06-14 13:15:42 -04:00
if ( Mesh - > HasAttributes ( ) )
{
FDynamicMeshEditor Editor ( Mesh ) ;
2019-12-19 18:07:47 -05:00
Editor . SetTriangleNormals ( Filler . NewTriangles , ( FVector3f ) PlaneNormal * Boundary . NormalSign ) ;
2019-06-14 13:15:42 -04:00
Editor . SetTriangleUVsFromProjection ( Filler . NewTriangles , ProjectionFrame , UVScaleFactor ) ;
}
2019-09-10 11:35:20 -04:00
}
}
return bAllOk ;
}
2020-04-18 18:42:59 -04:00
bool FMeshPlaneCut : : MinimalHoleFill ( int ConstantGroupID )
{
bool bAllOk = true ;
HoleFillTriangles . Empty ( ) ;
for ( FOpenBoundary & Boundary : OpenBoundaries )
{
TArray < int > & BoundaryFillTriangles = HoleFillTriangles . Emplace_GetRef ( ) ;
FFrame3d ProjectionFrame ( PlaneOrigin , PlaneNormal ) ;
for ( const FEdgeLoop & Loop : Boundary . CutLoops )
{
FMinimalHoleFiller Filler ( Mesh , Loop ) ;
int GID = ConstantGroupID > = 0 ? ConstantGroupID : Mesh - > AllocateTriangleGroup ( ) ;
bAllOk = Filler . Fill ( GID ) & & bAllOk ;
BoundaryFillTriangles . Append ( Filler . NewTriangles ) ;
if ( Mesh - > HasAttributes ( ) )
{
FDynamicMeshEditor Editor ( Mesh ) ;
Editor . SetTriangleNormals ( Filler . NewTriangles , ( FVector3f ) PlaneNormal * Boundary . NormalSign ) ;
Editor . SetTriangleUVsFromProjection ( Filler . NewTriangles , ProjectionFrame , UVScaleFactor ) ;
}
}
}
return bAllOk ;
}
2024-08-22 16:14:26 -04:00
bool FMeshPlaneCut : : HoleFill ( TFunction < TArray < FIndex3i > ( const FGeneralPolygon2d & ) > PlanarTriangulationFunc , bool bFillSpans , int ConstantGroupID , int MaterialID )
2019-09-10 11:35:20 -04:00
{
2019-12-19 18:07:47 -05:00
bool bAllOk = true ;
2019-09-10 11:35:20 -04:00
2019-12-19 18:07:47 -05:00
HoleFillTriangles . Empty ( ) ;
for ( FMeshPlaneCut : : FOpenBoundary & Boundary : OpenBoundaries )
2019-09-10 11:35:20 -04:00
{
2019-12-19 18:07:47 -05:00
TArray < TArray < int > > LoopVertices ;
for ( const FEdgeLoop & Loop : Boundary . CutLoops )
2019-09-10 11:35:20 -04:00
{
2019-12-19 18:07:47 -05:00
LoopVertices . Add ( Loop . Vertices ) ;
}
if ( bFillSpans )
{
for ( const FEdgeSpan & Span : Boundary . CutSpans )
{
LoopVertices . Add ( Span . Vertices ) ;
}
}
2021-03-30 21:25:22 -04:00
FVector3d SignedPlaneNormal = PlaneNormal * ( double ) Boundary . NormalSign ;
FPlanarHoleFiller Filler ( Mesh , & LoopVertices , PlanarTriangulationFunc , PlaneOrigin , SignedPlaneNormal ) ;
2019-12-19 18:07:47 -05:00
int GID = ConstantGroupID > = 0 ? ConstantGroupID : Mesh - > AllocateTriangleGroup ( ) ;
bool bFullyFilledHole = Filler . Fill ( GID ) ;
if ( Mesh - > HasAttributes ( ) )
{
FDynamicMeshEditor Editor ( Mesh ) ;
2021-03-30 21:25:22 -04:00
Editor . SetTriangleNormals ( Filler . NewTriangles , ( FVector3f ) ( SignedPlaneNormal ) ) ;
2019-12-19 18:07:47 -05:00
2021-03-30 21:25:22 -04:00
FFrame3d ProjectionFrame ( PlaneOrigin , SignedPlaneNormal ) ;
2019-12-19 18:07:47 -05:00
for ( int UVLayerIdx = 0 , NumLayers = Mesh - > Attributes ( ) - > NumUVLayers ( ) ; UVLayerIdx < NumLayers ; UVLayerIdx + + )
{
2020-01-27 20:11:15 -05:00
Editor . SetTriangleUVsFromProjection ( Filler . NewTriangles , ProjectionFrame , UVScaleFactor , FVector2f : : Zero ( ) , true , UVLayerIdx ) ;
2019-12-19 18:07:47 -05:00
}
2024-08-22 16:14:26 -04:00
if ( MaterialID > - 1 & & Mesh - > Attributes ( ) - > HasMaterialID ( ) )
{
FDynamicMeshMaterialAttribute * MaterialAttrib = Mesh - > Attributes ( ) - > GetMaterialID ( ) ;
for ( int32 TID : Filler . NewTriangles )
{
MaterialAttrib - > SetValue ( TID , MaterialID ) ;
}
}
2019-12-19 18:07:47 -05:00
}
2024-08-22 16:14:26 -04:00
HoleFillTriangles . Add ( MoveTemp ( Filler . NewTriangles ) ) ;
2019-12-19 18:07:47 -05:00
bAllOk = bAllOk & & bFullyFilledHole ;
}
return bAllOk ;
}
void FMeshPlaneCut : : TransferTriangleLabelsToHoleFillTriangles ( TDynamicMeshScalarTriangleAttribute < int > * TriLabels )
{
if ( ! ensure ( OpenBoundaries . Num ( ) = = HoleFillTriangles . Num ( ) ) )
{
return ;
}
for ( int BoundaryIdx = 0 ; BoundaryIdx < OpenBoundaries . Num ( ) ; BoundaryIdx + + )
{
const TArray < int > & Triangles = HoleFillTriangles [ BoundaryIdx ] ;
const FOpenBoundary & Boundary = OpenBoundaries [ BoundaryIdx ] ;
for ( int TID : Triangles )
{
TriLabels - > SetValue ( TID , Boundary . Label ) ;
2019-09-10 11:35:20 -04:00
}
}
}