2019-12-27 09:26:59 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
2019-06-04 15:42:48 -04:00
# include "Remesher.h"
2021-06-13 00:36:02 -04:00
# include "DynamicMesh/DynamicMeshAttributeSet.h"
2019-06-04 15:42:48 -04:00
# include "MeshWeights.h"
2020-04-18 18:42:59 -04:00
# include "Async/ParallelFor.h"
2019-06-04 15:42:48 -04:00
2021-03-09 19:33:56 -04:00
using namespace UE : : Geometry ;
2019-06-04 15:42:48 -04:00
void FRemesher : : SetTargetEdgeLength ( double fLength )
{
// from Botsch paper
//MinEdgeLength = fLength * (4.0/5.0);
//MaxEdgeLength = fLength * (4.0/3.0);
// much nicer!! makes sense as when we split, edges are both > min !
MinEdgeLength = fLength * 0.66 ;
MaxEdgeLength = fLength * 1.33 ;
}
void FRemesher : : Precompute ( )
{
// if we know Mesh is closed, we can skip is-boundary checks, which makes
// the flip-valence tests much faster!
bMeshIsClosed = true ;
for ( int eid : Mesh - > EdgeIndicesItr ( ) )
{
if ( Mesh - > IsBoundaryEdge ( eid ) )
{
bMeshIsClosed = false ;
break ;
}
}
}
void FRemesher : : BasicRemeshPass ( )
{
if ( Mesh - > TriangleCount ( ) = = 0 ) // badness if we don't catch this...
{
return ;
}
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. " ) ) ;
}
2019-06-04 15:42:48 -04:00
ProfileBeginPass ( ) ;
// Iterate over all edges in the mesh at start of pass.
// Some may be removed, so we skip those.
// However, some old eid's may also be re-used, so we will touch
// some new edges. Can't see how we could efficiently prevent this.
//
ProfileBeginOps ( ) ;
ModifiedEdgesLastPass = 0 ;
{
2021-01-27 01:53:01 -04:00
int cur_eid = StartEdges ( ) ;
bool done = false ;
int IterationCount = 0 ;
TRACE_CPUPROFILER_EVENT_SCOPE ( Remesher_EdgesPass ) ;
do
2019-06-04 15:42:48 -04:00
{
2021-01-27 01:53:01 -04:00
if ( Mesh - > IsEdge ( cur_eid ) )
2019-06-04 15:42:48 -04:00
{
2021-01-27 01:53:01 -04:00
EProcessResult result = ProcessEdge ( cur_eid ) ;
if ( result = = EProcessResult : : Ok_Collapsed | | result = = EProcessResult : : Ok_Flipped | | result = = EProcessResult : : Ok_Split )
{
ModifiedEdgesLastPass + + ;
}
2019-06-04 15:42:48 -04:00
}
2021-01-27 01:53:01 -04:00
if ( IterationCount + + % 1000 = = 0 & & Cancelled ( ) ) // expensive to check every iter?
{
return ;
}
cur_eid = GetNextEdge ( cur_eid , done ) ;
} while ( done = = false ) ;
}
2019-06-04 15:42:48 -04:00
ProfileEndOps ( ) ;
if ( Cancelled ( ) )
{
return ;
}
ProfileBeginSmooth ( ) ;
2019-10-18 13:04:19 -04:00
if ( bEnableSmoothing & & ( SmoothSpeedT > 0 | | CustomSmoothSpeedF ) )
2019-06-04 15:42:48 -04:00
{
2021-01-27 01:53:01 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( Remesher_SmoothingPass ) ;
2019-06-04 15:42:48 -04:00
if ( bEnableSmoothInPlace )
{
//FullSmoothPass_InPlace(EnableParallelSmooth);
check ( false ) ;
}
else
{
FullSmoothPass_Buffer ( bEnableParallelSmooth ) ;
}
DoDebugChecks ( ) ;
}
ProfileEndSmooth ( ) ;
if ( Cancelled ( ) )
{
return ;
}
ProfileBeginProject ( ) ;
if ( ProjTarget ! = nullptr & & ProjectionMode = = ETargetProjectionMode : : AfterRefinement )
{
2021-01-27 01:53:01 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( Remesher_ProjectionPass ) ;
2020-04-18 18:42:59 -04:00
FullProjectionPass ( bEnableParallelProjection ) ;
2019-06-04 15:42:48 -04:00
DoDebugChecks ( ) ;
}
ProfileEndProject ( ) ;
DoDebugChecks ( true ) ;
if ( Cancelled ( ) )
{
return ;
}
ProfileEndPass ( ) ;
}
FRemesher : : EProcessResult FRemesher : : ProcessEdge ( int edgeID )
{
RuntimeDebugCheck ( edgeID ) ;
FEdgeConstraint constraint =
2020-01-27 20:11:15 -05:00
( ! Constraints ) ? FEdgeConstraint : : Unconstrained ( ) : Constraints - > GetEdgeConstraint ( edgeID ) ;
2019-06-04 15:42:48 -04:00
if ( constraint . NoModifications ( ) )
{
return EProcessResult : : Ignored_EdgeIsFullyConstrained ;
}
// look up verts and tris for this edge
2021-01-27 01:53:01 -04:00
FDynamicMesh3 : : FEdge Edge ( Mesh - > GetEdge ( edgeID ) ) ;
2020-06-23 18:40:00 -04:00
int a = Edge . Vert [ 0 ] , b = Edge . Vert [ 1 ] ;
int t0 = Edge . Tri [ 0 ] , t1 = Edge . Tri [ 1 ] ;
2019-06-04 15:42:48 -04:00
bool bIsBoundaryEdge = ( t1 = = IndexConstants : : InvalidID ) ;
2021-01-27 01:53:01 -04:00
FVector3d vA ( Mesh - > GetVertex ( a ) ) ;
FVector3d vB ( Mesh - > GetVertex ( b ) ) ;
2021-03-30 21:25:22 -04:00
double edge_len_sqr = DistanceSquared ( vA , vB ) ;
2019-06-04 15:42:48 -04:00
// check if we should collapse, and also find which vertex we should collapse to,
// in cases where we have constraints/etc
int collapse_to = - 1 ;
bool bCanCollapse = bEnableCollapses
& & constraint . CanCollapse ( )
2021-01-27 01:53:01 -04:00
& & edge_len_sqr < MinEdgeLength * MinEdgeLength ;
2019-06-04 15:42:48 -04:00
// if edge length is too short, we want to collapse it
bool bTriedCollapse = false ;
if ( bCanCollapse )
{
2021-01-27 01:53:01 -04:00
// look up 'other' verts c (from t0) and d (from t1, if it exists)
FIndex2i ov = Mesh - > GetEdgeOpposingV ( edgeID ) ;
int c = ov [ 0 ] , d = ov [ 1 ] ;
if ( CanCollapseEdge ( edgeID , a , b , c , d , t0 , t1 , collapse_to ) = = false )
{
goto abort_collapse ;
}
// optimization: if edge cd exists, we cannot collapse or flip. look that up here?
// funcs will do it internally...
// (or maybe we can collapse if cd exists? edge-collapse doesn't check for it explicitly...)
2019-06-04 15:42:48 -04:00
double collapse_t = 0.5 ; // need to know t-value along edge to update lerpable attributes properly
FVector3d vNewPos = ( vA + vB ) * collapse_t ;
2020-08-11 01:36:57 -04:00
int iKeep = b ;
int iCollapse = a ;
2019-06-04 15:42:48 -04:00
// if either vtx is fixed, collapse to that position
2020-08-11 01:36:57 -04:00
if ( collapse_to = = b )
2019-06-04 15:42:48 -04:00
{
2020-08-11 01:36:57 -04:00
collapse_t = 1.0 ;
2019-06-04 15:42:48 -04:00
vNewPos = vB ;
}
2020-08-11 01:36:57 -04:00
else if ( collapse_to = = a )
2019-06-04 15:42:48 -04:00
{
iKeep = a ; iCollapse = b ;
collapse_t = 0 ;
vNewPos = vA ;
}
else
{
vNewPos = GetProjectedCollapsePosition ( iKeep , vNewPos ) ;
2021-03-30 21:25:22 -04:00
double div = Distance ( vA , vB ) ;
collapse_t = ( div < FMathd : : ZeroTolerance ) ? 0.5 : ( Distance ( vNewPos , Mesh - > GetVertex ( iKeep ) ) ) / div ;
2019-06-04 15:42:48 -04:00
collapse_t = VectorUtil : : Clamp ( collapse_t , 0.0 , 1.0 ) ;
}
// if new position would flip normal of one of the existing triangles
// either one-ring, don't allow it
if ( bPreventNormalFlips )
{
if ( CheckIfCollapseCreatesFlipOrInvalid ( a , b , vNewPos , t0 , t1 ) | | CheckIfCollapseCreatesFlipOrInvalid ( b , a , vNewPos , t0 , t1 ) )
{
goto abort_collapse ;
}
}
// lots of cases where we cannot collapse, but we should just let
// mesh sort that out, right?
2020-03-26 17:20:25 -04:00
SaveEdgeBeforeModify ( edgeID ) ;
2019-06-04 15:42:48 -04:00
COUNT_COLLAPSES + + ;
FDynamicMesh3 : : FEdgeCollapseInfo collapseInfo ;
EMeshResult result = Mesh - > CollapseEdge ( iKeep , iCollapse , collapse_t , collapseInfo ) ;
if ( result = = EMeshResult : : Ok )
{
Mesh - > SetVertex ( iKeep , vNewPos ) ;
2020-01-27 20:11:15 -05:00
if ( Constraints )
2019-06-04 15:42:48 -04:00
{
Constraints - > ClearEdgeConstraint ( edgeID ) ;
Constraints - > ClearEdgeConstraint ( collapseInfo . RemovedEdges . A ) ;
if ( collapseInfo . RemovedEdges . B ! = IndexConstants : : InvalidID )
{
Constraints - > ClearEdgeConstraint ( collapseInfo . RemovedEdges . B ) ;
}
Constraints - > ClearVertexConstraint ( iCollapse ) ;
}
OnEdgeCollapse ( edgeID , iKeep , iCollapse , collapseInfo ) ;
DoDebugChecks ( ) ;
return EProcessResult : : Ok_Collapsed ;
}
else
{
bTriedCollapse = true ;
}
}
abort_collapse :
// if this is not a boundary edge, maybe we want to flip
bool bTriedFlip = false ;
if ( bEnableFlips & & constraint . CanFlip ( ) & & bIsBoundaryEdge = = false )
{
2021-01-27 01:53:01 -04:00
// look up 'other' verts c (from t0) and d (from t1, if it exists)
FIndex2i ov = Mesh - > GetEdgeOpposingV ( edgeID ) ;
int c = ov [ 0 ] , d = ov [ 1 ] ;
2019-06-04 15:42:48 -04:00
// can we do this more efficiently somehow?
2020-06-23 18:40:00 -04:00
bool bTryFlip = false ;
if ( FlipMetric = = EFlipMetric : : OptimalValence )
{
bool a_is_boundary_vtx = ( bMeshIsClosed ) ? false : ( bIsBoundaryEdge | | Mesh - > IsBoundaryVertex ( a ) ) ;
bool b_is_boundary_vtx = ( bMeshIsClosed ) ? false : ( bIsBoundaryEdge | | Mesh - > IsBoundaryVertex ( b ) ) ;
bool c_is_boundary_vtx = ( bMeshIsClosed ) ? false : Mesh - > IsBoundaryVertex ( c ) ;
bool d_is_boundary_vtx = ( bMeshIsClosed ) ? false : Mesh - > IsBoundaryVertex ( d ) ;
int valence_a = Mesh - > GetVtxEdgeCount ( a ) , valence_b = Mesh - > GetVtxEdgeCount ( b ) ;
int valence_c = Mesh - > GetVtxEdgeCount ( c ) , valence_d = Mesh - > GetVtxEdgeCount ( d ) ;
int valence_a_target = ( a_is_boundary_vtx ) ? valence_a : 6 ;
int valence_b_target = ( b_is_boundary_vtx ) ? valence_b : 6 ;
int valence_c_target = ( c_is_boundary_vtx ) ? valence_c : 6 ;
int valence_d_target = ( d_is_boundary_vtx ) ? valence_d : 6 ;
2019-06-04 15:42:48 -04:00
2020-06-23 18:40:00 -04:00
// if total valence error improves by flip, we want to do it
int curr_err = abs ( valence_a - valence_a_target ) + abs ( valence_b - valence_b_target )
+ abs ( valence_c - valence_c_target ) + abs ( valence_d - valence_d_target ) ;
int flip_err = abs ( ( valence_a - 1 ) - valence_a_target ) + abs ( ( valence_b - 1 ) - valence_b_target )
+ abs ( ( valence_c + 1 ) - valence_c_target ) + abs ( ( valence_d + 1 ) - valence_d_target ) ;
bTryFlip = flip_err < curr_err ;
}
else
{
2021-03-30 21:25:22 -04:00
double CurDistSqr = DistanceSquared ( Mesh - > GetVertex ( a ) , Mesh - > GetVertex ( b ) ) ;
double FlipDistSqr = DistanceSquared ( Mesh - > GetVertex ( c ) , Mesh - > GetVertex ( d ) ) ;
2020-06-23 18:40:00 -04:00
bTryFlip = ( FlipDistSqr < MinLengthFlipThresh * MinLengthFlipThresh * CurDistSqr ) ;
}
2019-06-04 15:42:48 -04:00
if ( bTryFlip & & bPreventNormalFlips & & CheckIfFlipInvertsNormals ( a , b , c , d , t0 ) )
{
bTryFlip = false ;
}
if ( bTryFlip )
{
2020-03-26 17:20:25 -04:00
SaveEdgeBeforeModify ( edgeID ) ;
2019-06-04 15:42:48 -04:00
FDynamicMesh3 : : FEdgeFlipInfo flipInfo ;
COUNT_FLIPS + + ;
EMeshResult result = Mesh - > FlipEdge ( edgeID , flipInfo ) ;
if ( result = = EMeshResult : : Ok )
{
2019-09-10 11:35:20 -04:00
OnEdgeFlip ( edgeID , flipInfo ) ;
2019-06-04 15:42:48 -04:00
DoDebugChecks ( ) ;
return EProcessResult : : Ok_Flipped ;
}
else
{
bTriedFlip = true ;
}
}
}
// if edge length is too long, we want to split it
bool bTriedSplit = false ;
if ( bEnableSplits & & constraint . CanSplit ( ) & & edge_len_sqr > MaxEdgeLength * MaxEdgeLength )
{
2020-03-26 17:20:25 -04:00
SaveEdgeBeforeModify ( edgeID ) ;
2019-06-04 15:42:48 -04:00
FDynamicMesh3 : : FEdgeSplitInfo SplitInfo ;
COUNT_SPLITS + + ;
EMeshResult result = Mesh - > SplitEdge ( edgeID , SplitInfo ) ;
if ( result = = EMeshResult : : Ok )
{
UpdateAfterSplit ( edgeID , a , b , SplitInfo ) ;
OnEdgeSplit ( edgeID , a , b , SplitInfo ) ;
DoDebugChecks ( ) ;
return EProcessResult : : Ok_Split ;
}
else
{
bTriedSplit = true ;
}
}
if ( bTriedFlip | | bTriedSplit | | bTriedCollapse )
{
return EProcessResult : : Failed_OpNotSuccessful ;
}
else
{
return EProcessResult : : Ignored_EdgeIsFine ;
}
}
void FRemesher : : UpdateAfterSplit ( int edgeID , int va , int vb , const FDynamicMesh3 : : FEdgeSplitInfo & SplitInfo )
{
bool bPositionFixed = false ;
2020-01-27 20:11:15 -05:00
if ( Constraints & & Constraints - > HasEdgeConstraint ( edgeID ) )
2019-06-04 15:42:48 -04:00
{
// inherit edge constraint
Constraints - > SetOrUpdateEdgeConstraint ( SplitInfo . NewEdges . A , Constraints - > GetEdgeConstraint ( edgeID ) ) ;
2020-04-18 18:42:59 -04:00
// Update vertex constraints. Note that there is some ambiguity here.
2019-06-04 15:42:48 -04:00
// Both verts being constrained doesn't inherently mean that the edge is on
// a constraint, that's why these checks are only applied if edge is constrained.
// But constrained edge doesn't necessarily mean we want to inherit vert constraints!!
//
// although, pretty safe to assume that we would at least disable flips
// if both vertices are constrained to same line/curve. So, maybe this makes sense...
//
2020-04-18 18:42:59 -04:00
// (TODO: perhaps edge constraint should be explicitly tagged to resolve this ambiguity??)
2019-06-04 15:42:48 -04:00
// vert inherits Fixed if both orig edge verts Fixed, and both tagged with same SetID
FVertexConstraint ca = Constraints - > GetVertexConstraint ( va ) ;
FVertexConstraint cb = Constraints - > GetVertexConstraint ( vb ) ;
2020-08-11 01:36:57 -04:00
if ( ca . bCannotDelete & & cb . bCannotDelete )
2019-06-04 15:42:48 -04:00
{
int nSetID = ( ca . FixedSetID > 0 & & ca . FixedSetID = = cb . FixedSetID ) ?
ca . FixedSetID : FVertexConstraint : : InvalidSetID ;
2020-08-11 01:36:57 -04:00
bool bMovable = ca . bCanMove & & cb . bCanMove ;
2019-06-04 15:42:48 -04:00
Constraints - > SetOrUpdateVertexConstraint ( SplitInfo . NewVertex ,
2019-06-05 11:53:10 -04:00
FVertexConstraint ( true , bMovable , nSetID ) ) ;
2019-06-04 15:42:48 -04:00
bPositionFixed = true ;
}
// vert inherits Target if:
2020-08-11 01:36:57 -04:00
// 1) both source verts and edge have same Target, and is same as edge target, or
// 2) either vert has same target as edge, and other vert can't move
2019-06-04 15:42:48 -04:00
if ( ca . Target ! = nullptr | | cb . Target ! = nullptr )
{
IProjectionTarget * edge_target = Constraints - > GetEdgeConstraint ( edgeID ) . Target ;
IProjectionTarget * set_target = nullptr ;
if ( ca . Target = = cb . Target & & ca . Target = = edge_target )
{
set_target = edge_target ;
}
2020-08-11 01:36:57 -04:00
else if ( ca . Target = = edge_target & & ! cb . bCanMove )
2019-06-04 15:42:48 -04:00
{
set_target = edge_target ;
}
2020-08-11 01:36:57 -04:00
else if ( cb . Target = = edge_target & & ! ca . bCanMove )
2019-06-04 15:42:48 -04:00
{
set_target = edge_target ;
}
if ( set_target ! = nullptr )
{
Constraints - > SetOrUpdateVertexConstraint ( SplitInfo . NewVertex ,
2019-06-05 11:53:10 -04:00
FVertexConstraint ( set_target ) ) ;
2019-06-04 15:42:48 -04:00
ProjectVertex ( SplitInfo . NewVertex , set_target ) ;
bPositionFixed = true ;
}
}
}
if ( EnableInlineProjection ( ) & & bPositionFixed = = false & & ProjTarget ! = nullptr )
{
ProjectVertex ( SplitInfo . NewVertex , ProjTarget ) ;
}
}
void FRemesher : : ProjectVertex ( int VertexID , IProjectionTarget * UseTarget )
{
FVector3d curpos = Mesh - > GetVertex ( VertexID ) ;
FVector3d projected = UseTarget - > Project ( curpos , VertexID ) ;
Mesh - > SetVertex ( VertexID , projected ) ;
}
// used by collapse-edge to get projected position for new vertex
FVector3d FRemesher : : GetProjectedCollapsePosition ( int vid , const FVector3d & vNewPos )
{
2020-01-27 20:11:15 -05:00
if ( Constraints )
2019-06-04 15:42:48 -04:00
{
FVertexConstraint vc = Constraints - > GetVertexConstraint ( vid ) ;
if ( vc . Target ! = nullptr )
{
return vc . Target - > Project ( vNewPos , vid ) ;
}
2020-08-11 01:36:57 -04:00
if ( vc . bCanMove = = false )
2019-06-04 15:42:48 -04:00
{
return vNewPos ;
}
}
// no constraint applied, so if we have a target surface, project to that
if ( EnableInlineProjection ( ) & & ProjTarget ! = nullptr )
{
if ( VertexControlF = = nullptr | | ( ( int ) VertexControlF ( vid ) & ( int ) EVertexControl : : NoProject ) = = 0 )
{
return ProjTarget - > Project ( vNewPos , vid ) ;
}
}
return vNewPos ;
}
static FVector3d UniformSmooth ( const FDynamicMesh3 & mesh , int vID , double t )
{
FVector3d v = mesh . GetVertex ( vID ) ;
FVector3d c ;
mesh . GetVtxOneRingCentroid ( vID , c ) ;
return ( 1.0 - t ) * v + ( t ) * c ;
}
static FVector3d MeanValueSmooth ( const FDynamicMesh3 & mesh , int vID , double t )
{
FVector3d v = mesh . GetVertex ( vID ) ;
FVector3d c = FMeshWeights : : MeanValueCentroid ( mesh , vID ) ;
return ( 1.0 - t ) * v + ( t ) * c ;
}
static FVector3d CotanSmooth ( const FDynamicMesh3 & mesh , int vID , double t )
{
FVector3d v = mesh . GetVertex ( vID ) ;
FVector3d c = FMeshWeights : : CotanCentroid ( mesh , vID ) ;
return ( 1.0 - t ) * v + ( t ) * c ;
}
2019-09-10 11:35:20 -04:00
TFunction < FVector3d ( const FDynamicMesh3 & , int , double ) > FRemesher : : GetSmoothFunction ( )
{
if ( CustomSmoothF ! = nullptr )
{
return CustomSmoothF ;
}
else if ( SmoothType = = ESmoothTypes : : MeanValue )
{
return MeanValueSmooth ;
}
else if ( SmoothType = = ESmoothTypes : : Cotan )
{
return CotanSmooth ;
}
return UniformSmooth ;
}
2019-06-04 15:42:48 -04:00
void FRemesher : : FullSmoothPass_Buffer ( bool bParallel )
{
2019-09-10 11:35:20 -04:00
TFunction < FVector3d ( const FDynamicMesh3 & , int , double ) > UseSmoothFunc = GetSmoothFunction ( ) ;
2019-06-04 15:42:48 -04:00
2020-04-18 18:42:59 -04:00
if ( bParallel )
2019-06-04 15:42:48 -04:00
{
2020-04-18 18:42:59 -04:00
auto VertexMoveFunction = [ & UseSmoothFunc , this ] ( int VertexID , bool & bModified )
2019-06-04 15:42:48 -04:00
{
2020-04-18 18:42:59 -04:00
return ComputeSmoothedVertexPos ( VertexID , UseSmoothFunc , bModified ) ;
} ;
2019-06-04 15:42:48 -04:00
2020-04-18 18:42:59 -04:00
MoveVerticesParallel ( VertexMoveFunction ) ;
}
else
{
// Serial
2019-06-04 15:42:48 -04:00
2020-04-18 18:42:59 -04:00
InitializeVertexBufferForPass ( ) ;
auto SmoothAndUpdateFunc = [ this , UseSmoothFunc ] ( int vID )
{
bool bModified = false ;
FVector3d vSmoothed = ComputeSmoothedVertexPos ( vID , UseSmoothFunc , bModified ) ;
if ( bModified )
{
TempFlagBuffer [ vID ] = true ;
TempPosBuffer [ vID ] = vSmoothed ;
}
} ;
ApplyToSmoothVertices ( SmoothAndUpdateFunc ) ;
ApplyVertexBuffer ( false ) ;
}
2019-06-04 15:42:48 -04:00
}
void FRemesher : : InitializeVertexBufferForPass ( )
{
if ( ( int ) TempPosBuffer . GetLength ( ) < Mesh - > MaxVertexID ( ) )
{
TempPosBuffer . Resize ( Mesh - > MaxVertexID ( ) + Mesh - > MaxVertexID ( ) / 5 ) ;
}
2020-04-18 18:42:59 -04:00
if ( TempFlagBuffer . Num ( ) < Mesh - > MaxVertexID ( ) )
2019-06-04 15:42:48 -04:00
{
TempFlagBuffer . SetNum ( 2 * Mesh - > MaxVertexID ( ) ) ;
}
TempFlagBuffer . Init ( false , TempFlagBuffer . Num ( ) ) ;
}
2020-04-18 18:42:59 -04:00
2019-06-04 15:42:48 -04:00
void FRemesher : : ApplyVertexBuffer ( bool bParallel )
{
2020-04-18 18:42:59 -04:00
check ( TempFlagBuffer . Num ( ) > = Mesh - > MaxVertexID ( ) ) ;
check ( static_cast < int > ( TempPosBuffer . Num ( ) ) > = Mesh - > MaxVertexID ( ) ) ;
if ( ! bParallel )
2019-06-04 15:42:48 -04:00
{
2020-04-18 18:42:59 -04:00
// Serial
for ( int vid : Mesh - > VertexIndicesItr ( ) )
2019-06-04 15:42:48 -04:00
{
2020-04-18 18:42:59 -04:00
if ( TempFlagBuffer [ vid ] )
{
GeometryProcessing: clean up mesh timestamps.
- remove FDynamicMesh3::Timestamp (unused), rename Shape/Topology Timestamps to Shape/TopologyChangeStamp, change to atomic<uint32>
- add FDynamicMesh3::bEnableShapeChangeStamp, default to false, to disable ShapeChange tracking. Add ::SetShapeChangeStampEnabled() and ::HasShapeChangeStampEnabled() to configure.
- replace FDynamicMesh3::UpdateTimestamps() with UpdateChangeStamps()
- add bTrackChange param to FDynamicMesh3::SetVertex(), optionally updates ShapeChangeStamp (if enabled). Default true. Remove SetVertex_NoTimeStampUpdate(), update call sites.
- add FDynamicMesh3::GetChangeStamp(), returns combination of Shape and Topology stamps as uint64
- rename TTriangleMeshAdapter::GetTimestamp() to GetChangeStamp(), update usages
- remove TPointSetAdapter::Timestamp() (was not used in code)
- update TMeshAABBTree3 to use GetChangeStamp(), update internal checks to call IsValid() instead
- update TFastWindingTree w/ similar changes
- update calls in UVEditor, may require further updates
#rb semion.piskarev
#rnx
#jira none
#preflight 6126904c72e9eb00011434fe
#ROBOMERGE-SOURCE: CL 17310271 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v861-17282326)
[CL 17315112 by ryan schmidt in ue5-release-engine-test branch]
2021-08-26 06:57:55 -04:00
Mesh - > SetVertex ( vid , TempPosBuffer [ vid ] , false ) ;
2020-04-18 18:42:59 -04:00
}
2019-06-04 15:42:48 -04:00
}
GeometryProcessing: clean up mesh timestamps.
- remove FDynamicMesh3::Timestamp (unused), rename Shape/Topology Timestamps to Shape/TopologyChangeStamp, change to atomic<uint32>
- add FDynamicMesh3::bEnableShapeChangeStamp, default to false, to disable ShapeChange tracking. Add ::SetShapeChangeStampEnabled() and ::HasShapeChangeStampEnabled() to configure.
- replace FDynamicMesh3::UpdateTimestamps() with UpdateChangeStamps()
- add bTrackChange param to FDynamicMesh3::SetVertex(), optionally updates ShapeChangeStamp (if enabled). Default true. Remove SetVertex_NoTimeStampUpdate(), update call sites.
- add FDynamicMesh3::GetChangeStamp(), returns combination of Shape and Topology stamps as uint64
- rename TTriangleMeshAdapter::GetTimestamp() to GetChangeStamp(), update usages
- remove TPointSetAdapter::Timestamp() (was not used in code)
- update TMeshAABBTree3 to use GetChangeStamp(), update internal checks to call IsValid() instead
- update TFastWindingTree w/ similar changes
- update calls in UVEditor, may require further updates
#rb semion.piskarev
#rnx
#jira none
#preflight 6126904c72e9eb00011434fe
#ROBOMERGE-SOURCE: CL 17310271 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v861-17282326)
[CL 17315112 by ryan schmidt in ue5-release-engine-test branch]
2021-08-26 06:57:55 -04:00
Mesh - > UpdateChangeStamps ( true , false ) ;
2019-06-04 15:42:48 -04:00
}
2020-04-18 18:42:59 -04:00
else
{
GeometryProcessing: clean up mesh timestamps.
- remove FDynamicMesh3::Timestamp (unused), rename Shape/Topology Timestamps to Shape/TopologyChangeStamp, change to atomic<uint32>
- add FDynamicMesh3::bEnableShapeChangeStamp, default to false, to disable ShapeChange tracking. Add ::SetShapeChangeStampEnabled() and ::HasShapeChangeStampEnabled() to configure.
- replace FDynamicMesh3::UpdateTimestamps() with UpdateChangeStamps()
- add bTrackChange param to FDynamicMesh3::SetVertex(), optionally updates ShapeChangeStamp (if enabled). Default true. Remove SetVertex_NoTimeStampUpdate(), update call sites.
- add FDynamicMesh3::GetChangeStamp(), returns combination of Shape and Topology stamps as uint64
- rename TTriangleMeshAdapter::GetTimestamp() to GetChangeStamp(), update usages
- remove TPointSetAdapter::Timestamp() (was not used in code)
- update TMeshAABBTree3 to use GetChangeStamp(), update internal checks to call IsValid() instead
- update TFastWindingTree w/ similar changes
- update calls in UVEditor, may require further updates
#rb semion.piskarev
#rnx
#jira none
#preflight 6126904c72e9eb00011434fe
#ROBOMERGE-SOURCE: CL 17310271 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v861-17282326)
[CL 17315112 by ryan schmidt in ue5-release-engine-test branch]
2021-08-26 06:57:55 -04:00
// TODO: verify that this batching is still necessary, now that timestamps/locking situation has been improved
2020-04-18 18:42:59 -04:00
const int BatchSize = 1000 ;
const int NumBatches = FMath : : CeilToInt ( Mesh - > MaxVertexID ( ) / static_cast < float > ( BatchSize ) ) ;
ParallelFor ( NumBatches , [ this , BatchSize ] ( int32 BatchID )
{
for ( int VertexID = BatchID * BatchSize ; VertexID < ( BatchID + 1 ) * BatchSize ; + + VertexID )
{
if ( VertexID < TempFlagBuffer . Num ( ) & & TempFlagBuffer [ VertexID ] & & Mesh - > IsVertex ( VertexID ) )
{
GeometryProcessing: clean up mesh timestamps.
- remove FDynamicMesh3::Timestamp (unused), rename Shape/Topology Timestamps to Shape/TopologyChangeStamp, change to atomic<uint32>
- add FDynamicMesh3::bEnableShapeChangeStamp, default to false, to disable ShapeChange tracking. Add ::SetShapeChangeStampEnabled() and ::HasShapeChangeStampEnabled() to configure.
- replace FDynamicMesh3::UpdateTimestamps() with UpdateChangeStamps()
- add bTrackChange param to FDynamicMesh3::SetVertex(), optionally updates ShapeChangeStamp (if enabled). Default true. Remove SetVertex_NoTimeStampUpdate(), update call sites.
- add FDynamicMesh3::GetChangeStamp(), returns combination of Shape and Topology stamps as uint64
- rename TTriangleMeshAdapter::GetTimestamp() to GetChangeStamp(), update usages
- remove TPointSetAdapter::Timestamp() (was not used in code)
- update TMeshAABBTree3 to use GetChangeStamp(), update internal checks to call IsValid() instead
- update TFastWindingTree w/ similar changes
- update calls in UVEditor, may require further updates
#rb semion.piskarev
#rnx
#jira none
#preflight 6126904c72e9eb00011434fe
#ROBOMERGE-SOURCE: CL 17310271 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v861-17282326)
[CL 17315112 by ryan schmidt in ue5-release-engine-test branch]
2021-08-26 06:57:55 -04:00
Mesh - > SetVertex ( VertexID , TempPosBuffer [ VertexID ] , false ) ;
2020-04-18 18:42:59 -04:00
}
}
} , false ) ;
GeometryProcessing: clean up mesh timestamps.
- remove FDynamicMesh3::Timestamp (unused), rename Shape/Topology Timestamps to Shape/TopologyChangeStamp, change to atomic<uint32>
- add FDynamicMesh3::bEnableShapeChangeStamp, default to false, to disable ShapeChange tracking. Add ::SetShapeChangeStampEnabled() and ::HasShapeChangeStampEnabled() to configure.
- replace FDynamicMesh3::UpdateTimestamps() with UpdateChangeStamps()
- add bTrackChange param to FDynamicMesh3::SetVertex(), optionally updates ShapeChangeStamp (if enabled). Default true. Remove SetVertex_NoTimeStampUpdate(), update call sites.
- add FDynamicMesh3::GetChangeStamp(), returns combination of Shape and Topology stamps as uint64
- rename TTriangleMeshAdapter::GetTimestamp() to GetChangeStamp(), update usages
- remove TPointSetAdapter::Timestamp() (was not used in code)
- update TMeshAABBTree3 to use GetChangeStamp(), update internal checks to call IsValid() instead
- update TFastWindingTree w/ similar changes
- update calls in UVEditor, may require further updates
#rb semion.piskarev
#rnx
#jira none
#preflight 6126904c72e9eb00011434fe
#ROBOMERGE-SOURCE: CL 17310271 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v861-17282326)
[CL 17315112 by ryan schmidt in ue5-release-engine-test branch]
2021-08-26 06:57:55 -04:00
Mesh - > UpdateChangeStamps ( true , false ) ;
2020-04-18 18:42:59 -04:00
}
2019-06-04 15:42:48 -04:00
}
FVector3d FRemesher : : ComputeSmoothedVertexPos ( int vID ,
TFunction < FVector3d ( const FDynamicMesh3 & , int , double ) > smoothFunc , bool & bModified )
{
bModified = false ;
FVertexConstraint vConstraint = FVertexConstraint : : Unconstrained ( ) ;
GetVertexConstraint ( vID , vConstraint ) ;
2020-08-11 01:36:57 -04:00
if ( vConstraint . bCanMove = = false )
2019-06-04 15:42:48 -04:00
{
return Mesh - > GetVertex ( vID ) ;
}
EVertexControl vControl = ( VertexControlF = = nullptr ) ? EVertexControl : : AllowAll : VertexControlF ( vID ) ;
if ( ( ( int ) vControl & ( int ) EVertexControl : : NoSmooth ) ! = 0 )
{
return Mesh - > GetVertex ( vID ) ;
}
2019-10-18 13:04:19 -04:00
double UseSmoothSpeed = ( CustomSmoothSpeedF ) ? CustomSmoothSpeedF ( * Mesh , vID ) : SmoothSpeedT ;
if ( UseSmoothSpeed < = 0 )
{
return Mesh - > GetVertex ( vID ) ;
}
2019-06-04 15:42:48 -04:00
FVector3d vSmoothed = smoothFunc ( * Mesh , vID , SmoothSpeedT ) ;
2019-11-07 12:45:47 -05:00
// @todo we should probably make sure that vertex does not move too far here...
checkSlow ( VectorUtil : : IsFinite ( vSmoothed ) ) ;
if ( VectorUtil : : IsFinite ( vSmoothed ) = = false )
{
return Mesh - > GetVertex ( vID ) ;
}
2019-06-04 15:42:48 -04:00
// project onto either vtx constraint target, or surface target
if ( vConstraint . Target ! = nullptr )
{
vSmoothed = vConstraint . Target - > Project ( vSmoothed , vID ) ;
}
else if ( EnableInlineProjection ( ) & & ProjTarget ! = nullptr )
{
if ( ( ( int ) vControl & ( int ) EVertexControl : : NoProject ) = = 0 )
{
vSmoothed = ProjTarget - > Project ( vSmoothed , vID ) ;
}
}
bModified = true ;
return vSmoothed ;
}
void FRemesher : : ApplyToSmoothVertices ( const TFunction < void ( int ) > & VertexSmoothFunc )
{
for ( int vid : Mesh - > VertexIndicesItr ( ) )
{
VertexSmoothFunc ( vid ) ;
}
}
// Project vertices onto projection target.
2020-04-18 18:42:59 -04:00
void FRemesher : : FullProjectionPass ( bool bParallel )
2019-06-04 15:42:48 -04:00
{
2020-04-18 18:42:59 -04:00
if ( bParallel )
2019-06-04 15:42:48 -04:00
{
2020-04-18 18:42:59 -04:00
auto VertexMoveFunction = [ this ] ( int VertexID , bool & bModified )
2019-06-04 15:42:48 -04:00
{
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 ;
} ;
MoveVerticesParallel ( VertexMoveFunction ) ;
}
else
{
// Serial
auto UseProjectionFunc = [ this ] ( int vID )
2019-06-04 15:42:48 -04:00
{
2020-06-23 18:40:00 -04:00
if ( IsVertexPositionConstrained ( vID ) )
2020-04-18 18:42:59 -04:00
{
return ;
}
if ( VertexControlF ! = nullptr & & ( ( int ) VertexControlF ( vID ) & ( int ) EVertexControl : : NoProject ) ! = 0 )
{
return ;
}
FVector3d curpos = Mesh - > GetVertex ( vID ) ;
FVector3d projected = ProjTarget - > Project ( curpos , vID ) ;
Mesh - > SetVertex ( vID , projected ) ;
} ;
2019-06-04 15:42:48 -04:00
2020-04-18 18:42:59 -04:00
ApplyToProjectVertices ( UseProjectionFunc ) ;
}
2019-06-04 15:42:48 -04:00
}
void FRemesher : : ApplyToProjectVertices ( const TFunction < void ( int ) > & VertexProjectFunc )
{
for ( int vid : Mesh - > VertexIndicesItr ( ) )
{
VertexProjectFunc ( vid ) ;
}
}
2020-04-18 18:42:59 -04:00
void FRemesher : : MoveVerticesParallel ( TFunction < FVector3d ( int , bool & ) > NewVertexPosition )
{
// This is done in two 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. Copy all vertex positions saved in step 1 from the buffer into the Mesh data structure. Update the timestamps.
2019-06-04 15:42:48 -04:00
2020-04-18 18:42:59 -04:00
InitializeVertexBufferForPass ( ) ;
2019-06-04 15:42:48 -04:00
2020-04-18 18:42:59 -04:00
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 ) ;
// Finally move the vertex positions according to the buffer
ApplyVertexBuffer ( true ) ;
}