2020-04-18 18:42:59 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
// Port of geometry3Sharp MeshBoolean
# include "Operations/MeshBoolean.h"
# include "Operations/MeshMeshCut.h"
2020-12-07 15:18:20 -04:00
# include "Selections/MeshConnectedComponents.h"
2020-04-18 18:42:59 -04:00
# include "Async/ParallelFor.h"
2021-06-13 00:36:02 -04:00
# include "DynamicMesh/MeshTransforms.h"
2021-10-14 21:33:35 -04:00
# include "DynamicMesh/MeshNormals.h"
2020-11-09 17:15:51 -04:00
# include "Spatial/SparseDynamicOctree3.h"
2020-04-18 18:42:59 -04:00
# include "Algo/RemoveIf.h"
2021-06-13 00:36:02 -04:00
# include "DynamicMesh/DynamicMeshAABBTree3.h"
2020-04-18 18:42:59 -04:00
2021-03-09 19:33:56 -04:00
using namespace UE : : Geometry ;
2020-06-23 18:40:00 -04:00
// TODO: Commented out below is a custom thick triangle intersection.
// It's much better at finding all the near-tolerance collision edges.
// But it ends up creating harder problems downstream in terms of
// tiny edges, almost-exactly-at-tolerance coplanar faces, etc.
// Consider re-enabling it in combination with more robust downstream processing!
//// helper for ThickTriTriIntersection, using the plane of Tri0 as reference
//// (factored out so we can try using both planes as reference, and make the result less dependent on triangle ordering)
//bool ThickTriTriHelper(const FTriangle3d& Tri0, const FTriangle3d& Tri1, const FPlane3d& Plane0,
// const FVector3d& IntersectionDir, const FVector3d& dist1, const FIndex3i& sign1,
// int pos1, int neg1, int zero1,
// FIntrTriangle3Triangle3d& Intr, double Tolerance)
//{
// int SegmentsFound = 0;
// int PtsFound[2]{ 0,0 }; // points found on the positive and negative sides
// FVector3d CrossingPts[4]; // space for triangle-plane intersection segments; with negative-side endpoints first
// int PtsSide[4]; // -1, 1 or 0
//
// // offset tolerance -- used to accept intersections off the plane, when we'd otherwise miss "near intersections"
// double ToleranceOffset = Tolerance * .99; // scale down to be extra sure not to create un-snappable geometry
// // only accept 'offset plane' points if not doing so would miss a much-larger-than-tolerance edge
// double AcceptOffsetPointsThresholdSq = 1e-2 * 1e-2;
//
// double InPlaneTolerance = FMathd::ZeroTolerance;
//
// // consider all crossings
// for (int i = 0, lasti = 2; i < 3; lasti = i++)
// {
// if (sign1[lasti] == sign1[i])
// {
// continue;
// }
// //
// if (sign1[lasti] == 0 || sign1[i] == 0)
// {
// int nzi = lasti, zi = i;
// if (sign1[lasti] == 0)
// {
// nzi = i;
// zi = lasti;
// }
// int SideIdx = (sign1[nzi] + 1) / 2;
// int PtIdx = SideIdx * 2 + PtsFound[SideIdx];
// int Side = sign1[nzi];
//
// double ParamOnTolPlane = (dist1[nzi] - sign1[nzi] * ToleranceOffset) / (dist1[nzi] - dist1[zi]);
// FVector3d IntrPt;
// if (ParamOnTolPlane < 1)
// {
// IntrPt = Tri1.V[nzi] + (Tri1.V[zi] - Tri1.V[nzi]) * ParamOnTolPlane;
// if (IntrPt.DistanceSquared(Tri1.V[zi]) < AcceptOffsetPointsThresholdSq)
// {
// Side = 0;
// IntrPt = Tri1.V[zi];
// }
// }
// else
// {
// IntrPt = Tri1.V[zi];
// }
//
// // record crossing pt
// PtsSide[PtIdx] = Side;
// CrossingPts[PtIdx] = IntrPt;
// PtsFound[SideIdx]++;
// }
// else
// {
// double OffsetParamDiff = sign1[i] * ToleranceOffset / (dist1[i] - dist1[lasti]);
// FVector3d Edge = Tri1.V[lasti] - Tri1.V[i];
// double OffsetDSq = Edge.SquaredLength() * OffsetParamDiff * OffsetParamDiff;
// if (OffsetDSq < AcceptOffsetPointsThresholdSq)
// {
// FVector3d IntrPt = Tri1.V[i] + Edge * dist1[i] / (dist1[i] - dist1[lasti]);
// for (int SideIdx = 0; SideIdx < 2; SideIdx++)
// {
// int PtIdx = SideIdx * 2 + PtsFound[SideIdx];
// PtsSide[PtIdx] = 0;
// CrossingPts[PtIdx] = IntrPt;
// PtsFound[SideIdx]++;
// }
// }
// else
// {
// for (int Sign = -1; Sign <= 1; Sign += 2)
// {
// double ParamOnPlane = (dist1[i] - Sign * ToleranceOffset) / (dist1[i] - dist1[lasti]);
// FVector3d IntrPt = Tri1.V[i] + (Tri1.V[lasti] - Tri1.V[i]) * ParamOnPlane;
// int SideIdx = (Sign + 1) / 2;
// int PtIdx = SideIdx * 2 + PtsFound[SideIdx];
// PtsSide[PtIdx] = Sign;
// CrossingPts[PtIdx] = IntrPt;
// PtsFound[SideIdx]++;
// }
// }
// }
// }
//
// bool bMadeZeroEdge = false;
// int AddedPts = 0;
// for (int SideIdx = 0; SideIdx < 2; SideIdx++)
// {
// if (PtsFound[SideIdx] == 2)
// {
// int PtIdx0 = SideIdx * 2;
// if (PtsSide[PtIdx0] == 0 && PtsSide[PtIdx0 + 1] == 0)
// {
// if (bMadeZeroEdge)
// {
// continue;
// }
// bMadeZeroEdge = true;
// }
// FVector3d OutA, OutB;
// int IntrQ = FIntrTriangle3Triangle3d::IntersectTriangleWithCoplanarSegment(Plane0, Tri0, CrossingPts[PtIdx0], CrossingPts[PtIdx0 + 1], OutA, OutB, InPlaneTolerance);
//
// if (IntrQ == 2)
// {
// Intr.Points[AddedPts++] = OutA;
// Intr.Points[AddedPts++] = OutB;
// }
// }
// }
// Intr.Quantity = AddedPts;
// if (AddedPts == 4)
// {
// Intr.Result = EIntersectionResult::Intersects;
// Intr.Type = EIntersectionType::MultiSegment;
// }
// else if (AddedPts == 2)
// {
// Intr.Result = EIntersectionResult::Intersects;
// Intr.Type = EIntersectionType::Segment;
// }
// else
// {
// Intr.Result = EIntersectionResult::NoIntersection;
// Intr.Type = EIntersectionType::Empty;
// return false;
// }
//
// return true;
//}
//
//bool ThickTriTriIntersection(FIntrTriangle3Triangle3d& Intr, double Tolerance)
//{
// // intersection tolerance is applied one dimension at a time, so we scale down by 1/sqrt(2)
// // to ensure approximations remain within snapping distance
// Intr.SetTolerance(Tolerance);
// const FTriangle3d& Triangle0 = Intr.GetTriangle0();
// const FTriangle3d& Triangle1 = Intr.GetTriangle1();
// FPlane3d Plane0(Triangle0.V[0], Triangle0.V[1], Triangle0.V[2]);
//
// // Compute the signed distances of Triangle1 vertices to Plane0. Use an epsilon-thick plane test.
// int pos1, neg1, zero1;
// FVector3d dist1;
// FIndex3i sign1;
// FIntrTriangle3Triangle3d::TrianglePlaneRelations(Triangle1, Plane0, dist1, sign1, pos1, neg1, zero1, Tolerance);
// if (pos1 == 3 || neg1 == 3)
// {
// // ignore triangles that are more than tolerance-separated
// Intr.SetResultNone();
// return false;
// }
//
// FPlane3d Plane1(Triangle1.V[0], Triangle1.V[1], Triangle1.V[2]);
// FVector3d IntersectionDir = Plane0.Normal.Cross(Plane1.Normal);
//
// FVector3d SegA, SegB;
// bool bFound = false;
// bFound = zero1 < 3 && ThickTriTriHelper(Triangle0, Triangle1, Plane0, IntersectionDir, dist1, sign1, pos1, neg1, zero1, Intr, Tolerance);
// if (!bFound || Intr.Quantity == 1)
// {
// int pos0, neg0, zero0;
// FVector3d dist0;
// FIndex3i sign0;
// FIntrTriangle3Triangle3d::TrianglePlaneRelations(Triangle0, Plane1, dist0, sign0, pos0, neg0, zero0, Tolerance);
// bFound = zero1 < 3 && ThickTriTriHelper(Triangle1, Triangle0, Plane1, IntersectionDir, dist0, sign0, pos0, neg0, zero0, Intr, Tolerance);
// }
// if (!bFound) // make sure the Intr values are set in the coplanar case
// {
// Intr.SetResultNone();
// }
//
// return bFound;
//}
2020-04-18 18:42:59 -04:00
bool FMeshBoolean : : Compute ( )
{
// copy meshes
FDynamicMesh3 CutMeshB ( * Meshes [ 1 ] ) ;
2020-11-09 17:15:51 -04:00
if ( Result ! = Meshes [ 0 ] )
{
* Result = * Meshes [ 0 ] ;
}
2020-04-18 18:42:59 -04:00
FDynamicMesh3 * CutMesh [ 2 ] { Result , & CutMeshB } ; // just an alias to keep things organized
2020-11-30 18:20:34 -04:00
// transform the copies to a shared space (centered at the origin and scaled to a unit cube)
2021-08-23 22:08:34 -04:00
FAxisAlignedBox3d CombinedAABB ( CutMesh [ 0 ] - > GetBounds ( true ) , Transforms [ 0 ] ) ;
FAxisAlignedBox3d MeshB_AABB ( CutMesh [ 1 ] - > GetBounds ( true ) , Transforms [ 1 ] ) ;
2020-04-18 18:42:59 -04:00
CombinedAABB . Contain ( MeshB_AABB ) ;
2020-06-23 18:40:00 -04:00
double ScaleFactor = 1.0 / FMath : : Clamp ( CombinedAABB . MaxDim ( ) , 0.01 , 1000000.0 ) ;
2020-04-18 18:42:59 -04:00
for ( int MeshIdx = 0 ; MeshIdx < 2 ; MeshIdx + + )
{
2022-01-29 14:37:53 -05:00
FTransformSRT3d CenteredTransform = Transforms [ MeshIdx ] ;
2020-06-23 18:40:00 -04:00
CenteredTransform . SetTranslation ( ScaleFactor * ( CenteredTransform . GetTranslation ( ) - CombinedAABB . Center ( ) ) ) ;
CenteredTransform . SetScale ( ScaleFactor * CenteredTransform . GetScale ( ) ) ;
2020-04-18 18:42:59 -04:00
MeshTransforms : : ApplyTransform ( * CutMesh [ MeshIdx ] , CenteredTransform ) ;
2020-06-23 18:40:00 -04:00
if ( CenteredTransform . GetDeterminant ( ) < 0 )
{
CutMesh [ MeshIdx ] - > ReverseOrientation ( false ) ;
}
2020-04-18 18:42:59 -04:00
}
2022-01-29 14:37:53 -05:00
ResultTransform = FTransformSRT3d ( CombinedAABB . Center ( ) ) ;
2020-06-23 18:40:00 -04:00
ResultTransform . SetScale ( FVector3d : : One ( ) * ( 1.0 / ScaleFactor ) ) ;
2020-04-18 18:42:59 -04:00
if ( Cancelled ( ) )
{
return false ;
}
// build spatial data and use it to find intersections
FDynamicMeshAABBTree3 Spatial [ 2 ] { CutMesh [ 0 ] , CutMesh [ 1 ] } ;
2020-06-23 18:40:00 -04:00
Spatial [ 0 ] . SetTolerance ( SnapTolerance ) ;
Spatial [ 1 ] . SetTolerance ( SnapTolerance ) ;
MeshIntersection : : FIntersectionsQueryResult Intersections
= Spatial [ 0 ] . FindAllIntersections ( Spatial [ 1 ] , nullptr , IMeshSpatial : : FQueryOptions ( ) , IMeshSpatial : : FQueryOptions ( ) ,
[ this ] ( FIntrTriangle3Triangle3d & Intr )
{
Intr . SetTolerance ( SnapTolerance ) ;
return Intr . Find ( ) ;
// TODO: if we revisit "thick" tri tri collisions, this is where we'd call:
// ThickTriTriIntersection(Intr, SnapTolerance);
} ) ;
2020-04-18 18:42:59 -04:00
if ( Cancelled ( ) )
{
return false ;
}
2020-12-07 15:18:20 -04:00
bool bOpOnSingleMesh = Operation = = EBooleanOp : : TrimInside | | Operation = = EBooleanOp : : TrimOutside | | Operation = = EBooleanOp : : NewGroupInside | | Operation = = EBooleanOp : : NewGroupOutside ;
2020-12-02 15:36:50 -04:00
2020-04-18 18:42:59 -04:00
// cut the meshes
FMeshMeshCut Cut ( CutMesh [ 0 ] , CutMesh [ 1 ] ) ;
Cut . bTrackInsertedVertices = bCollapseDegenerateEdgesOnCut ; // to collect candidates to collapse
2020-12-07 15:18:20 -04:00
Cut . bMutuallyCut = ! bOpOnSingleMesh ;
2020-06-23 18:40:00 -04:00
Cut . SnapTolerance = SnapTolerance ;
2020-04-18 18:42:59 -04:00
Cut . Cut ( Intersections ) ;
if ( Cancelled ( ) )
{
return false ;
}
2020-12-07 15:18:20 -04:00
int NumMeshesToProcess = bOpOnSingleMesh ? 1 : 2 ;
2020-04-22 16:36:56 -04:00
2020-04-18 18:42:59 -04:00
// collapse tiny edges along cut boundary
if ( bCollapseDegenerateEdgesOnCut )
{
2020-06-23 18:40:00 -04:00
double DegenerateEdgeTolSq = DegenerateEdgeTolFactor * DegenerateEdgeTolFactor * SnapTolerance * SnapTolerance ;
2020-04-22 16:36:56 -04:00
for ( int MeshIdx = 0 ; MeshIdx < NumMeshesToProcess ; MeshIdx + + )
2020-04-18 18:42:59 -04:00
{
// convert vertex chains to edge IDs to simplify logic of finding remaining candidate edges after collapses
TArray < int > EIDs ;
for ( int ChainIdx = 0 ; ChainIdx < Cut . VertexChains [ MeshIdx ] . Num ( ) ; )
{
int ChainLen = Cut . VertexChains [ MeshIdx ] [ ChainIdx ] ;
int ChainEnd = ChainIdx + 1 + ChainLen ;
for ( int ChainSubIdx = ChainIdx + 1 ; ChainSubIdx + 1 < ChainEnd ; ChainSubIdx + + )
{
int VID [ 2 ] { Cut . VertexChains [ MeshIdx ] [ ChainSubIdx ] , Cut . VertexChains [ MeshIdx ] [ ChainSubIdx + 1 ] } ;
2021-03-30 21:25:22 -04:00
if ( DistanceSquared ( CutMesh [ MeshIdx ] - > GetVertex ( VID [ 0 ] ) , CutMesh [ MeshIdx ] - > GetVertex ( VID [ 1 ] ) ) < DegenerateEdgeTolSq )
2020-04-18 18:42:59 -04:00
{
EIDs . Add ( CutMesh [ MeshIdx ] - > FindEdge ( VID [ 0 ] , VID [ 1 ] ) ) ;
}
}
ChainIdx = ChainEnd ;
}
2020-06-23 18:40:00 -04:00
TSet < int > AllEIDs ( EIDs ) ;
for ( int Idx = 0 ; Idx < EIDs . Num ( ) ; Idx + + )
2020-04-18 18:42:59 -04:00
{
2020-06-23 18:40:00 -04:00
int EID = EIDs [ Idx ] ;
if ( ! CutMesh [ MeshIdx ] - > IsEdge ( EID ) )
2020-04-18 18:42:59 -04:00
{
continue ;
}
FVector3d A , B ;
CutMesh [ MeshIdx ] - > GetEdgeV ( EID , A , B ) ;
2021-03-30 21:25:22 -04:00
if ( DistanceSquared ( A , B ) > DegenerateEdgeTolSq )
2020-04-18 18:42:59 -04:00
{
continue ;
}
FIndex2i EV = CutMesh [ MeshIdx ] - > GetEdgeV ( EID ) ;
// if the vertex we'd remove is on a seam, try removing the other one instead
if ( CutMesh [ MeshIdx ] - > HasAttributes ( ) & & CutMesh [ MeshIdx ] - > 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 ( CutMesh [ MeshIdx ] - > HasAttributes ( ) & & CutMesh [ MeshIdx ] - > Attributes ( ) - > IsSeamVertex ( EV . B , false ) )
{
continue ;
}
}
FDynamicMesh3 : : FEdgeCollapseInfo CollapseInfo ;
2020-06-23 18:40:00 -04:00
EMeshResult CollapseResult = CutMesh [ MeshIdx ] - > CollapseEdge ( EV . A , EV . B , .5 , CollapseInfo ) ;
if ( CollapseResult = = EMeshResult : : Ok )
{
for ( int i = 0 ; i < 2 ; i + + )
{
if ( AllEIDs . Contains ( CollapseInfo . RemovedEdges [ i ] ) )
{
int ToAdd = CollapseInfo . KeptEdges [ i ] ;
bool bWasPresent ;
AllEIDs . Add ( ToAdd , & bWasPresent ) ;
if ( ! bWasPresent )
{
EIDs . Add ( ToAdd ) ;
}
}
}
}
2020-04-18 18:42:59 -04:00
}
}
}
if ( Cancelled ( ) )
{
return false ;
}
// edges that will become new boundary edges after the boolean op removes triangles on each mesh
TArray < int > CutBoundaryEdges [ 2 ] ;
// Vertices on the cut boundary that *may* not have a corresonding vertex on the other mesh
TSet < int > PossUnmatchedBdryVerts [ 2 ] ;
// delete geometry according to boolean rules, tracking the boundary edges
{ // (just for scope)
// first decide what triangles to delete for both meshes (*before* deleting anything so winding doesn't get messed up!)
TArray < bool > KeepTri [ 2 ] ;
2021-10-20 22:11:03 -04:00
// This array is used to double-check the assumption that we will delete the other surface when we keep a coplanar tri
// Note we only need it for mesh 0 (i.e., the mesh we try to keep triangles from when we preserve coplanar surfaces)
TArray < int32 > DeleteIfOtherKept ;
2021-11-08 16:04:43 -05:00
if ( NumMeshesToProcess > 1 )
{
DeleteIfOtherKept . Init ( - 1 , CutMesh [ 0 ] - > MaxTriangleID ( ) ) ;
}
2020-04-22 16:36:56 -04:00
for ( int MeshIdx = 0 ; MeshIdx < NumMeshesToProcess ; MeshIdx + + )
2020-04-18 18:42:59 -04:00
{
TFastWindingTree < FDynamicMesh3 > Winding ( & Spatial [ 1 - MeshIdx ] ) ;
FDynamicMeshAABBTree3 & OtherSpatial = Spatial [ 1 - MeshIdx ] ;
FDynamicMesh3 & ProcessMesh = * CutMesh [ MeshIdx ] ;
int MaxTriID = ProcessMesh . MaxTriangleID ( ) ;
KeepTri [ MeshIdx ] . SetNumUninitialized ( MaxTriID ) ;
2020-12-07 15:18:20 -04:00
bool bCoplanarKeepSameDir = ( Operation ! = EBooleanOp : : Difference & & Operation ! = EBooleanOp : : TrimInside & & Operation ! = EBooleanOp : : NewGroupInside ) ;
2020-04-18 18:42:59 -04:00
bool bRemoveInside = 1 ; // whether to remove the inside triangles (e.g. for union) or the outside ones (e.g. for intersection)
2020-12-07 15:18:20 -04:00
if ( Operation = = EBooleanOp : : NewGroupOutside | | Operation = = EBooleanOp : : TrimOutside | | Operation = = EBooleanOp : : Intersect | | ( Operation = = EBooleanOp : : Difference & & MeshIdx = = 1 ) )
2020-04-18 18:42:59 -04:00
{
bRemoveInside = 0 ;
}
2021-10-14 21:33:35 -04:00
FMeshNormals OtherNormals ( OtherSpatial . GetMesh ( ) ) ;
OtherNormals . ComputeTriangleNormals ( ) ;
const double OnPlaneTolerance = SnapTolerance ;
IMeshSpatial : : FQueryOptions NonDegenCoplanarCandidateFilter ( OnPlaneTolerance ,
[ & OtherNormals ] ( int TID ) - > bool // filter degenerate triangles from matching
{
// By convention, the normal for degenerate triangles is the zero vector
return ! OtherNormals [ TID ] . IsZero ( ) ;
} ) ;
ParallelFor ( MaxTriID , [ & ] ( int TID )
2020-04-18 18:42:59 -04:00
{
if ( ! ProcessMesh . IsTriangle ( TID ) )
{
return ;
}
2020-06-23 18:40:00 -04:00
2021-10-14 21:33:35 -04:00
FTriangle3d Tri ;
ProcessMesh . GetTriVertices ( TID , Tri . V [ 0 ] , Tri . V [ 1 ] , Tri . V [ 2 ] ) ;
FVector3d Centroid = Tri . Centroid ( ) ;
2020-06-23 18:40:00 -04:00
// first check for the coplanar case
2020-04-18 18:42:59 -04:00
{
double DSq ;
2021-10-14 21:33:35 -04:00
int OtherTID = OtherSpatial . FindNearestTriangle ( Centroid , DSq , NonDegenCoplanarCandidateFilter ) ;
2020-04-18 18:42:59 -04:00
if ( OtherTID > - 1 ) // only consider it coplanar if there is a matching tri
{
2020-06-23 18:40:00 -04:00
2021-10-14 21:33:35 -04:00
FVector3d OtherNormal = OtherNormals [ OtherTID ] ;
2020-04-18 18:42:59 -04:00
FVector3d Normal = ProcessMesh . GetTriNormal ( TID ) ;
double DotNormals = OtherNormal . Dot ( Normal ) ;
2020-06-23 18:40:00 -04:00
//if (FMath::Abs(DotNormals) > .9) // TODO: do we actually want to check for a normal match? coplanar vertex check below is more robust?
2020-04-18 18:42:59 -04:00
{
// To be extra sure it's a coplanar match, check the vertices are *also* on the other mesh (w/in SnapTolerance)
2021-10-14 21:33:35 -04:00
2020-04-18 18:42:59 -04:00
bool bAllTrisOnOtherMesh = true ;
for ( int Idx = 0 ; Idx < 3 ; Idx + + )
{
2021-10-14 21:33:35 -04:00
// use a slightly more forgiving tolerance to account for the likelihood that these vertices were mesh-cut right to the boundary of the coplanar region and have some additional error
if ( OtherSpatial . FindNearestTriangle ( Tri . V [ Idx ] , DSq , OnPlaneTolerance * 2 ) = = FDynamicMesh3 : : InvalidID )
2020-04-18 18:42:59 -04:00
{
bAllTrisOnOtherMesh = false ;
break ;
}
}
if ( bAllTrisOnOtherMesh )
{
2021-10-14 21:33:35 -04:00
// for coplanar tris favor the first mesh; just delete from the other mesh
// for fully degenerate tris, favor deletion also
// (Note: For degenerate tris we have no orientation info, so we are choosing between
// potentially leaving 'cracks' in solid regions or 'spikes' in empty regions)
if ( MeshIdx ! = 0 | | Normal . IsZero ( ) )
2020-04-18 18:42:59 -04:00
{
KeepTri [ MeshIdx ] [ TID ] = false ;
return ;
}
2021-10-14 21:33:35 -04:00
else // for the first mesh, & with a valid normal, logic depends on orientation of matching tri
2020-04-18 18:42:59 -04:00
{
2021-10-20 22:11:03 -04:00
bool bKeep = DotNormals > 0 = = bCoplanarKeepSameDir ;
KeepTri [ MeshIdx ] [ TID ] = bKeep ;
2021-11-08 16:04:43 -05:00
if ( NumMeshesToProcess > 1 & & bKeep )
2021-10-20 22:11:03 -04:00
{
// If we kept this tri, remember the coplanar pair we expect to be deleted, in case
// it isn't deleted (e.g. because it wasn't coplanar); to then delete this one instead.
// This can help clean up sliver triangles near a cut boundary that look locally coplanar
DeleteIfOtherKept [ TID ] = OtherTID ;
}
2020-04-18 18:42:59 -04:00
return ;
}
}
}
}
}
2020-06-23 18:40:00 -04:00
// didn't already return a coplanar result; use the winding number
double WindingNum = Winding . FastWindingNumber ( Centroid ) ;
2020-04-18 18:42:59 -04:00
KeepTri [ MeshIdx ] [ TID ] = ( WindingNum > WindingThreshold ) ! = bRemoveInside ;
} ) ;
2021-10-20 22:11:03 -04:00
}
// Don't keep coplanar tris if the matched, second-mesh tri that we expected to delete was actually kept
2021-11-08 16:04:43 -05:00
if ( NumMeshesToProcess > 1 )
2021-10-20 22:11:03 -04:00
{
2021-11-08 16:04:43 -05:00
for ( int TID : CutMesh [ 0 ] - > TriangleIndicesItr ( ) )
2021-10-20 22:11:03 -04:00
{
2021-11-08 16:04:43 -05:00
int32 DeleteIfOtherKeptTID = DeleteIfOtherKept [ TID ] ;
if ( DeleteIfOtherKeptTID > - 1 & & KeepTri [ 1 ] [ DeleteIfOtherKeptTID ] )
{
KeepTri [ 0 ] [ TID ] = false ;
}
2021-10-20 22:11:03 -04:00
}
}
for ( int MeshIdx = 0 ; MeshIdx < NumMeshesToProcess ; MeshIdx + + )
{
FDynamicMesh3 & ProcessMesh = * CutMesh [ MeshIdx ] ;
2020-04-18 18:42:59 -04:00
for ( int EID : ProcessMesh . EdgeIndicesItr ( ) )
{
2021-08-26 22:54:58 -04:00
FDynamicMesh3 : : FEdge Edge = ProcessMesh . GetEdge ( EID ) ;
if ( Edge . Tri . B = = IndexConstants : : InvalidID | | KeepTri [ MeshIdx ] [ Edge . Tri . A ] = = KeepTri [ MeshIdx ] [ Edge . Tri . B ] )
2020-04-18 18:42:59 -04:00
{
continue ;
}
CutBoundaryEdges [ MeshIdx ] . Add ( EID ) ;
2021-08-26 22:54:58 -04:00
PossUnmatchedBdryVerts [ MeshIdx ] . Add ( Edge . Vert . A ) ;
PossUnmatchedBdryVerts [ MeshIdx ] . Add ( Edge . Vert . B ) ;
2020-04-18 18:42:59 -04:00
}
}
// now go ahead and delete from both meshes
2020-12-07 15:18:20 -04:00
bool bRegroupInsteadOfDelete = Operation = = EBooleanOp : : NewGroupInside | | Operation = = EBooleanOp : : NewGroupOutside ;
int NewGroupID = - 1 ;
TArray < int > NewGroupTris ;
if ( bRegroupInsteadOfDelete )
{
ensure ( NumMeshesToProcess = = 1 ) ;
NewGroupID = CutMesh [ 0 ] - > AllocateTriangleGroup ( ) ;
}
2020-04-22 16:36:56 -04:00
for ( int MeshIdx = 0 ; MeshIdx < NumMeshesToProcess ; MeshIdx + + )
2020-04-18 18:42:59 -04:00
{
FDynamicMesh3 & ProcessMesh = * CutMesh [ MeshIdx ] ;
for ( int TID = 0 ; TID < KeepTri [ MeshIdx ] . Num ( ) ; TID + + )
{
if ( ProcessMesh . IsTriangle ( TID ) & & ! KeepTri [ MeshIdx ] [ TID ] )
{
2020-12-07 15:18:20 -04:00
if ( bRegroupInsteadOfDelete )
{
ProcessMesh . SetTriangleGroup ( TID , NewGroupID ) ;
NewGroupTris . Add ( TID ) ;
}
else
{
ProcessMesh . RemoveTriangle ( TID , true , false ) ;
}
}
}
}
if ( bRegroupInsteadOfDelete )
{
// the new triangle group could include disconnected components; best to give them separate triangle groups
FMeshConnectedComponents Components ( CutMesh [ 0 ] ) ;
Components . FindConnectedTriangles ( NewGroupTris ) ;
for ( int ComponentIdx = 1 ; ComponentIdx < Components . Num ( ) ; ComponentIdx + + )
{
int SplitGroupID = CutMesh [ 0 ] - > AllocateTriangleGroup ( ) ;
for ( int TID : Components . GetComponent ( ComponentIdx ) . Indices )
{
CutMesh [ 0 ] - > SetTriangleGroup ( TID , SplitGroupID ) ;
2020-04-18 18:42:59 -04:00
}
}
}
}
if ( Cancelled ( ) )
{
return false ;
}
2020-04-22 16:36:56 -04:00
// correspond vertices across both meshes (in cases where both meshes were processed)
2020-04-18 18:42:59 -04:00
TMap < int , int > AllVIDMatches ; // mapping of matched vertex IDs from cutmesh 0 to cutmesh 1
2020-04-22 16:36:56 -04:00
if ( NumMeshesToProcess = = 2 )
2020-04-18 18:42:59 -04:00
{
2021-02-16 14:40:20 -04:00
TMap < int , int > FoundMatchesMaps [ 2 ] ; // mappings of matched vertex IDs from mesh 1->0 and mesh 0->1
2020-06-23 18:40:00 -04:00
double SnapToleranceSq = SnapTolerance * SnapTolerance ;
2020-04-22 16:36:56 -04:00
// ensure segments that are now on boundaries have 1:1 vertex correspondence across meshes
for ( int MeshIdx = 0 ; MeshIdx < 2 ; MeshIdx + + )
2020-04-18 18:42:59 -04:00
{
2020-04-22 16:36:56 -04:00
int OtherMeshIdx = 1 - MeshIdx ;
FDynamicMesh3 & OtherMesh = * CutMesh [ OtherMeshIdx ] ;
2020-04-18 18:42:59 -04:00
2021-08-23 22:08:34 -04:00
TPointHashGrid3d < int > OtherMeshPointHash ( OtherMesh . GetBounds ( true ) . MaxDim ( ) / 64 , - 1 ) ;
2021-02-16 14:40:20 -04:00
for ( int BoundaryVID : PossUnmatchedBdryVerts [ OtherMeshIdx ] )
{
OtherMeshPointHash . InsertPointUnsafe ( BoundaryVID , OtherMesh . GetVertex ( BoundaryVID ) ) ;
}
2020-11-09 17:15:51 -04:00
FSparseDynamicOctree3 EdgeOctree ;
EdgeOctree . RootDimension = .25 ;
EdgeOctree . SetMaxTreeDepth ( 7 ) ;
auto EdgeBounds = [ & OtherMesh ] ( int EID )
{
FDynamicMesh3 : : FEdge Edge = OtherMesh . GetEdge ( EID ) ;
FVector3d A = OtherMesh . GetVertex ( Edge . Vert . A ) ;
FVector3d B = OtherMesh . GetVertex ( Edge . Vert . B ) ;
if ( A . X > B . X )
{
Swap ( A . X , B . X ) ;
}
if ( A . Y > B . Y )
{
Swap ( A . Y , B . Y ) ;
}
if ( A . Z > B . Z )
{
Swap ( A . Z , B . Z ) ;
}
return FAxisAlignedBox3d ( A , B ) ;
} ;
auto AddEdge = [ & EdgeOctree , & OtherMesh , EdgeBounds ] ( int EID )
{
EdgeOctree . InsertObject ( EID , EdgeBounds ( EID ) ) ;
} ;
auto UpdateEdge = [ & EdgeOctree , & OtherMesh , EdgeBounds ] ( int EID )
{
EdgeOctree . ReinsertObject ( EID , EdgeBounds ( EID ) ) ;
} ;
for ( int EID : CutBoundaryEdges [ OtherMeshIdx ] )
{
AddEdge ( EID ) ;
}
TArray < int > EdgesInRange ;
2020-04-22 16:36:56 -04:00
// mapping from OtherMesh VIDs to ProcessMesh VIDs
// used to ensure we only keep the best match, in cases where multiple boundary vertices map to a given vertex on the other mesh boundary
2021-02-16 14:40:20 -04:00
TMap < int , int > & FoundMatches = FoundMatchesMaps [ MeshIdx ] ;
2020-04-22 16:36:56 -04:00
for ( int BoundaryVID : PossUnmatchedBdryVerts [ MeshIdx ] )
{
2021-02-16 14:40:20 -04:00
if ( MeshIdx = = 1 & & FoundMatchesMaps [ 0 ] . Contains ( BoundaryVID ) )
{
continue ; // was already snapped to a vertex
}
2020-04-22 16:36:56 -04:00
FVector3d Pos = CutMesh [ MeshIdx ] - > GetVertex ( BoundaryVID ) ;
2021-02-16 14:40:20 -04:00
TPair < int , double > VIDDist = OtherMeshPointHash . FindNearestInRadius ( Pos , SnapTolerance , [ & Pos , & OtherMesh ] ( int VID )
2020-04-22 16:36:56 -04:00
{
2021-03-30 21:25:22 -04:00
return DistanceSquared ( Pos , OtherMesh . GetVertex ( VID ) ) ;
2020-04-22 16:36:56 -04:00
} ) ;
int NearestVID = VIDDist . Key ; // ID of nearest vertex on other mesh
double DSq = VIDDist . Value ; // square distance to that vertex
if ( NearestVID ! = FDynamicMesh3 : : InvalidID )
{
int * Match = FoundMatches . Find ( NearestVID ) ;
if ( Match )
{
2021-03-30 21:25:22 -04:00
double OldDSq = DistanceSquared ( CutMesh [ MeshIdx ] - > GetVertex ( * Match ) , OtherMesh . GetVertex ( NearestVID ) ) ;
2020-04-22 16:36:56 -04:00
if ( DSq < OldDSq ) // new vertex is a better match than the old one
{
int OldVID = * Match ; // copy old VID out of match before updating the TMap
FoundMatches . Add ( NearestVID , BoundaryVID ) ; // new VID is recorded as best match
// old VID is swapped in as the one to consider as unmatched
// it will now be matched below
BoundaryVID = OldVID ;
Pos = CutMesh [ MeshIdx ] - > GetVertex ( BoundaryVID ) ;
DSq = OldDSq ;
}
NearestVID = FDynamicMesh3 : : InvalidID ; // one of these vertices will be unmatched
}
else
{
FoundMatches . Add ( NearestVID , BoundaryVID ) ;
}
}
// if we didn't find a valid match, try to split the nearest edge to create a match
if ( NearestVID = = FDynamicMesh3 : : InvalidID )
{
// vertex had no match -- try to split edge to match it
2020-11-09 17:15:51 -04:00
FAxisAlignedBox3d QueryBox ( Pos , SnapTolerance ) ;
EdgesInRange . Reset ( ) ;
EdgeOctree . RangeQuery ( QueryBox , EdgesInRange ) ;
int OtherEID = FindNearestEdge ( OtherMesh , EdgesInRange , Pos ) ;
2020-04-22 16:36:56 -04:00
if ( OtherEID ! = FDynamicMesh3 : : InvalidID )
{
FVector3d EdgePts [ 2 ] ;
OtherMesh . GetEdgeV ( OtherEID , EdgePts [ 0 ] , EdgePts [ 1 ] ) ;
2020-06-23 18:40:00 -04:00
// only accept the match if it's not going to create a degenerate edge -- TODO: filter already-matched edges from the FindNearestEdge query!
2021-03-30 21:25:22 -04:00
if ( DistanceSquared ( EdgePts [ 0 ] , Pos ) > SnapToleranceSq & & DistanceSquared ( EdgePts [ 1 ] , Pos ) > SnapToleranceSq )
2020-04-22 16:36:56 -04:00
{
2020-06-23 18:40:00 -04:00
FSegment3d Seg ( EdgePts [ 0 ] , EdgePts [ 1 ] ) ;
double Along = Seg . ProjectUnitRange ( Pos ) ;
FDynamicMesh3 : : FEdgeSplitInfo SplitInfo ;
if ( ensure ( EMeshResult : : Ok = = OtherMesh . SplitEdge ( OtherEID , SplitInfo , Along ) ) )
{
FoundMatches . Add ( SplitInfo . NewVertex , BoundaryVID ) ;
OtherMesh . SetVertex ( SplitInfo . NewVertex , Pos ) ;
CutBoundaryEdges [ OtherMeshIdx ] . Add ( SplitInfo . NewEdges . A ) ;
2020-11-09 17:15:51 -04:00
UpdateEdge ( OtherEID ) ;
AddEdge ( SplitInfo . NewEdges . A ) ;
2020-06-23 18:40:00 -04:00
// Note: Do not update PossUnmatchedBdryVerts with the new vertex, because it is already matched by construction
// Likewise do not update the pointhash -- we don't want it to find vertices that were already perfectly matched
}
2020-04-22 16:36:56 -04:00
}
}
}
}
// actually snap the positions together for final matches
for ( TPair < int , int > & Match : FoundMatches )
{
CutMesh [ MeshIdx ] - > SetVertex ( Match . Value , OtherMesh . GetVertex ( Match . Key ) ) ;
// Copy match to AllVIDMatches; note this is always mapping from CutMesh 0 to 1
int VIDs [ 2 ] { Match . Key , Match . Value } ; // just so we can access by index
AllVIDMatches . Add ( VIDs [ 1 - MeshIdx ] , VIDs [ MeshIdx ] ) ;
}
2020-04-18 18:42:59 -04:00
}
}
2021-02-11 01:44:27 -04:00
if ( bSimplifyAlongNewEdges )
{
SimplifyAlongNewEdges ( NumMeshesToProcess , CutMesh , CutBoundaryEdges , AllVIDMatches ) ;
}
2020-04-18 18:42:59 -04:00
if ( Operation = = EBooleanOp : : Difference )
{
// TODO: implement a way to flip all the triangles in the mesh without building this AllTID array
TArray < int > AllTID ;
for ( int TID : CutMesh [ 1 ] - > TriangleIndicesItr ( ) )
{
AllTID . Add ( TID ) ;
}
FDynamicMeshEditor FlipEditor ( CutMesh [ 1 ] ) ;
FlipEditor . ReverseTriangleOrientations ( AllTID , true ) ;
}
if ( Cancelled ( ) )
{
return false ;
}
2020-04-22 16:36:56 -04:00
bool bSuccess = true ;
2020-04-18 18:42:59 -04:00
2020-04-22 16:36:56 -04:00
if ( NumMeshesToProcess > 1 )
{
FDynamicMeshEditor Editor ( Result ) ;
FMeshIndexMappings IndexMaps ;
Editor . AppendMesh ( CutMesh [ 1 ] , IndexMaps ) ;
2020-04-18 18:42:59 -04:00
2021-08-26 16:59:59 -04:00
if ( bPopulateSecondMeshGroupMap )
{
SecondMeshGroupMap = IndexMaps . GetGroupMap ( ) ;
}
2020-11-09 17:15:51 -04:00
if ( bWeldSharedEdges )
{
bool bWeldSuccess = MergeEdges ( IndexMaps , CutMesh , CutBoundaryEdges , AllVIDMatches ) ;
bSuccess = bSuccess & & bWeldSuccess ;
}
2021-08-10 09:18:57 -04:00
else
{
CreatedBoundaryEdges = CutBoundaryEdges [ 0 ] ;
for ( int OldMeshEID : CutBoundaryEdges [ 1 ] )
{
if ( ! CutMesh [ 1 ] - > IsEdge ( OldMeshEID ) )
{
ensure ( false ) ;
continue ;
}
FIndex2i OtherEV = CutMesh [ 1 ] - > GetEdgeV ( OldMeshEID ) ;
int MappedEID = Result - > FindEdge ( IndexMaps . GetNewVertex ( OtherEV . A ) , IndexMaps . GetNewVertex ( OtherEV . B ) ) ;
checkSlow ( Result - > IsBoundaryEdge ( MappedEID ) ) ;
CreatedBoundaryEdges . Add ( MappedEID ) ;
}
}
2020-04-22 16:36:56 -04:00
}
else
{
CreatedBoundaryEdges = CutBoundaryEdges [ 0 ] ;
}
2020-12-09 14:18:38 -04:00
if ( bTrackAllNewEdges )
{
for ( int32 eid : CreatedBoundaryEdges )
{
AllNewEdges . Add ( eid ) ;
}
}
2020-06-23 18:40:00 -04:00
if ( bPutResultInInputSpace )
{
MeshTransforms : : ApplyTransform ( * Result , ResultTransform ) ;
2022-01-29 14:37:53 -05:00
ResultTransform = FTransformSRT3d : : Identity ( ) ;
2020-06-23 18:40:00 -04:00
}
2020-04-22 16:36:56 -04:00
return bSuccess ;
2020-04-18 18:42:59 -04:00
}
2021-02-11 01:44:27 -04:00
bool FMeshBoolean : : IsFlat ( const FDynamicMesh3 & Mesh , int VID , double DotTolerance , FVector3d & OutFirstNormal )
{
bool bHasFirst = false ;
bool bIsFlat = true ;
Mesh . EnumerateVertexTriangles ( VID , [ & Mesh , DotTolerance , & OutFirstNormal , & bHasFirst , & bIsFlat ] ( int32 TID )
{
if ( ! bIsFlat )
{
return ;
}
FVector3d Normal = Mesh . GetTriNormal ( TID ) ;
if ( ! bHasFirst )
{
OutFirstNormal = Normal ;
bHasFirst = true ;
}
else
{
bIsFlat = bIsFlat & & Normal . Dot ( OutFirstNormal ) > = DotTolerance ;
}
} ) ;
return bIsFlat ;
}
/**
* Test if a given edge collapse would cause a triangle flip or other unacceptable decrease in mesh quality
*/
bool FMeshBoolean : : CollapseWouldHurtTriangleQuality (
const FDynamicMesh3 & Mesh , const FVector3d & ExpectNormal ,
2021-02-16 14:40:20 -04:00
int32 RemoveV , const FVector3d & RemoveVPos , int32 KeepV , const FVector3d & KeepVPos ,
double TryToImproveTriQualityThreshold
2021-02-11 01:44:27 -04:00
)
{
2021-02-16 14:40:20 -04:00
double WorstQualityNewTriangle = FMathd : : MaxReal ;
2021-02-11 01:44:27 -04:00
bool bIsHurt = false ;
Mesh . EnumerateVertexTriangles ( RemoveV ,
2021-02-16 14:40:20 -04:00
[ & Mesh , & bIsHurt , & KeepVPos , RemoveV , KeepV , & ExpectNormal ,
TryToImproveTriQualityThreshold , & WorstQualityNewTriangle ] ( int32 TID )
2021-02-11 01:44:27 -04:00
{
if ( bIsHurt )
{
return ;
}
FIndex3i Tri = Mesh . GetTriangle ( TID ) ;
FVector3d Verts [ 3 ] ;
for ( int Idx = 0 ; Idx < 3 ; Idx + + )
{
int VID = Tri [ Idx ] ;
if ( VID = = KeepV )
{
// this tri has both RemoveV and KeepV, so it'll be removed and we don't need to consider it
return ;
}
else if ( VID = = RemoveV )
{
// anything at RemoveV is reconnected to KeepV's position
Verts [ Idx ] = KeepVPos ;
}
else
{
// it's not on the collapsed edge so it stays still
Verts [ Idx ] = Mesh . GetVertex ( Tri [ Idx ] ) ;
}
}
FVector3d Edge1 ( Verts [ 1 ] - Verts [ 0 ] ) ;
FVector3d Edge2 ( Verts [ 2 ] - Verts [ 0 ] ) ;
FVector3d VCross ( Edge2 . Cross ( Edge1 ) ) ;
2021-03-17 19:32:44 -04:00
// TODO: does this tolerance make a difference? if not, set to zero and remove the Normalize(VCross)
2021-02-11 01:44:27 -04:00
double EdgeFlipTolerance = 1.e-5 ;
2021-03-17 19:32:44 -04:00
double Area2 = Normalize ( VCross ) ;
2021-02-16 14:40:20 -04:00
if ( TryToImproveTriQualityThreshold > 0 )
{
FVector3d Edge3 ( Verts [ 2 ] - Verts [ 1 ] ) ;
double MaxLenSq = FMathd : : Max3 ( Edge1 . SquaredLength ( ) , Edge2 . SquaredLength ( ) , Edge3 . SquaredLength ( ) ) ;
double Quality = Area2 / ( MaxLenSq + FMathd : : ZeroTolerance ) ;
WorstQualityNewTriangle = FMathd : : Min ( Quality , WorstQualityNewTriangle ) ;
}
2021-02-11 01:44:27 -04:00
bIsHurt = VCross . Dot ( ExpectNormal ) < = EdgeFlipTolerance ;
}
) ;
2021-02-16 14:40:20 -04:00
// note tri quality was computed as 2*Area / MaxEdgeLenSquared
// so need to multiply it by 2/sqrt(3) to get actual aspect ratio
if ( ! bIsHurt & & WorstQualityNewTriangle * 2 * FMathd : : InvSqrt3 < TryToImproveTriQualityThreshold )
{
// we found a bad tri; tentatively switch to rejecting this edge collapse
bIsHurt = true ;
// but if there was an even worse tri in the original neighborhood, accept the collapse after all
Mesh . EnumerateVertexTriangles ( RemoveV , [ & Mesh , & bIsHurt , WorstQualityNewTriangle ] ( int TID )
{
if ( ! bIsHurt ) // early out if we already found an originally-worse tri
{
return ;
}
FVector3d A , B , C ;
Mesh . GetTriVertices ( TID , A , B , C ) ;
FVector3d E1 = B - A , E2 = C - A , E3 = C - B ;
double Area2 = E1 . Cross ( E2 ) . Length ( ) ;
double MaxLenSq = FMathd : : Max3 ( E1 . SquaredLength ( ) , E2 . SquaredLength ( ) , E3 . SquaredLength ( ) ) ;
double Quality = Area2 / ( MaxLenSq + FMathd : : ZeroTolerance ) ;
if ( Quality < WorstQualityNewTriangle )
{
bIsHurt = false ;
}
}
) ;
}
2021-02-11 01:44:27 -04:00
return bIsHurt ;
}
/**
* Test if a given edge collapse would change the mesh shape or UVs unacceptably
*/
bool FMeshBoolean : : CollapseWouldChangeShapeOrUVs (
2021-02-16 14:40:20 -04:00
const FDynamicMesh3 & Mesh , const TSet < int > & CutBoundaryEdgeSet , double DotTolerance , int SourceEID ,
2021-02-11 01:44:27 -04:00
int32 RemoveV , const FVector3d & RemoveVPos , int32 KeepV , const FVector3d & KeepVPos , const FVector3d & EdgeDir ,
2022-05-12 12:09:00 -04:00
bool bPreserveTriangleGroups , bool bPreserveUVsForMesh , bool bPreserveVertexUVs , bool bPreserveOverlayUVs ,
float UVEqualThresholdSq , bool bPreserveVertexNormals , float NormalEqualCosThreshold )
2021-02-11 01:44:27 -04:00
{
// Search the edges connected to the vertex to find one in the boundary set that points in the opposite direction
// If we don't find that edge, or if there are other boundary/seam edges attached, we can't remove this vertex
// We also can't remove the vertex if doing so would distort the UVs
bool bHasBadEdge = false ;
int OpposedEdge = - 1 ;
2021-09-01 12:33:20 -04:00
int SourceGroupID = Mesh . GetTriangleGroup ( Mesh . GetEdgeT ( SourceEID ) . A ) ;
2021-02-11 01:44:27 -04:00
Mesh . EnumerateVertexEdges ( RemoveV ,
2021-09-01 12:33:20 -04:00
[ & ] ( int32 VertEID )
2021-02-11 01:44:27 -04:00
{
if ( bHasBadEdge | | VertEID = = SourceEID )
{
return ;
}
2021-09-01 12:33:20 -04:00
FDynamicMesh3 : : FEdge Edge = Mesh . GetEdge ( VertEID ) ;
if ( bPreserveTriangleGroups & & Mesh . HasTriangleGroups ( ) )
{
if ( SourceGroupID ! = Mesh . GetTriangleGroup ( Edge . Tri . A ) | |
( Edge . Tri . B ! = FDynamicMesh3 : : InvalidID & & SourceGroupID ! = Mesh . GetTriangleGroup ( Edge . Tri . B ) ) )
{
// RemoveV is on a group boundary, so the edge collapse would change the shape of the groups
bHasBadEdge = true ;
return ;
}
}
2021-02-11 01:44:27 -04:00
// it's a known boundary edge; check if it's the opposite-facing one we need
if ( CutBoundaryEdgeSet . Contains ( VertEID ) )
{
if ( OpposedEdge ! = - 1 )
{
bHasBadEdge = true ;
return ;
}
2021-09-01 12:33:20 -04:00
FIndex2i OtherEdgeV = Edge . Vert ;
2021-02-11 01:44:27 -04:00
int OtherV = IndexUtil : : FindEdgeOtherVertex ( OtherEdgeV , RemoveV ) ;
FVector3d OtherVPos = Mesh . GetVertex ( OtherV ) ;
FVector3d OtherEdgeDir = OtherVPos - RemoveVPos ;
2021-03-17 19:32:44 -04:00
if ( Normalize ( OtherEdgeDir ) = = 0 )
2021-02-11 01:44:27 -04:00
{
// collapsing degenerate edges above should prevent this
bHasBadEdge = true ;
return ; // break instead of continue to skip the whole edge
}
2021-08-26 22:54:58 -04:00
if ( OtherEdgeDir . Dot ( EdgeDir ) < = - DotTolerance )
2021-02-11 01:44:27 -04:00
{
OpposedEdge = VertEID ;
}
else
{
bHasBadEdge = true ;
return ;
}
// test that UVs are not too distorted through the collapse
if ( ! bPreserveUVsForMesh )
{
return ;
}
float LerpT = ( RemoveVPos - OtherVPos ) . Dot ( OtherEdgeDir ) / ( KeepVPos - OtherVPos ) . Dot ( OtherEdgeDir ) ;
if ( bPreserveVertexUVs & & Mesh . HasVertexUVs ( ) )
{
FVector2f OtherUV = Mesh . GetVertexUV ( OtherV ) ;
FVector2f RemoveUV = Mesh . GetVertexUV ( RemoveV ) ;
FVector2f KeepUV = Mesh . GetVertexUV ( KeepV ) ;
2021-08-30 18:03:07 -04:00
if ( DistanceSquared ( Lerp ( OtherUV , KeepUV , LerpT ) , RemoveUV ) > UVEqualThresholdSq )
2021-02-11 01:44:27 -04:00
{
bHasBadEdge = true ;
return ;
}
}
2022-05-12 12:09:00 -04:00
if ( bPreserveVertexNormals & & Mesh . HasVertexNormals ( ) )
{
FVector3f OtherN = Mesh . GetVertexNormal ( OtherV ) ;
FVector3f RemoveN = Mesh . GetVertexNormal ( RemoveV ) ;
FVector3f KeepN = Mesh . GetVertexNormal ( KeepV ) ;
if ( Normalized ( Lerp ( OtherN , KeepN , LerpT ) ) . Dot ( Normalized ( RemoveN ) ) < NormalEqualCosThreshold )
{
bHasBadEdge = true ;
return ;
}
}
2021-02-11 01:44:27 -04:00
if ( bPreserveOverlayUVs & & Mesh . HasAttributes ( ) )
{
int NumLayers = Mesh . Attributes ( ) - > NumUVLayers ( ) ;
FIndex2i SourceEdgeTris = Mesh . GetEdgeT ( SourceEID ) ;
2021-09-01 12:33:20 -04:00
FIndex2i OppEdgeTris = Edge . Tri ;
2021-02-11 01:44:27 -04:00
// special handling of seam edge when the edges aren't boundary edges
// -- if they're seams, we'd need to check both sides of the seams for a UV match
// but this is complicated and should be quite rare, so we just don't collapse these
if ( SourceEdgeTris . B ! = - 1 | | OppEdgeTris . B ! = - 1 )
{
if ( Mesh . Attributes ( ) - > IsSeamEdge ( SourceEID ) | |
Mesh . Attributes ( ) - > IsSeamEdge ( VertEID ) )
{
bHasBadEdge = true ;
return ;
}
}
FIndex3i SourceBaseTri = Mesh . GetTriangle ( SourceEdgeTris . A ) ;
FIndex3i OppBaseTri = Mesh . GetTriangle ( OppEdgeTris . A ) ;
int KeepSourceIdx = IndexUtil : : FindTriIndex ( KeepV , SourceBaseTri ) ;
int RemoveSourceIdx = IndexUtil : : FindTriIndex ( RemoveV , SourceBaseTri ) ;
int OtherOppIdx = IndexUtil : : FindTriIndex ( OtherV , OppBaseTri ) ;
if ( ! ensure ( KeepSourceIdx ! = - 1 & & RemoveSourceIdx ! = - 1 & & OtherOppIdx ! = - 1 ) )
{
bHasBadEdge = true ;
return ;
}
// get the UVs per overlay off the triangle(s) attached the two edges
for ( int UVLayerIdx = 0 ; UVLayerIdx < NumLayers ; UVLayerIdx + + )
{
const FDynamicMeshUVOverlay * UVs = Mesh . Attributes ( ) - > GetUVLayer ( UVLayerIdx ) ;
if ( UVs - > ElementCount ( ) < 3 )
{
// overlay is not actually in use; skip it
continue ;
}
FIndex3i SourceT = UVs - > GetTriangle ( SourceEdgeTris . A ) ;
FIndex3i OppT = UVs - > GetTriangle ( OppEdgeTris . A ) ;
int KeepE = SourceT [ KeepSourceIdx ] ;
int RemoveE = SourceT [ RemoveSourceIdx ] ;
int OtherE = OppT [ OtherOppIdx ] ;
if ( KeepE = = - 1 | | RemoveE = = - 1 | | OtherE = = - 1 )
{
// overlay is not set on relevant triangles; skip it
continue ;
}
FVector2f OtherUV = UVs - > GetElement ( OtherE ) ;
FVector2f RemoveUV = UVs - > GetElement ( RemoveE ) ;
FVector2f KeepUV = UVs - > GetElement ( KeepE ) ;
2021-08-30 18:03:07 -04:00
if ( DistanceSquared ( Lerp ( OtherUV , KeepUV , LerpT ) , RemoveUV ) > UVEqualThresholdSq )
2021-02-11 01:44:27 -04:00
{
bHasBadEdge = true ;
return ;
}
}
}
}
else // it wasn't in the boundary edge set; check if it's one that would prevent us from safely removing the vertex
{
if ( Mesh . IsBoundaryEdge ( VertEID ) | | ( Mesh . HasAttributes ( ) & & Mesh . Attributes ( ) - > IsSeamEdge ( VertEID ) ) )
{
bHasBadEdge = true ;
}
}
} ) ;
return bHasBadEdge ;
}
void FMeshBoolean : : SimplifyAlongNewEdges ( int NumMeshesToProcess , FDynamicMesh3 * CutMesh [ 2 ] , TArray < int > CutBoundaryEdges [ 2 ] , TMap < int , int > & AllVIDMatches )
{
double DotTolerance = FMathd : : Cos ( SimplificationAngleTolerance * FMathd : : DegToRad ) ;
TSet < int > CutBoundaryEdgeSets [ 2 ] ; // set versions of CutBoundaryEdges, for faster membership tests
for ( int MeshIdx = 0 ; MeshIdx < NumMeshesToProcess ; MeshIdx + + )
{
CutBoundaryEdgeSets [ MeshIdx ] . Append ( CutBoundaryEdges [ MeshIdx ] ) ;
}
int NumCollapses = 0 , CollapseIters = 0 ;
int MaxCollapseIters = 1 ; // TODO: is there a case where we need more iterations? Perhaps if we add some triangle quality criteria?
while ( CollapseIters < MaxCollapseIters )
{
int LastNumCollapses = NumCollapses ;
for ( int EID : CutBoundaryEdges [ 0 ] )
{
// this can happen if a collapse removes another cut boundary edge
// (which can happen e.g. if you have a degenerate (colinear) tri flat on the cut boundary)
if ( ! CutMesh [ 0 ] - > IsEdge ( EID ) )
{
continue ;
}
// don't allow collapses if we somehow get down to our last triangle on either mesh
if ( CutMesh [ 0 ] - > TriangleCount ( ) < = 1 | | ( NumMeshesToProcess = = 2 & & CutMesh [ 1 ] - > TriangleCount ( ) < = 1 ) )
{
break ;
}
FDynamicMesh3 : : FEdge Edge = CutMesh [ 0 ] - > GetEdge ( EID ) ;
int Matches [ 2 ] { - 1 , - 1 } ;
bool bHasMatches = NumMeshesToProcess = = 2 ;
if ( bHasMatches )
{
for ( int MatchIdx = 0 ; MatchIdx < 2 ; MatchIdx + + )
{
int * Match = AllVIDMatches . Find ( Edge . Vert [ MatchIdx ] ) ;
if ( Match )
{
Matches [ MatchIdx ] = * Match ;
}
else
{
bHasMatches = false ;
// TODO: if we switch to allow collapse on unmatched edges, we shouldn't break here
// b/c we may be partially matched, and need to track which is matched.
break ;
}
}
if ( ! bHasMatches )
{
continue ; // edge wasn't matched up on the other mesh; can't collapse it?
// TODO: consider supporting collapses in this case?
}
}
// if we have matched vertices, we also need a matched edge to collapse
int OtherEID = - 1 ;
if ( bHasMatches )
{
OtherEID = CutMesh [ 1 ] - > FindEdge ( Matches [ 0 ] , Matches [ 1 ] ) ;
if ( OtherEID = = - 1 )
{
continue ;
}
}
// track whether the neighborhood of the vertex is flat (and likewise its matched pair's neighborhood, if present)
bool Flat [ 2 ] { false , false } ;
// normals for each flat vertex, and each "side" (mesh 0 and mesh 1, if mesh 1 is present)
FVector3d FlatNormals [ 2 ] [ 2 ] { { FVector3d : : Zero ( ) , FVector3d : : Zero ( ) } , { FVector3d : : Zero ( ) , FVector3d : : Zero ( ) } } ;
int NumFlat = 0 ;
for ( int VIdx = 0 ; VIdx < 2 ; VIdx + + )
{
if ( IsFlat ( * CutMesh [ 0 ] , Edge . Vert [ VIdx ] , DotTolerance , FlatNormals [ VIdx ] [ 0 ] ) )
{
Flat [ VIdx ] = ( Matches [ VIdx ] = = - 1 ) | | IsFlat ( * CutMesh [ 1 ] , Matches [ VIdx ] , DotTolerance , FlatNormals [ VIdx ] [ 1 ] ) ;
}
if ( Flat [ VIdx ] )
{
NumFlat + + ;
}
}
if ( NumFlat = = 0 )
{
continue ;
}
// see if we can collapse to remove either vertex
for ( int RemoveVIdx = 0 ; RemoveVIdx < 2 ; RemoveVIdx + + )
{
if ( ! Flat [ RemoveVIdx ] )
{
continue ;
}
int KeepVIdx = 1 - RemoveVIdx ;
FVector3d RemoveVPos = CutMesh [ 0 ] - > GetVertex ( Edge . Vert [ RemoveVIdx ] ) ;
FVector3d KeepVPos = CutMesh [ 0 ] - > GetVertex ( Edge . Vert [ KeepVIdx ] ) ;
FVector3d EdgeDir = KeepVPos - RemoveVPos ;
2021-03-17 19:32:44 -04:00
if ( Normalize ( EdgeDir ) = = 0 ) // 0 is returned as a special case when the edge was too short to normalize
2021-02-11 01:44:27 -04:00
{
// collapsing degenerate edges above should prevent this
ensure ( ! bCollapseDegenerateEdgesOnCut ) ;
// Just skip these edges, because in practice we generally have bCollapseDegenerateEdgesOnCut enabled
break ; // break instead of continue to skip the whole edge
}
bool bHasBadEdge = false ; // will be set if either mesh can't collapse the edge
for ( int MeshIdx = 0 ; ! bHasBadEdge & & MeshIdx < NumMeshesToProcess ; MeshIdx + + )
{
int RemoveV = MeshIdx = = 0 ? Edge . Vert [ RemoveVIdx ] : Matches [ RemoveVIdx ] ;
int KeepV = MeshIdx = = 0 ? Edge . Vert [ KeepVIdx ] : Matches [ KeepVIdx ] ;
int SourceEID = MeshIdx = = 0 ? EID : OtherEID ;
2021-02-16 14:40:20 -04:00
bHasBadEdge = bHasBadEdge | | CollapseWouldHurtTriangleQuality ( * CutMesh [ MeshIdx ] ,
FlatNormals [ RemoveVIdx ] [ MeshIdx ] , RemoveV , RemoveVPos , KeepV , KeepVPos , TryToImproveTriQualityThreshold ) ;
2021-02-11 01:44:27 -04:00
bHasBadEdge = bHasBadEdge | | CollapseWouldChangeShapeOrUVs (
* CutMesh [ MeshIdx ] , CutBoundaryEdgeSets [ MeshIdx ] , DotTolerance ,
2021-09-01 12:33:20 -04:00
SourceEID , RemoveV , RemoveVPos , KeepV , KeepVPos , EdgeDir , bPreserveTriangleGroups ,
2021-02-11 01:44:27 -04:00
PreserveUVsOnlyForMesh = = - 1 | | MeshIdx = = PreserveUVsOnlyForMesh ,
2022-05-12 12:09:00 -04:00
bPreserveVertexUVs , bPreserveOverlayUVs , UVDistortTolerance * UVDistortTolerance ,
bPreserveVertexNormals , FMathf : : Cos ( NormalDistortTolerance * FMathf : : DegToRad ) ) ;
2021-02-11 01:44:27 -04:00
} ;
if ( bHasBadEdge )
{
continue ;
}
2021-06-24 12:29:55 -04:00
// do some pre-collapse sanity checks on the matched edge (if present) to see if it will fail to collapse
bool bAttemptCollapse = true ;
if ( bHasMatches )
{
int OtherRemoveV = Matches [ RemoveVIdx ] ;
int OtherKeepV = Matches [ KeepVIdx ] ;
int a = OtherRemoveV , b = OtherKeepV ;
int eab = CutMesh [ 1 ] - > FindEdge ( OtherRemoveV , OtherKeepV ) ;
const FDynamicMesh3 : : FEdge EdgeAB = CutMesh [ 1 ] - > GetEdge ( eab ) ;
int t0 = EdgeAB . Tri [ 0 ] ;
if ( t0 = = FDynamicMesh3 : : InvalidID )
{
bAttemptCollapse = false ;
}
else
{
FIndex3i T0tv = CutMesh [ 1 ] - > GetTriangle ( t0 ) ;
int c = IndexUtil : : FindTriOtherVtx ( a , b , T0tv ) ;
checkSlow ( EdgeAB . Tri [ 1 ] = = FDynamicMesh3 : : InvalidID ) ;
// We cannot collapse if edge lists of a and b share vertices other
// than c and d (because then we will make a triangle [x b b].
// Brute-force search logic adapted from FDynamicMesh3::CollapseEdge implementation.
// (simplified because we know this is a boundary edge)
CutMesh [ 1 ] - > EnumerateVertexVertices ( a , [ & ] ( int VID )
{
if ( ! bAttemptCollapse | | VID = = c | | VID = = b )
{
return ;
}
CutMesh [ 1 ] - > EnumerateVertexVertices ( b , [ & ] ( int VID2 )
{
bAttemptCollapse & = ( VID ! = VID2 ) ;
} ) ;
} ) ;
}
}
if ( ! bAttemptCollapse )
{
break ; // don't try starting from other vertex if the match edge couldn't be collapsed
}
2021-02-11 01:44:27 -04:00
FDynamicMesh3 : : FEdgeCollapseInfo CollapseInfo ;
int RemoveV = Edge . Vert [ RemoveVIdx ] ;
int KeepV = Edge . Vert [ KeepVIdx ] ;
2021-08-26 22:54:58 -04:00
// Detect the case of a triangle with two boundary edges, where collapsing
// the target boundary edge would keep the non-boundary edge.
// This collapse will remove the triangle, so we add the
// (formerly) non-boundary edge as our new boundary edge.
auto WouldRemoveTwoBoundaryEdges = [ ] ( const FDynamicMesh3 & Mesh , int EID , int RemoveV )
{
checkSlow ( Mesh . IsEdge ( EID ) ) ;
int OppV = Mesh . GetEdgeOpposingV ( EID ) . A ;
int NextOnTri = Mesh . FindEdge ( RemoveV , OppV ) ;
return Mesh . IsBoundaryEdge ( NextOnTri ) ;
} ;
bool bWouldRemoveNext = WouldRemoveTwoBoundaryEdges ( * CutMesh [ 0 ] , EID , RemoveV ) ;
2021-02-11 01:44:27 -04:00
EMeshResult CollapseResult = CutMesh [ 0 ] - > CollapseEdge ( KeepV , RemoveV , 0 , CollapseInfo ) ;
2021-02-16 14:40:20 -04:00
if ( CollapseResult = = EMeshResult : : Ok )
2021-02-11 01:44:27 -04:00
{
2021-08-26 22:54:58 -04:00
if ( bWouldRemoveNext & & ensure ( CutMesh [ 0 ] - > IsBoundaryEdge ( CollapseInfo . KeptEdges . A ) ) )
{
CutBoundaryEdgeSets [ 0 ] . Add ( CollapseInfo . KeptEdges . A ) ;
}
2021-02-11 01:44:27 -04:00
if ( bHasMatches )
{
int OtherRemoveV = Matches [ RemoveVIdx ] ;
int OtherKeepV = Matches [ KeepVIdx ] ;
2021-08-26 22:54:58 -04:00
bool bOtherWouldRemoveNext = WouldRemoveTwoBoundaryEdges ( * CutMesh [ 1 ] , OtherEID , OtherRemoveV ) ;
2021-02-11 01:44:27 -04:00
FDynamicMesh3 : : FEdgeCollapseInfo OtherCollapseInfo ;
EMeshResult OtherCollapseResult = CutMesh [ 1 ] - > CollapseEdge ( OtherKeepV , OtherRemoveV , 0 , OtherCollapseInfo ) ;
2021-02-16 14:40:20 -04:00
if ( OtherCollapseResult ! = EMeshResult : : Ok )
2021-02-11 01:44:27 -04:00
{
// if we get here, we've somehow managed to collapse the first edge but failed on the second (matched) edge
// which will leave a crack in the result unless we can somehow undo the first collapse, which would require a bunch of extra work
// but the only case where I could see this happen is if the second edge is on an isolated triangle, which means there is a hole anyway
// or if the mesh topology is somehow invalid
2021-02-16 14:40:20 -04:00
ensureMsgf ( OtherCollapseResult = = EMeshResult : : Failed_CollapseTriangle , TEXT ( " Collapse failed with result: %d " ) , ( int ) OtherCollapseResult ) ;
2021-02-11 01:44:27 -04:00
}
else
{
2021-08-26 22:54:58 -04:00
if ( bOtherWouldRemoveNext & & ensure ( CutMesh [ 1 ] - > IsBoundaryEdge ( OtherCollapseInfo . KeptEdges . A ) ) )
{
CutBoundaryEdgeSets [ 1 ] . Add ( OtherCollapseInfo . KeptEdges . A ) ;
}
2021-02-11 01:44:27 -04:00
AllVIDMatches . Remove ( RemoveV ) ;
CutBoundaryEdgeSets [ 1 ] . Remove ( OtherCollapseInfo . CollapsedEdge ) ;
CutBoundaryEdgeSets [ 1 ] . Remove ( OtherCollapseInfo . RemovedEdges [ 0 ] ) ;
if ( OtherCollapseInfo . RemovedEdges [ 1 ] ! = - 1 )
{
CutBoundaryEdgeSets [ 1 ] . Remove ( OtherCollapseInfo . RemovedEdges [ 1 ] ) ;
}
}
}
2021-02-16 14:40:20 -04:00
NumCollapses + + ;
CutBoundaryEdgeSets [ 0 ] . Remove ( CollapseInfo . CollapsedEdge ) ;
CutBoundaryEdgeSets [ 0 ] . Remove ( CollapseInfo . RemovedEdges [ 0 ] ) ;
if ( CollapseInfo . RemovedEdges [ 1 ] ! = - 1 )
2021-02-11 01:44:27 -04:00
{
2021-02-16 14:40:20 -04:00
CutBoundaryEdgeSets [ 0 ] . Remove ( CollapseInfo . RemovedEdges [ 1 ] ) ;
2021-02-11 01:44:27 -04:00
}
}
break ; // if we got through to trying to collapse the edge, don't try to collapse from the other vertex.
}
}
CutBoundaryEdges [ 0 ] = CutBoundaryEdgeSets [ 0 ] . Array ( ) ;
CutBoundaryEdges [ 1 ] = CutBoundaryEdgeSets [ 1 ] . Array ( ) ;
if ( NumCollapses = = LastNumCollapses )
{
break ;
}
CollapseIters + + ;
}
}
2020-04-18 18:42:59 -04:00
bool FMeshBoolean : : MergeEdges ( const FMeshIndexMappings & IndexMaps , FDynamicMesh3 * CutMesh [ 2 ] , const TArray < int > CutBoundaryEdges [ 2 ] , const TMap < int , int > & AllVIDMatches )
{
// translate the edge IDs from CutMesh[1] over to Result mesh edge IDs
TArray < int > OtherMeshEdges ;
for ( int OldMeshEID : CutBoundaryEdges [ 1 ] )
{
2021-02-11 01:44:27 -04:00
if ( ! ensure ( CutMesh [ 1 ] - > IsEdge ( OldMeshEID ) ) )
{
continue ;
}
2020-04-18 18:42:59 -04:00
FIndex2i OtherEV = CutMesh [ 1 ] - > GetEdgeV ( OldMeshEID ) ;
int MappedEID = Result - > FindEdge ( IndexMaps . GetNewVertex ( OtherEV . A ) , IndexMaps . GetNewVertex ( OtherEV . B ) ) ;
if ( ensure ( Result - > IsBoundaryEdge ( MappedEID ) ) )
{
OtherMeshEdges . Add ( MappedEID ) ;
}
}
// find "easy" match candidates using the already-made vertex correspondence
TArray < FIndex2i > CandidateMatches ;
2020-06-23 18:40:00 -04:00
TArray < int > UnmatchedEdges ;
2020-04-18 18:42:59 -04:00
for ( int EID : CutBoundaryEdges [ 0 ] )
{
if ( ! ensure ( Result - > IsBoundaryEdge ( EID ) ) )
{
continue ;
}
FIndex2i VIDs = Result - > GetEdgeV ( EID ) ;
const int * OtherA = AllVIDMatches . Find ( VIDs . A ) ;
const int * OtherB = AllVIDMatches . Find ( VIDs . B ) ;
2020-06-23 18:40:00 -04:00
bool bAddedCandidate = false ;
2020-04-18 18:42:59 -04:00
if ( OtherA & & OtherB )
{
int MapOtherA = IndexMaps . GetNewVertex ( * OtherA ) ;
int MapOtherB = IndexMaps . GetNewVertex ( * OtherB ) ;
int OtherEID = Result - > FindEdge ( MapOtherA , MapOtherB ) ;
if ( OtherEID ! = FDynamicMesh3 : : InvalidID )
{
CandidateMatches . Add ( FIndex2i ( EID , OtherEID ) ) ;
2020-06-23 18:40:00 -04:00
bAddedCandidate = true ;
2020-04-18 18:42:59 -04:00
}
}
2020-06-23 18:40:00 -04:00
if ( ! bAddedCandidate )
{
UnmatchedEdges . Add ( EID ) ;
}
2020-04-18 18:42:59 -04:00
}
// merge the easy matches
for ( FIndex2i Candidate : CandidateMatches )
{
if ( ! Result - > IsEdge ( Candidate . A ) | | ! Result - > IsBoundaryEdge ( Candidate . A ) )
{
continue ;
}
FDynamicMesh3 : : FMergeEdgesInfo MergeInfo ;
EMeshResult EdgeMergeResult = Result - > MergeEdges ( Candidate . A , Candidate . B , MergeInfo ) ;
if ( EdgeMergeResult ! = EMeshResult : : Ok )
{
UnmatchedEdges . Add ( Candidate . A ) ;
}
2020-12-09 14:18:38 -04:00
else
{
if ( bTrackAllNewEdges )
{
AllNewEdges . Add ( Candidate . A ) ;
}
}
2020-04-18 18:42:59 -04:00
}
// filter matched edges from the edge array for the other mesh
OtherMeshEdges . SetNum ( Algo : : RemoveIf ( OtherMeshEdges , [ this ] ( int EID )
{
return ! Result - > IsEdge ( EID ) | | ! Result - > IsBoundaryEdge ( EID ) ;
} ) ) ;
// see if we can match anything else
2021-02-11 01:44:27 -04:00
bool bAllMatched = true ;
2020-04-18 18:42:59 -04:00
if ( UnmatchedEdges . Num ( ) > 0 )
{
// greedily match within snap tolerance
double SnapToleranceSq = SnapTolerance * SnapTolerance ;
for ( int OtherEID : OtherMeshEdges )
{
if ( ! Result - > IsEdge ( OtherEID ) | | ! Result - > IsBoundaryEdge ( OtherEID ) )
{
continue ;
}
FVector3d OA , OB ;
Result - > GetEdgeV ( OtherEID , OA , OB ) ;
for ( int UnmatchedIdx = 0 ; UnmatchedIdx < UnmatchedEdges . Num ( ) ; UnmatchedIdx + + )
{
int EID = UnmatchedEdges [ UnmatchedIdx ] ;
if ( ! Result - > IsEdge ( EID ) | | ! Result - > IsBoundaryEdge ( EID ) )
{
UnmatchedEdges . RemoveAtSwap ( UnmatchedIdx , 1 , false ) ;
UnmatchedIdx - - ;
continue ;
}
FVector3d A , B ;
Result - > GetEdgeV ( EID , A , B ) ;
2021-03-30 21:25:22 -04:00
if ( DistanceSquared ( OA , A ) < SnapToleranceSq & & DistanceSquared ( OB , B ) < SnapToleranceSq )
2020-04-18 18:42:59 -04:00
{
FDynamicMesh3 : : FMergeEdgesInfo MergeInfo ;
EMeshResult EdgeMergeResult = Result - > MergeEdges ( EID , OtherEID , MergeInfo ) ;
if ( EdgeMergeResult = = EMeshResult : : Ok )
{
UnmatchedEdges . RemoveAtSwap ( UnmatchedIdx , 1 , false ) ;
2020-12-09 14:18:38 -04:00
if ( bTrackAllNewEdges )
{
AllNewEdges . Add ( EID ) ;
}
2020-04-18 18:42:59 -04:00
break ;
}
}
}
}
// store the failure cases from the first mesh's array
for ( int EID : UnmatchedEdges )
{
if ( Result - > IsEdge ( EID ) & & Result - > IsBoundaryEdge ( EID ) )
{
CreatedBoundaryEdges . Add ( EID ) ;
bAllMatched = false ;
}
}
}
// store the failure cases from the second mesh's array
for ( int OtherEID : OtherMeshEdges )
{
if ( Result - > IsEdge ( OtherEID ) & & Result - > IsBoundaryEdge ( OtherEID ) )
{
CreatedBoundaryEdges . Add ( OtherEID ) ;
bAllMatched = false ;
}
}
return bAllMatched ;
}
int FMeshBoolean : : FindNearestEdge ( const FDynamicMesh3 & OnMesh , const TArray < int > & EIDs , FVector3d Pos )
{
int NearEID = FDynamicMesh3 : : InvalidID ;
double NearSqr = SnapTolerance * SnapTolerance ;
FVector3d EdgePts [ 2 ] ;
for ( int EID : EIDs ) {
OnMesh . GetEdgeV ( EID , EdgePts [ 0 ] , EdgePts [ 1 ] ) ;
FSegment3d Seg ( EdgePts [ 0 ] , EdgePts [ 1 ] ) ;
double DSqr = Seg . DistanceSquared ( Pos ) ;
if ( DSqr < NearSqr )
{
NearEID = EID ;
NearSqr = DSqr ;
}
}
return NearEID ;
}