2020-04-18 18:42:59 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "QueueRemesher.h"
# include <iostream>
# include "Async/ParallelFor.h"
2021-03-09 19:33:56 -04:00
using namespace UE : : Geometry ;
2020-04-18 18:42:59 -04:00
void FQueueRemesher : : FastestRemesh ( )
{
2020-10-29 13:38:15 -04:00
if ( Mesh - > HasAttributes ( ) & & GetConstraints ( ) . IsSet ( ) = = false )
{
ensureMsgf ( false , TEXT ( " Input Mesh has Attribute overlays but no Constraints are configured. Use FMeshConstraintsUtil::ConstrainAllBoundariesAndSeams() to create a Constraint Set for Attribute seams. " ) ) ;
}
2020-04-18 18:42:59 -04:00
ResetQueue ( ) ;
// First we do fast splits to hit edge length target
for ( int k = 0 ; k < MaxFastSplitIterations ; + + k )
{
if ( Cancelled ( ) )
{
return ;
}
int nSplits = FastSplitIteration ( ) ;
2022-06-08 15:29:16 -04:00
ModifiedEdgesLastPass = nSplits ;
2020-04-18 18:42:59 -04:00
if ( ( double ) nSplits / ( double ) Mesh - > EdgeCount ( ) < 0.01 )
{
// Call it converged
break ;
}
}
ResetQueue ( ) ;
// Now do Remesh iterations. Disable projection every other iteration to improve speed
ETargetProjectionMode OriginalProjectionMode = ProjectionMode ;
for ( int k = 0 ; k < MaxRemeshIterations - 1 ; + + k )
{
if ( Cancelled ( ) )
{
break ;
}
2022-06-08 15:29:16 -04:00
// if fraction of modified edges falls below threshold, consider the result converged and terminate
auto EarlyTerminationCheck = [ k , this ] ( ) {
double ModifiedEdgeFraction = ( double ) ModifiedEdgesLastPass / ( double ) Mesh - > EdgeCount ( ) ;
return ( k > 10 & & ModifiedEdgeFraction < MinActiveEdgeFraction ) ;
} ;
2020-04-18 18:42:59 -04:00
ProjectionMode = ( k % 2 = = 0 ) ? ETargetProjectionMode : : NoProjection : OriginalProjectionMode ;
2022-06-08 15:29:16 -04:00
RemeshIteration ( EarlyTerminationCheck ) ;
// If fraction of active edges falls below threshold, terminate early
if ( EarlyTerminationCheck ( ) )
{
break ;
}
2020-04-18 18:42:59 -04:00
}
if ( Cancelled ( ) )
{
return ;
}
// Final pass with full projection
ProjectionMode = OriginalProjectionMode ;
2022-06-08 15:29:16 -04:00
RemeshIteration ( [ ] ( ) { return false ; } ) ;
2020-04-18 18:42:59 -04:00
}
int FQueueRemesher : : FastSplitIteration ( )
{
if ( Mesh - > TriangleCount ( ) = = 0 )
{
return 0 ;
}
2020-10-29 13:38:15 -04:00
if ( Mesh - > HasAttributes ( ) & & GetConstraints ( ) . IsSet ( ) = = false )
{
ensureMsgf ( false , TEXT ( " Input Mesh has Attribute overlays but no Constraints are configured. Use FMeshConstraintsUtil::ConstrainAllBoundariesAndSeams() to create a Constraint Set for Attribute seams. " ) ) ;
}
2020-04-18 18:42:59 -04:00
// Temporarily turn off all operations except edge splits
PushState ( ) ;
bEnableFlips = bEnableCollapses = bEnableSmoothing = false ;
ProjectionMode = ETargetProjectionMode : : NoProjection ;
ProfileBeginPass ( ) ;
ProfileBeginOps ( ) ;
// When we split an edge, we need to check it and the adjacent ones we added.
// Because of overhead in ProcessEdge, it is worth it to do a distance-check here
double MaxEdgeLengthSqr = MaxEdgeLength * MaxEdgeLength ;
// Callback after an edge is split: enqueue new edges whose lengths are long enough
2021-01-13 09:10:54 -04:00
PostEdgeSplitFunction = [ this , & MaxEdgeLengthSqr ] ( int EdgeID , int VertexA , int VertexB , int NewVertex ) //-V1047 - This lambda is cleared before routine exit
2020-04-18 18:42:59 -04:00
{
FVector3d NewVertexPosition = Mesh - > GetVertex ( NewVertex ) ;
for ( auto eid : Mesh - > VtxEdgesItr ( NewVertex ) )
{
FIndex2i EdgeVertices = Mesh - > GetEdgeV ( eid ) ;
int OtherVertex = ( EdgeVertices . A = = NewVertex ) ? EdgeVertices . B : EdgeVertices . A ;
2021-03-30 21:25:22 -04:00
if ( DistanceSquared ( Mesh - > GetVertex ( OtherVertex ) , NewVertexPosition ) > MaxEdgeLengthSqr )
2020-04-18 18:42:59 -04:00
{
QueueEdge ( eid ) ;
}
}
} ;
int TotalNumSplitEdges = 0 ;
// Generic lambda to traverse a range of edges and process them. Below we will call this either with iterators
// from the entire Mesh, or iterators over the set of Modified edges.
auto DoSplits = [ this , & TotalNumSplitEdges ] ( const auto & Begin , const auto & End )
{
for ( auto EdgeIter = Begin ; EdgeIter ! = End ; + + EdgeIter )
{
int CurrentEdgeID = * EdgeIter ;
if ( Cancelled ( ) )
{
break ;
}
if ( Mesh - > IsEdge ( CurrentEdgeID ) )
{
EProcessResult result = ProcessEdge ( CurrentEdgeID ) ;
if ( result = = EProcessResult : : Ok_Split )
{
// new edges queued by PostSplitFunction
+ + ModifiedEdgesLastPass ;
+ + TotalNumSplitEdges ;
}
}
}
} ;
if ( ! ModifiedEdges )
{
// FIRST ITERATION
// If the set of ModifiedEdges doesn't exist, this must be the first time through this iteration. In this
// case, iterate over the full set of Mesh edges.
ModifiedEdges = TSet < int > ( ) ; // But first, create the ModifiedEdges set to write into.
DoSplits ( Mesh - > EdgeIndicesItr ( ) . begin ( ) , Mesh - > EdgeIndicesItr ( ) . end ( ) ) ;
}
else
{
// SUBSEQUENT ITERATIONS
// If the set of ModifiedEdges *does* exist, iterate over (a copy of) that set.
EdgeBuffer = MoveTemp ( * ModifiedEdges ) ;
ModifiedEdges - > Reset ( ) ;
DoSplits ( EdgeBuffer . begin ( ) , EdgeBuffer . end ( ) ) ;
}
ProfileEndOps ( ) ;
PostEdgeSplitFunction = nullptr ;
// Turn back on pre-existing operations
PopState ( ) ;
ProfileEndPass ( ) ;
return TotalNumSplitEdges ;
}
2022-06-08 15:29:16 -04:00
void FQueueRemesher : : RemeshIteration ( TFunctionRef < bool ( void ) > EarlyTerminationCheck )
2020-04-18 18:42:59 -04:00
{
2022-06-08 15:29:16 -04:00
ModifiedEdgesLastPass = 0 ;
2020-04-18 18:42:59 -04:00
if ( Mesh - > TriangleCount ( ) = = 0 )
{
return ;
}
ProfileBeginPass ( ) ;
ProfileBeginOps ( ) ;
// Attempt to process an edge, and queue the resulting vertex neighborhoods
auto ProcessCurrentEdge = [ this ] ( int CurrentEdgeID )
{
FIndex2i EdgeVertices = Mesh - > GetEdgeV ( CurrentEdgeID ) ;
FIndex2i OpposingEdgeVertices = Mesh - > GetEdgeOpposingV ( CurrentEdgeID ) ;
EProcessResult result = ProcessEdge ( CurrentEdgeID ) ;
if ( result = = EProcessResult : : Ok_Collapsed | | result = = EProcessResult : : Ok_Flipped | | result = = EProcessResult : : Ok_Split )
{
QueueOneRing ( EdgeVertices . A ) ;
QueueOneRing ( EdgeVertices . B ) ;
QueueOneRing ( OpposingEdgeVertices . A ) ;
QueueOneRing ( OpposingEdgeVertices . B ) ;
+ + ModifiedEdgesLastPass ;
}
} ;
2022-06-08 15:29:16 -04:00
int32 ProcessedEdges = 0 ;
2020-04-18 18:42:59 -04:00
if ( ! ModifiedEdges )
{
// FIRST ITERATION
// If the set of ModifiedEdges doesn't exist, this must be the first time through this iteration. In this
// case, iterate over the full set of Mesh edges.
ModifiedEdges = TSet < int > ( ) ; // But first, create the ModifiedEdges set to write into.
int CurrentEdgeID = StartEdges ( ) ;
bool done = false ;
while ( done = = false )
{
if ( Cancelled ( ) )
{
break ;
}
if ( Mesh - > IsEdge ( CurrentEdgeID ) )
{
ProcessCurrentEdge ( CurrentEdgeID ) ;
}
CurrentEdgeID = GetNextEdge ( CurrentEdgeID , done ) ;
}
}
else
{
// SUBSEQUENT ITERATIONS
// If the set of ModifiedEdges *does* exist, iterate over (a copy of) that set.
EdgeBuffer = MoveTemp ( * ModifiedEdges ) ;
ModifiedEdges - > Reset ( ) ;
2022-06-08 15:29:16 -04:00
for ( int CurrentEdgeID : EdgeBuffer )
2020-04-18 18:42:59 -04:00
{
if ( Cancelled ( ) )
{
break ;
}
if ( ! Mesh - > IsEdge ( CurrentEdgeID ) )
{
continue ;
}
ProcessCurrentEdge ( CurrentEdgeID ) ;
}
}
ProfileEndOps ( ) ;
2022-06-08 15:29:16 -04:00
if ( EarlyTerminationCheck ( ) )
{
return ;
}
2020-04-18 18:42:59 -04:00
if ( Cancelled ( ) )
{
return ;
}
ProfileBeginSmooth ( ) ;
if ( bEnableSmoothing & & SmoothSpeedT > 0 )
{
TrackedFullSmoothPass_Buffer ( bEnableParallelSmooth ) ;
DoDebugChecks ( ) ;
}
ProfileEndSmooth ( ) ;
if ( Cancelled ( ) )
{
return ;
}
ProfileBeginProject ( ) ;
if ( ProjTarget ! = nullptr & & ProjectionMode = = ETargetProjectionMode : : AfterRefinement )
{
TrackedFullProjectionPass ( bEnableParallelProjection ) ;
DoDebugChecks ( ) ;
}
ProfileEndProject ( ) ;
ProfileEndPass ( ) ;
}
void FQueueRemesher : : InitializeVertexBufferForPass ( )
{
FRemesher : : InitializeVertexBufferForPass ( ) ;
// This is not done in FRemesher::InitializeVertexBufferForPass
for ( size_t i = 0 ; i < TempPosBuffer . Num ( ) ; + + i )
{
TempPosBuffer [ i ] = FVector3d ( 0.0 , 0.0 , 0.0 ) ;
}
if ( EdgeShouldBeQueuedBuffer . Num ( ) < Mesh - > MaxEdgeID ( ) )
{
EdgeShouldBeQueuedBuffer . SetNum ( 2 * Mesh - > MaxEdgeID ( ) ) ;
}
EdgeShouldBeQueuedBuffer . Init ( false , EdgeShouldBeQueuedBuffer . Num ( ) ) ;
}
void FQueueRemesher : : TrackedMoveVerticesParallel ( TFunction < FVector3d ( int , bool & ) > NewVertexPosition )
{
// This is done in several passes:
//
// 1. Looping over all vertices, compute new vertex positions and put them in a buffer. Simultaneously fill a
// parallel buffer of bools indicating which vertices were given new positions.
// 2. Loop over all edges and determine if their new lengths would make them a candidate for splitting or collapsing.
// We maintain a temporary buffer of bools, one for each edge, which can be updated in this pass without
// requiring a lock.
// 3. Add all edges identified in step 2 to the queue of modified edges. This is done in serial, reading from the
// buffer of bools from step 2 and writing into ModifiedEdges.
// 4. Copy all vertex positions saved in step 1 from the buffer into the Mesh data structure. Update the timestamps.
// Note: Updating the queue of modified edges as the vertices change (using a mutex around ModifiedEdges) is
// significantly slower than this approach in my testing. The upside of the mutex approach is that it doesn't require
// the auxiliary EdgeShouldBeQueuedBuffer data structure, so it requires less temporary memory.
InitializeVertexBufferForPass ( ) ;
check ( TempFlagBuffer . Num ( ) > = Mesh - > MaxVertexID ( ) ) ;
check ( static_cast < int > ( TempPosBuffer . Num ( ) ) > = Mesh - > MaxVertexID ( ) ) ;
// First compute all vertex displacements and put them into a buffer
ParallelFor ( Mesh - > MaxVertexID ( ) , [ this , NewVertexPosition ] ( int32 VertexID )
{
if ( ! Mesh - > IsVertex ( VertexID ) ) { return ; }
const FVector3d CurrentPosition = Mesh - > GetVertex ( VertexID ) ;
bool bModified = false ;
const FVector3d NewPosition = NewVertexPosition ( VertexID , bModified ) ;
if ( bModified )
{
TempFlagBuffer [ VertexID ] = true ;
TempPosBuffer [ VertexID ] = NewPosition ;
}
} , false ) ;
check ( EdgeShouldBeQueuedBuffer . Num ( ) > = Mesh - > MaxEdgeID ( ) ) ;
// Now check all edges to see if they need to be added to the queue (they do if one of their end points moved.)
ParallelFor ( Mesh - > MaxEdgeID ( ) , [ this ] ( int32 EdgeID )
{
if ( ! Mesh - > IsEdge ( EdgeID ) ) { return ; }
const FIndex2i EdgeVertices = Mesh - > GetEdgeV ( EdgeID ) ;
auto VertexA = EdgeVertices [ 0 ] ; auto VertexB = EdgeVertices [ 1 ] ;
if ( TempFlagBuffer [ VertexA ] )
{
const FVector3d & SmoothedPosition = TempPosBuffer [ VertexA ] ;
const FVector3d OtherVertexPosition = Mesh - > GetVertex ( VertexB ) ;
2021-03-30 21:25:22 -04:00
const double NewEdgeLengthSqr = DistanceSquared ( SmoothedPosition , OtherVertexPosition ) ;
2020-04-18 18:42:59 -04:00
if ( NewEdgeLengthSqr < MinEdgeLength * MinEdgeLength | | NewEdgeLengthSqr > MaxEdgeLength * MaxEdgeLength )
{
EdgeShouldBeQueuedBuffer [ EdgeID ] = true ;
}
}
if ( TempFlagBuffer [ VertexB ] & & ! EdgeShouldBeQueuedBuffer [ EdgeID ] )
{
const FVector3d & SmoothedPosition = TempPosBuffer [ VertexB ] ;
const FVector3d OtherVertexPosition = Mesh - > GetVertex ( VertexA ) ;
2021-03-30 21:25:22 -04:00
const double NewEdgeLengthSqr = DistanceSquared ( SmoothedPosition , OtherVertexPosition ) ;
2020-04-18 18:42:59 -04:00
if ( NewEdgeLengthSqr < MinEdgeLength * MinEdgeLength | | NewEdgeLengthSqr > MaxEdgeLength * MaxEdgeLength )
{
EdgeShouldBeQueuedBuffer [ EdgeID ] = true ;
}
}
} , false ) ;
// Now add all flagged edges to the queue (serial loop due to the shared data structure ModifiedEdges)
for ( auto EdgeID : Mesh - > EdgeIndicesItr ( ) )
{
if ( ! Mesh - > IsEdge ( EdgeID ) ) { continue ; }
if ( EdgeShouldBeQueuedBuffer [ EdgeID ] )
{
QueueEdge ( EdgeID ) ;
}
}
// Finally move the vertex positions according to the buffer
ApplyVertexBuffer ( true ) ;
// Needed for determinism, to get the same results as serial mode.
// TODO: Should we have a switch for deterministic mode?
ModifiedEdges - > Sort ( std : : less < int > ( ) ) ;
}
void FQueueRemesher : : TrackedFullSmoothPass_Buffer ( bool bParallel )
{
if ( bParallel )
{
TFunction < FVector3d ( const FDynamicMesh3 & , int , double ) > UseSmoothFunc = GetSmoothFunction ( ) ;
auto VertexMoveFunction = [ & UseSmoothFunc , this ] ( int VertexID , bool & bModified )
{
return ComputeSmoothedVertexPos ( VertexID , UseSmoothFunc , bModified ) ;
} ;
TrackedMoveVerticesParallel ( VertexMoveFunction ) ;
return ;
}
// Serial:
InitializeVertexBufferForPass ( ) ;
TFunction < FVector3d ( const FDynamicMesh3 & , int , double ) > UseSmoothFunc = GetSmoothFunction ( ) ;
auto SmoothAndUpdateFunc = [ this , & UseSmoothFunc ] ( int VertexID )
{
if ( ! Mesh - > IsVertex ( VertexID ) )
{
return ;
}
FVector3d CurrentPosition = Mesh - > GetVertex ( VertexID ) ;
bool bModified = false ;
FVector3d SmoothedPosition = ComputeSmoothedVertexPos ( VertexID , UseSmoothFunc , bModified ) ;
if ( bModified )
{
TempFlagBuffer [ VertexID ] = true ;
TempPosBuffer [ VertexID ] = SmoothedPosition ;
for ( int EdgeID : Mesh - > VtxEdgesItr ( VertexID ) )
{
FIndex2i EdgeVertices = Mesh - > GetEdgeV ( EdgeID ) ;
int OtherVertexID = ( EdgeVertices . A = = VertexID ) ? EdgeVertices . B : EdgeVertices . A ;
FVector3d OtherVertexPosition = Mesh - > GetVertex ( OtherVertexID ) ;
2021-03-30 21:25:22 -04:00
double NewEdgeLength = Distance ( SmoothedPosition , OtherVertexPosition ) ;
2020-04-18 18:42:59 -04:00
if ( NewEdgeLength < MinEdgeLength | | NewEdgeLength > MaxEdgeLength )
{
QueueEdge ( EdgeID ) ;
}
}
}
} ;
check ( TempFlagBuffer . Num ( ) > = Mesh - > MaxVertexID ( ) ) ;
check ( static_cast < int > ( TempPosBuffer . Num ( ) ) > = Mesh - > MaxVertexID ( ) ) ;
check ( EdgeShouldBeQueuedBuffer . Num ( ) > = Mesh - > MaxEdgeID ( ) ) ;
ApplyToSmoothVertices ( SmoothAndUpdateFunc ) ;
ApplyVertexBuffer ( bParallel ) ;
// Needed for determinism, to get the same results as parallel mode.
// TODO: Should we have a switch for deterministic mode?
ModifiedEdges - > Sort ( std : : less < int > ( ) ) ;
}
void FQueueRemesher : : TrackedFullProjectionPass ( bool bParallel )
{
if ( bParallel )
{
auto VertexMoveFunction = [ this ] ( int VertexID , bool & bModified )
{
2020-06-23 18:40:00 -04:00
bModified = false ;
2020-04-18 18:42:59 -04:00
FVector3d CurrentPosition = Mesh - > GetVertex ( VertexID ) ;
2020-06-23 18:40:00 -04:00
if ( IsVertexPositionConstrained ( VertexID ) )
{
return CurrentPosition ;
}
if ( VertexControlF ! = nullptr & & ( ( int ) VertexControlF ( VertexID ) & ( int ) EVertexControl : : NoProject ) ! = 0 )
{
return CurrentPosition ;
}
2020-04-18 18:42:59 -04:00
FVector3d ProjectedPosition = ProjTarget - > Project ( CurrentPosition , VertexID ) ;
bModified = ! VectorUtil : : EpsilonEqual ( CurrentPosition , ProjectedPosition , FMathd : : ZeroTolerance ) ;
return ProjectedPosition ;
} ;
TrackedMoveVerticesParallel ( VertexMoveFunction ) ;
return ;
}
// Serial:
InitializeVertexBufferForPass ( ) ;
ensure ( ProjTarget ! = nullptr ) ;
auto UseProjectionFunc = [ this ] ( int VertexID )
{
2020-06-23 18:40:00 -04:00
if ( IsVertexPositionConstrained ( VertexID ) )
2020-04-18 18:42:59 -04:00
{
return ;
}
if ( VertexControlF ! = nullptr & & ( ( int ) VertexControlF ( VertexID ) & ( int ) EVertexControl : : NoProject ) ! = 0 )
{
return ;
}
FVector3d CurrentPosition = Mesh - > GetVertex ( VertexID ) ;
FVector3d ProjectedPosition = ProjTarget - > Project ( CurrentPosition , VertexID ) ;
bool bModified = ! VectorUtil : : EpsilonEqual ( CurrentPosition , ProjectedPosition , FMathd : : ZeroTolerance ) ;
if ( bModified )
{
TempFlagBuffer [ VertexID ] = true ;
TempPosBuffer [ VertexID ] = ProjectedPosition ;
for ( int EdgeID : Mesh - > VtxEdgesItr ( VertexID ) )
{
FIndex2i EdgeVertices = Mesh - > GetEdgeV ( EdgeID ) ;
int OtherVertexID = ( EdgeVertices . A = = VertexID ) ? EdgeVertices . B : EdgeVertices . A ;
FVector3d OtherVertexPosition = Mesh - > GetVertex ( OtherVertexID ) ;
2021-03-30 21:25:22 -04:00
double NewEdgeLength = Distance ( ProjectedPosition , OtherVertexPosition ) ;
2020-04-18 18:42:59 -04:00
if ( NewEdgeLength < MinEdgeLength | | NewEdgeLength > MaxEdgeLength )
{
QueueEdge ( EdgeID ) ;
}
}
}
} ;
ApplyToProjectVertices ( UseProjectionFunc ) ;
ApplyVertexBuffer ( bParallel ) ;
// Needed for determinism, to get the same results as parallel mode.
// TODO: Should we have a switch for deterministic mode?
ModifiedEdges - > Sort ( std : : less < int > ( ) ) ;
}