2019-12-27 09:26:59 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
2019-05-02 21:41:24 -04:00
# include "MeshSimplification.h"
2021-02-16 12:58:51 -04:00
# include "MeshConstraintsUtil.h"
2021-06-13 00:36:02 -04:00
# include "DynamicMesh/DynamicMeshAttributeSet.h"
2019-05-02 21:41:24 -04:00
# include "Util/IndexUtil.h"
2020-06-23 18:40:00 -04:00
# include "Async/ParallelFor.h"
2021-01-07 14:37:02 -04:00
# include "Templates/UnrealTypeTraits.h"
2019-05-02 21:41:24 -04:00
2021-03-09 19:33:56 -04:00
using namespace UE : : Geometry ;
2019-05-02 21:41:24 -04:00
2019-05-24 12:36:32 -04:00
template < typename QuadricErrorType >
QuadricErrorType TMeshSimplification < QuadricErrorType > : : ComputeFaceQuadric ( const int tid , FVector3d & nface , FVector3d & c , double & Area ) const
2019-05-02 21:41:24 -04:00
{
2019-05-24 12:36:32 -04:00
// compute the new quadric for this tri.
Mesh - > GetTriInfo ( tid , nface , Area , c ) ;
return FQuadricErrorType ( nface , c ) ;
}
// Face Quadric Error computation specialized for FAttrBasedQuadricErrord
template < >
FAttrBasedQuadricErrord TMeshSimplification < FAttrBasedQuadricErrord > : : ComputeFaceQuadric ( const int tid , FVector3d & nface , FVector3d & c , double & Area ) const
{
// compute the new quadric for this tri.
Mesh - > GetTriInfo ( tid , nface , Area , c ) ;
FVector3f n0 ; FVector3f n1 ; FVector3f n2 ;
if ( NormalOverlay ! = nullptr )
2019-05-02 21:41:24 -04:00
{
2021-09-12 17:30:07 -04:00
if ( NormalOverlay - > IsSetTriangle ( tid ) )
{
NormalOverlay - > GetTriElements ( tid , n0 , n1 , n2 ) ;
}
else
{
FVector3f FaceNormal = ( FVector3f ) Mesh - > GetTriNormal ( tid ) ;
n0 = n1 = n2 = FaceNormal ;
}
2019-05-24 12:36:32 -04:00
}
else
{
FIndex3i vids = Mesh - > GetTriangle ( tid ) ;
n0 = Mesh - > GetVertexNormal ( vids [ 0 ] ) ;
n1 = Mesh - > GetVertexNormal ( vids [ 1 ] ) ;
n2 = Mesh - > GetVertexNormal ( vids [ 2 ] ) ;
2019-05-02 21:41:24 -04:00
}
2019-05-24 12:36:32 -04:00
FVector3d p0 , p1 , p2 ;
Mesh - > GetTriVertices ( tid , p0 , p1 , p2 ) ;
FVector3d n0d ( n0 . X , n0 . Y , n0 . Z ) ;
FVector3d n1d ( n1 . X , n1 . Y , n1 . Z ) ;
FVector3d n2d ( n2 . X , n2 . Y , n2 . Z ) ;
2020-06-23 18:40:00 -04:00
double attrweight = 16. ;
2019-05-24 12:36:32 -04:00
return FQuadricErrorType ( p0 , p1 , p2 , n0d , n1d , n2d , nface , c , attrweight ) ;
}
template < typename QuadricErrorType >
void TMeshSimplification < QuadricErrorType > : : InitializeTriQuadrics ( )
{
const int NT = Mesh - > MaxTriangleID ( ) ;
triQuadrics . SetNum ( NT ) ;
triAreas . SetNum ( NT ) ;
2020-06-23 18:40:00 -04:00
// tested with ParallelFor - no measurable benefit
2019-05-24 12:36:32 -04:00
//@todo parallel version
//gParallel.BlockStartEnd(0, Mesh->MaxTriangleID - 1, (start_tid, end_tid) = > {
FVector3d n , c ;
for ( int tid : Mesh - > TriangleIndicesItr ( ) )
{
triQuadrics [ tid ] = ComputeFaceQuadric ( tid , n , c , triAreas [ tid ] ) ;
}
}
2020-08-11 01:36:57 -04:00
template < typename QuadricErrorType >
void TMeshSimplification < QuadricErrorType > : : InitializeSeamQuadrics ( )
{
// early out if this feature isn't needed.
if ( ! bAllowSeamCollapse )
{
return ;
}
double EdgeWeight = this - > SeamEdgeWeight ;
auto AddSeamQuadric = [ EdgeWeight , this ] ( int eid )
{
FDynamicMesh3 : : FEdge edge = Mesh - > GetEdge ( eid ) ;
FVector3d p0 = Mesh - > GetVertex ( edge . Vert [ 0 ] ) ;
FVector3d p1 = Mesh - > GetVertex ( edge . Vert [ 1 ] ) ;
// face normal
FVector3d nA = Mesh - > GetTriNormal ( edge . Tri . A ) ;
// this constrains the point to a plane aligned with the edge and normal to the face
2021-03-30 21:25:22 -04:00
FSeamQuadricType & seamQuadric = seamQuadrics . Add ( eid , CreateSeamQuadric < double > ( p0 , p1 , nA ) ) ;
2020-08-11 01:36:57 -04:00
// add the other side - this constrains the point to the line where the two planes intersect.
if ( edge . Tri . B ! = FDynamicMesh3 : : InvalidID )
{
FVector3d nB = Mesh - > GetTriNormal ( edge . Tri . B ) ;
2021-03-30 21:25:22 -04:00
seamQuadric . Add ( CreateSeamQuadric < double > ( p0 , p1 , nB ) ) ;
2020-08-11 01:36:57 -04:00
}
seamQuadric . Scale ( EdgeWeight ) ;
} ;
if ( Constraints ) // The edge constraints an entry for each seam, boundary, group boundary and material boundary
{
const auto & EdgeConstraints = Constraints - > GetEdgeConstraints ( ) ;
for ( auto & ConstraintPair : EdgeConstraints )
{
int eid = ConstraintPair . Key ;
AddSeamQuadric ( eid ) ;
}
}
else
{
const FDynamicMeshAttributeSet * Attributes = Mesh - > Attributes ( ) ;
for ( int eid : Mesh - > EdgeIndicesItr ( ) )
{
bool bNeedsQuadric = Mesh - > IsBoundaryEdge ( eid ) ;
bNeedsQuadric = bNeedsQuadric | | Mesh - > IsGroupBoundaryEdge ( eid ) ;
if ( Attributes )
{
bNeedsQuadric = bNeedsQuadric | | Attributes - > IsMaterialBoundaryEdge ( eid ) ;
bNeedsQuadric = bNeedsQuadric | | Attributes - > IsSeamEdge ( eid ) ;
}
if ( bNeedsQuadric )
{
AddSeamQuadric ( eid ) ;
}
}
}
}
2019-05-24 12:36:32 -04:00
template < typename QuadricErrorType >
void TMeshSimplification < QuadricErrorType > : : InitializeVertexQuadrics ( )
{
2019-05-02 21:41:24 -04:00
int NV = Mesh - > MaxVertexID ( ) ;
vertQuadrics . SetNum ( NV ) ;
2020-06-23 18:40:00 -04:00
// tested with ParallelFor - no measurable benefit
2019-05-02 21:41:24 -04:00
//gParallel.BlockStartEnd(0, Mesh->MaxVertexID - 1, (start_vid, end_vid) = > {
for ( int vid : Mesh - > VertexIndicesItr ( ) )
{
2019-05-24 12:36:32 -04:00
vertQuadrics [ vid ] = FQuadricErrorType : : Zero ( ) ;
2019-05-02 21:41:24 -04:00
for ( int tid : Mesh - > VtxTrianglesItr ( vid ) )
{
vertQuadrics [ vid ] . Add ( triAreas [ tid ] , triQuadrics [ tid ] ) ;
}
//check(TMathUtil.EpsilonEqual(0, vertQuadrics[i].Evaluate(Mesh->GetVertex(i)), TMathUtil.Epsilon * 10));
}
}
2020-06-23 18:40:00 -04:00
template < typename QuadricErrorType >
QuadricErrorType TMeshSimplification < QuadricErrorType > : : AssembleEdgeQuadric ( const FDynamicMesh3 : : FEdge & edge ) const
{
2021-01-07 14:37:02 -04:00
// form standard edge quadric as sum of the vertex quadrics for the edge endpoints
QuadricErrorType EdgeQuadric ( vertQuadrics [ edge . Vert . A ] , vertQuadrics [ edge . Vert . B ] ) ;
2021-02-16 12:58:51 -04:00
if ( ! bRetainQuadricMemory )
{
// the edge.Tri faces are double counted. Remove one.
const FIndex2i & Tris = edge . Tri ;
if ( Tris . A ! = FDynamicMesh3 : : InvalidID )
{
EdgeQuadric . Add ( - triAreas [ Tris . A ] , triQuadrics [ Tris . A ] ) ;
}
if ( Tris . B ! = FDynamicMesh3 : : InvalidID )
{
EdgeQuadric . Add ( - triAreas [ Tris . B ] , triQuadrics [ Tris . B ] ) ;
}
}
2021-01-07 14:37:02 -04:00
if ( bAllowSeamCollapse )
{
// lambda that adds any adjacent seam quadrics to the edge quadric
auto AddSeamQuadricsToEdge = [ & , this ] ( int vid )
{
for ( int eid : Mesh - > VtxEdgesItr ( vid ) )
{
if ( const FSeamQuadricType * seamQuadric = seamQuadrics . Find ( eid ) )
{
EdgeQuadric . AddSeamQuadric ( * seamQuadric ) ;
2021-02-16 12:58:51 -04:00
}
2021-01-07 14:37:02 -04:00
}
} ;
// accumulate any adjacent seam quadrics onto this edge quadric.
AddSeamQuadricsToEdge ( edge . Vert . A ) ;
AddSeamQuadricsToEdge ( edge . Vert . B ) ;
}
return EdgeQuadric ;
2020-06-23 18:40:00 -04:00
}
2019-05-02 21:41:24 -04:00
2019-05-24 12:36:32 -04:00
template < typename QuadricErrorType >
void TMeshSimplification < QuadricErrorType > : : InitializeQueue ( )
2019-05-02 21:41:24 -04:00
{
int NE = Mesh - > EdgeCount ( ) ;
int MaxEID = Mesh - > MaxEdgeID ( ) ;
EdgeQuadrics . SetNum ( MaxEID ) ;
EdgeQueue . Initialize ( MaxEID ) ;
TArray < FEdgeError > EdgeErrors ;
2021-10-09 19:40:12 -04:00
EdgeErrors . Init ( FEdgeError { MAX_FLT , - 1 } , MaxEID ) ;
2019-05-02 21:41:24 -04:00
// @todo vertex quadrics can be computed in parallel
//gParallel.BlockStartEnd(0, MaxEID - 1, (start_eid, end_eid) = > {
//for (int eid = start_eid; eid <= end_eid; eid++) {
for ( int eid : Mesh - > EdgeIndicesItr ( ) )
{
2020-06-23 18:40:00 -04:00
FDynamicMesh3 : : FEdge edge = Mesh - > GetEdge ( eid ) ;
FQuadricErrorType Q = AssembleEdgeQuadric ( edge ) ;
FVector3d opt = OptimalPoint ( eid , Q , edge . Vert . A , edge . Vert . B ) ;
2019-05-02 21:41:24 -04:00
EdgeErrors [ eid ] = { ( float ) Q . Evaluate ( opt ) , eid } ;
EdgeQuadrics [ eid ] = QEdge ( eid , Q , opt ) ;
}
// sorted pq insert is faster, so sort edge errors array and index map
EdgeErrors . Sort ( ) ;
// now do inserts
int N = EdgeErrors . Num ( ) ;
2019-05-24 12:36:32 -04:00
for ( int i = 0 ; i < N ; + + i )
2019-05-02 21:41:24 -04:00
{
int eid = EdgeErrors [ i ] . eid ;
if ( Mesh - > IsEdge ( eid ) )
{
2020-06-23 18:40:00 -04:00
QEdge & edge = EdgeQuadrics [ eid ] ;
float error = EdgeErrors [ i ] . error ;
EdgeQueue . Insert ( eid , error ) ;
2019-05-02 21:41:24 -04:00
}
}
/*
// previous code that does unsorted insert. This is marginally slower, but
// might get even slower on larger meshes? have only tried up to about 350k.
// (still, this function is not the bottleneck...)
int cur_eid = StartEdges ( ) ;
bool done = false ;
do {
if ( Mesh - > IsEdge ( cur_eid ) ) {
QEdge edge = EdgeQuadrics [ cur_eid ] ;
double err = errList [ cur_eid ] ;
EdgeQueue . Enqueue ( cur_eid , ( float ) err ) ;
}
cur_eid = GetNextEdge ( cur_eid , out done ) ;
} while ( done = = false ) ;
*/
}
2019-05-24 12:36:32 -04:00
template < typename QuadricErrorType >
FVector3d TMeshSimplification < QuadricErrorType > : : OptimalPoint ( int eid , const FQuadricErrorType & q , int ea , int eb )
2019-05-02 21:41:24 -04:00
{
// if we would like to preserve boundary, we need to know that here
// so that we properly score these edges
if ( bHaveBoundary & & bPreserveBoundaryShape )
{
if ( Mesh - > IsBoundaryEdge ( eid ) )
{
2021-02-23 20:17:08 -04:00
const bool bModeAllowsVertMovement = ( CollapseMode ! = ESimplificationCollapseModes : : MinimalExistingVertexError ) ;
if ( bModeAllowsVertMovement )
{
return ( Mesh - > GetVertex ( ea ) + Mesh - > GetVertex ( eb ) ) * 0.5 ;
} // else MinimalExistingVertexError case below will choose one of the vertex locations
2019-05-02 21:41:24 -04:00
}
else
{
if ( IsBoundaryVertex ( ea ) )
{
return Mesh - > GetVertex ( ea ) ;
}
else if ( IsBoundaryVertex ( eb ) )
{
return Mesh - > GetVertex ( eb ) ;
}
}
}
// [TODO] if we have constraints, we should apply them here, for same reason as bdry above...
2020-08-11 01:36:57 -04:00
switch ( CollapseMode )
2019-05-02 21:41:24 -04:00
{
2020-08-11 01:36:57 -04:00
case ESimplificationCollapseModes : : AverageVertexPosition :
2019-05-02 21:41:24 -04:00
{
2020-08-11 01:36:57 -04:00
return GetProjectedPoint ( ( Mesh - > GetVertex ( ea ) + Mesh - > GetVertex ( eb ) ) * 0.5 ) ;
2019-05-02 21:41:24 -04:00
}
2020-08-11 01:36:57 -04:00
break ;
2019-05-02 21:41:24 -04:00
2020-08-11 01:36:57 -04:00
case ESimplificationCollapseModes : : MinimalExistingVertexError :
2019-05-02 21:41:24 -04:00
{
2020-08-11 01:36:57 -04:00
FVector3d va = Mesh - > GetVertex ( ea ) ;
FVector3d vb = Mesh - > GetVertex ( eb ) ;
double fa = q . Evaluate ( va ) ;
double fb = q . Evaluate ( vb ) ;
if ( fa < fb )
{
return va ;
}
else
{
return vb ;
}
2019-05-02 21:41:24 -04:00
}
2020-08-11 01:36:57 -04:00
break ;
case ESimplificationCollapseModes : : MinimalQuadricPositionError :
2019-05-02 21:41:24 -04:00
{
2020-08-11 01:36:57 -04:00
FVector3d result = FVector3d : : Zero ( ) ;
if ( q . OptimalPoint ( result ) )
{
return GetProjectedPoint ( result ) ;
}
// degenerate matrix, evaluate quadric at edge end and midpoints
// (could do line search here...)
FVector3d va = Mesh - > GetVertex ( ea ) ;
FVector3d vb = Mesh - > GetVertex ( eb ) ;
FVector3d c = GetProjectedPoint ( ( va + vb ) * 0.5 ) ;
double fa = q . Evaluate ( va ) ;
double fb = q . Evaluate ( vb ) ;
double fc = q . Evaluate ( c ) ;
double m = FMath : : Min3 ( fa , fb , fc ) ;
if ( m = = fa )
{
return va ;
}
else if ( m = = fb )
{
return vb ;
}
return c ;
2019-05-02 21:41:24 -04:00
}
2020-08-11 01:36:57 -04:00
break ;
default :
// should never happen
checkSlow ( 0 ) ;
return FVector3d : : Zero ( ) ;
2019-05-02 21:41:24 -04:00
}
}
2021-02-16 12:58:51 -04:00
template < typename QuadricErrorType >
void TMeshSimplification < QuadricErrorType > : : UpdateNeighborhood ( const FDynamicMesh3 : : FEdgeCollapseInfo & collapseInfo )
{
2021-01-07 14:37:02 -04:00
int kvid = collapseInfo . KeptVertex ;
int rvid = collapseInfo . RemovedVertex ;
2020-08-11 01:36:57 -04:00
2021-02-16 12:58:51 -04:00
FIndex2i removedTris = collapseInfo . RemovedTris ;
FIndex2i opposingVerts = collapseInfo . OpposingVerts ;
2020-08-11 01:36:57 -04:00
2021-02-16 12:58:51 -04:00
// --- Update the seam quadrics
if ( bAllowSeamCollapse )
2019-05-02 21:41:24 -04:00
{
2021-02-16 12:58:51 -04:00
FIndex2i removedEdges = collapseInfo . RemovedEdges ;
FIndex2i keptEdges = collapseInfo . KeptEdges ;
2020-08-11 01:36:57 -04:00
2021-02-16 12:58:51 -04:00
// update the map between edge id and seam quadrics
// if constraints exist, they define the edges with seam quadrics
// otherwise require kept edges to have a seam quadric if either
// the kept or collapse edge had a seam quadric.
if ( Constraints ) // quadrics on the constrained edges
2020-08-11 01:36:57 -04:00
{
2021-02-16 12:58:51 -04:00
if ( Constraints - > HasEdgeConstraint ( keptEdges . A ) )
{
seamQuadrics . Add ( keptEdges . A ) ;
}
else
{
seamQuadrics . Remove ( keptEdges . A ) ;
}
2020-08-11 01:36:57 -04:00
2021-02-16 12:58:51 -04:00
if ( keptEdges . B ! = FDynamicMesh3 : : InvalidID )
{
if ( Constraints - > HasEdgeConstraint ( keptEdges . B ) )
{
seamQuadrics . Add ( keptEdges . B ) ;
}
else
{
seamQuadrics . Remove ( keptEdges . B ) ;
}
}
}
else // propagate any existing seam quadric requirements.
{
if ( FSeamQuadricType * seamQuadric = seamQuadrics . Find ( removedEdges . A ) )
{
seamQuadrics . Add ( keptEdges . A ) ;
}
if ( removedEdges . B ! = FDynamicMesh3 : : InvalidID )
{
if ( FSeamQuadricType * seamQuadric = seamQuadrics . Find ( removedEdges . B ) )
{
seamQuadrics . Add ( keptEdges . B ) ;
}
}
}
// removed quadrics from deleted edges
seamQuadrics . Remove ( removedEdges . A ) ;
if ( removedEdges . B ! = FDynamicMesh3 : : InvalidID )
{
seamQuadrics . Remove ( removedEdges . B ) ;
}
// update any seam quadrics adjacent to kvid to reflect changes in the seams
double EdgeWeight = this - > SeamEdgeWeight ;
for ( int eid : Mesh - > VtxEdgesItr ( kvid ) )
{
FDynamicMesh3 : : FEdge ne = Mesh - > GetEdge ( eid ) ;
2020-08-11 01:36:57 -04:00
// need to recompute this seam quadric
if ( FSeamQuadricType * seamQuadric = seamQuadrics . Find ( eid ) )
{
// rebuild the seam quadric
FVector3d p0 = Mesh - > GetVertex ( ne . Vert [ 0 ] ) ;
FVector3d p1 = Mesh - > GetVertex ( ne . Vert [ 1 ] ) ;
// face normal
FVector3d nA = Mesh - > GetTriNormal ( ne . Tri . A ) ;
// this constrains the point to a plane aligned with the edge and normal to the face
2021-03-30 21:25:22 -04:00
* seamQuadric = CreateSeamQuadric < double > ( p0 , p1 , nA ) ;
2020-08-11 01:36:57 -04:00
// add the other side - this constrains the point to the line where the two planes intersect.
if ( ne . Tri . B ! = FDynamicMesh3 : : InvalidID )
{
FVector3d nB = Mesh - > GetTriNormal ( ne . Tri . B ) ;
2021-03-30 21:25:22 -04:00
seamQuadric - > Add ( CreateSeamQuadric < double > ( p0 , p1 , nB ) ) ;
2020-08-11 01:36:57 -04:00
}
seamQuadric - > Scale ( EdgeWeight ) ;
}
2019-05-02 21:41:24 -04:00
}
}
2021-02-16 12:58:51 -04:00
// --- Update the vertex quadrics
if ( bRetainQuadricMemory )
{
// Quadric "memory" the retained vertex quadric is the sum of the two vert quadrics
vertQuadrics [ kvid ] = QuadricErrorType ( vertQuadrics [ kvid ] , vertQuadrics [ rvid ] ) ;
2020-06-23 18:40:00 -04:00
}
2021-02-16 12:58:51 -04:00
else
2020-06-23 18:40:00 -04:00
{
2019-05-24 12:36:32 -04:00
// compute the change in affected face quadrics, and then propagate
2021-02-16 12:58:51 -04:00
// that change to the face adjacent verts.
2019-05-24 12:36:32 -04:00
FVector3d n , c ;
double NewtriArea ;
// Update the triangle areas and quadrics that will have changed
2021-02-16 12:58:51 -04:00
for ( int tid : Mesh - > VtxTrianglesItr ( kvid ) )
2019-05-24 12:36:32 -04:00
{
const double OldtriArea = triAreas [ tid ] ;
const FQuadricErrorType OldtriQuadric = triQuadrics [ tid ] ;
// compute the new quadric for this tri.
FQuadricErrorType NewtriQuadric = ComputeFaceQuadric ( tid , n , c , NewtriArea ) ;
2020-06-23 18:40:00 -04:00
// update the arrays that hold the current face area & quadric
2019-05-24 12:36:32 -04:00
triAreas [ tid ] = NewtriArea ;
triQuadrics [ tid ] = NewtriQuadric ;
FIndex3i tri_vids = Mesh - > GetTriangle ( tid ) ;
// update the vert quadrics that are adjacent to vid.
for ( int32 i = 0 ; i < 3 ; + + i )
{
2021-02-16 12:58:51 -04:00
if ( tri_vids [ i ] = = kvid ) continue ;
2019-05-24 12:36:32 -04:00
// correct the adjacent vertQuadrics
vertQuadrics [ tri_vids [ i ] ] . Add ( - OldtriArea , OldtriQuadric ) ; // subtract old quadric
vertQuadrics [ tri_vids [ i ] ] . Add ( NewtriArea , NewtriQuadric ) ; // add new quadric
}
}
// remove the influence of the dead tris from the two verts that were opposing the collapsed edge
{
for ( int i = 0 ; i < 2 ; + + i )
{
if ( removedTris [ i ] ! = FDynamicMesh3 : : InvalidID )
{
const double oldArea = triAreas [ removedTris [ i ] ] ;
FQuadricErrorType oldQuadric = triQuadrics [ removedTris [ i ] ] ;
// subtract the quadric from the opposing vert
vertQuadrics [ opposingVerts [ i ] ] . Add ( - oldArea , oldQuadric ) ;
2020-06-23 18:40:00 -04:00
// zero out the quadric & area for the removed tris.
triQuadrics [ removedTris [ i ] ] = FQuadricErrorType : : Zero ( ) ;
triAreas [ removedTris [ i ] ] = 0. ;
2019-05-24 12:36:32 -04:00
}
}
}
// Rebuild the quadric for the vert that was retained during the collapse.
// NB: in the version with memory this quadric took the value of the edge quadric that collapsed.
{
2020-06-23 18:40:00 -04:00
FQuadricErrorType vertQuadric = FQuadricErrorType : : Zero ( ) ;
2021-02-16 12:58:51 -04:00
for ( int tid : Mesh - > VtxTrianglesItr ( kvid ) )
2019-05-24 12:36:32 -04:00
{
vertQuadric . Add ( triAreas [ tid ] , triQuadrics [ tid ] ) ;
}
2021-02-16 12:58:51 -04:00
vertQuadrics [ kvid ] = vertQuadric ;
2019-05-24 12:36:32 -04:00
}
2021-02-16 12:58:51 -04:00
}
2020-08-11 01:36:57 -04:00
2021-02-16 12:58:51 -04:00
// --- Update all edge quadrics in the nbrhood
// NB: this has to follow updating all potential seam quadrics adjacent to kvid
// because an edge quadric gathers seam quadrics adjacent the ends
if ( bRetainQuadricMemory )
{
for ( int eid : Mesh - > VtxEdgesItr ( kvid ) )
2020-08-11 01:36:57 -04:00
{
2021-02-16 12:58:51 -04:00
FDynamicMesh3 : : FEdge ne = Mesh - > GetEdge ( eid ) ;
QuadricErrorType Q = AssembleEdgeQuadric ( ne ) ;
FVector3d opt = OptimalPoint ( eid , Q , ne . Vert . A , ne . Vert . B ) ;
float err = ( float ) Q . Evaluate ( opt ) ;
EdgeQuadrics [ eid ] = QEdge ( eid , Q , opt ) ;
if ( EdgeQueue . Contains ( eid ) )
2020-08-11 01:36:57 -04:00
{
2021-02-16 12:58:51 -04:00
EdgeQueue . Update ( eid , err ) ;
2020-08-11 01:36:57 -04:00
}
2021-02-16 12:58:51 -04:00
else
{
EdgeQueue . Insert ( eid , err ) ;
}
}
2020-08-11 01:36:57 -04:00
}
2021-02-16 12:58:51 -04:00
else
2019-05-24 12:36:32 -04:00
{
TArray < int , TInlineAllocator < 64 > > EdgesToUpdate ;
2021-02-16 12:58:51 -04:00
for ( int adjeid : Mesh - > VtxEdgesItr ( kvid ) )
2019-05-24 12:36:32 -04:00
{
2020-06-23 18:40:00 -04:00
EdgesToUpdate . Add ( adjeid ) ;
const FIndex2i Verts = Mesh - > GetEdgeV ( adjeid ) ;
2021-02-16 12:58:51 -04:00
int adjvid = ( Verts [ 0 ] = = kvid ) ? Verts [ 1 ] : Verts [ 0 ] ;
2020-06-23 18:40:00 -04:00
if ( adjvid ! = FDynamicMesh3 : : InvalidID )
2019-05-24 12:36:32 -04:00
{
2020-06-23 18:40:00 -04:00
for ( int eid : Mesh - > VtxEdgesItr ( adjvid ) )
{
if ( eid ! = adjeid )
{
EdgesToUpdate . AddUnique ( eid ) ;
}
}
2019-05-24 12:36:32 -04:00
}
}
for ( int eid : EdgesToUpdate )
{
2020-06-23 18:40:00 -04:00
const FDynamicMesh3 : : FEdge edgeData = Mesh - > GetEdge ( eid ) ;
FQuadricErrorType Q = AssembleEdgeQuadric ( edgeData ) ;
2019-05-24 12:36:32 -04:00
2020-06-23 18:40:00 -04:00
FVector3d opt = OptimalPoint ( eid , Q , edgeData . Vert [ 0 ] , edgeData . Vert [ 1 ] ) ;
float err = ( float ) Q . Evaluate ( opt ) ;
2019-05-24 12:36:32 -04:00
EdgeQuadrics [ eid ] = QEdge ( eid , Q , opt ) ;
if ( EdgeQueue . Contains ( eid ) )
{
2020-06-23 18:40:00 -04:00
EdgeQueue . Update ( eid , err ) ;
2019-05-24 12:36:32 -04:00
}
else
{
2020-06-23 18:40:00 -04:00
EdgeQueue . Insert ( eid , err ) ;
2019-05-24 12:36:32 -04:00
}
}
}
}
2019-05-02 21:41:24 -04:00
2019-05-24 12:36:32 -04:00
template < typename QuadricErrorType >
void TMeshSimplification < QuadricErrorType > : : Precompute ( bool bMeshIsClosed )
2019-05-02 21:41:24 -04:00
{
bHaveBoundary = false ;
IsBoundaryVtxCache . SetNum ( Mesh - > MaxVertexID ( ) ) ;
if ( bMeshIsClosed = = false )
{
for ( int eid : Mesh - > BoundaryEdgeIndicesItr ( ) )
{
FIndex2i ev = Mesh - > GetEdgeV ( eid ) ;
IsBoundaryVtxCache [ ev . A ] = true ;
IsBoundaryVtxCache [ ev . B ] = true ;
bHaveBoundary = true ;
}
}
}
2019-05-24 12:36:32 -04:00
template < typename QuadricErrorType >
void TMeshSimplification < QuadricErrorType > : : DoSimplify ( )
2019-05-02 21:41:24 -04:00
{
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-05-02 21:41:24 -04:00
ProfileBeginPass ( ) ;
ProfileBeginSetup ( ) ;
Precompute ( ) ;
if ( Cancelled ( ) )
{
return ;
}
2019-05-24 12:36:32 -04:00
InitializeTriQuadrics ( ) ;
if ( Cancelled ( ) )
{
return ;
}
2020-08-11 01:36:57 -04:00
InitializeSeamQuadrics ( ) ;
if ( Cancelled ( ) )
{
return ;
}
2019-05-02 21:41:24 -04:00
InitializeVertexQuadrics ( ) ;
if ( Cancelled ( ) )
{
return ;
}
InitializeQueue ( ) ;
if ( Cancelled ( ) )
{
return ;
}
ProfileEndSetup ( ) ;
ProfileBeginOps ( ) ;
ProfileBeginCollapse ( ) ;
2019-05-24 12:36:32 -04:00
while ( EdgeQueue . GetCount ( ) > 0 )
2019-05-02 21:41:24 -04:00
{
// termination criteria
if ( SimplifyMode = = ETargetModes : : VertexCount )
{
if ( Mesh - > VertexCount ( ) < = TargetCount )
{
break ;
}
}
2019-09-13 14:24:58 -04:00
else if ( SimplifyMode = = ETargetModes : : MaxError )
{
float qe = EdgeQueue . GetFirstNodePriority ( ) ;
if ( FMath : : Abs ( qe ) > MaxErrorAllowed )
{
break ;
}
}
2019-05-24 12:36:32 -04:00
else
2019-05-02 21:41:24 -04:00
{
if ( Mesh - > TriangleCount ( ) < = TargetCount )
{
break ;
}
}
2019-09-13 14:24:58 -04:00
2020-06-23 18:40:00 -04:00
COUNT_ITERATIONS + + ;
int eid = EdgeQueue . Dequeue ( ) ;
2019-05-02 21:41:24 -04:00
if ( Mesh - > IsEdge ( eid ) = = false )
{
continue ;
}
if ( Cancelled ( ) )
{
return ;
}
2019-05-24 12:36:32 -04:00
2020-08-11 01:36:57 -04:00
FDynamicMesh3 : : FEdgeCollapseInfo collapseInfo ;
ESimplificationResult result = CollapseEdge ( eid , EdgeQuadrics [ eid ] . collapse_pt , collapseInfo ) ;
2019-05-24 12:36:32 -04:00
if ( result = = ESimplificationResult : : Ok_Collapsed )
2019-05-02 21:41:24 -04:00
{
2021-02-16 12:58:51 -04:00
// update the quadrics
2021-01-07 14:37:02 -04:00
UpdateNeighborhood ( collapseInfo ) ;
2021-02-16 12:58:51 -04:00
}
else if ( result = = ESimplificationResult : : Failed_IsolatedTriangle & & Mesh - > TriangleCount ( ) > 2 )
{
const FDynamicMesh3 : : FEdge Edge = Mesh - > GetEdge ( eid ) ;
RemoveIsolatedTriangle ( Edge . Tri . A ) ;
2019-05-02 21:41:24 -04:00
}
}
ProfileEndCollapse ( ) ;
ProfileEndOps ( ) ;
if ( Cancelled ( ) )
{
return ;
}
2021-02-23 20:17:08 -04:00
// [TODO] - consider, skip this when CollapseMode == ESimplificationCollapseModes::MinimalExistingVertexError ?
2019-05-02 21:41:24 -04:00
Reproject ( ) ;
ProfileEndPass ( ) ;
}
2019-05-24 12:36:32 -04:00
template < typename QuadricErrorType >
void TMeshSimplification < QuadricErrorType > : : SimplifyToTriangleCount ( int nCount )
2019-05-02 21:41:24 -04:00
{
SimplifyMode = ETargetModes : : TriangleCount ;
TargetCount = FMath : : Max ( 1 , nCount ) ;
MinEdgeLength = FMathd : : MaxReal ;
2019-09-13 14:24:58 -04:00
MaxErrorAllowed = FMathf : : MaxReal ;
2019-05-02 21:41:24 -04:00
DoSimplify ( ) ;
}
2019-05-24 12:36:32 -04:00
template < typename QuadricErrorType >
void TMeshSimplification < QuadricErrorType > : : SimplifyToVertexCount ( int nCount )
2019-05-02 21:41:24 -04:00
{
SimplifyMode = ETargetModes : : VertexCount ;
TargetCount = FMath : : Max ( 3 , nCount ) ;
MinEdgeLength = FMathd : : MaxReal ;
2019-09-13 14:24:58 -04:00
MaxErrorAllowed = FMathf : : MaxReal ;
2019-05-02 21:41:24 -04:00
DoSimplify ( ) ;
}
2019-05-24 12:36:32 -04:00
template < typename QuadricErrorType >
void TMeshSimplification < QuadricErrorType > : : SimplifyToEdgeLength ( double minEdgeLen )
2019-05-02 21:41:24 -04:00
{
SimplifyMode = ETargetModes : : MinEdgeLength ;
TargetCount = 1 ;
MinEdgeLength = minEdgeLen ;
2019-09-13 14:24:58 -04:00
MaxErrorAllowed = FMathf : : MaxReal ;
DoSimplify ( ) ;
}
template < typename QuadricErrorType >
void TMeshSimplification < QuadricErrorType > : : SimplifyToMaxError ( double MaxError )
{
SimplifyMode = ETargetModes : : MaxError ;
TargetCount = 1 ;
MinEdgeLength = FMathd : : MaxReal ;
2022-07-08 10:53:04 -04:00
MaxErrorAllowed = ( float ) MaxError ;
2019-05-02 21:41:24 -04:00
DoSimplify ( ) ;
}
2020-06-23 18:40:00 -04:00
template < typename GetTriNormalFuncType >
static bool IsDevelopableVertex ( const FDynamicMesh3 & Mesh , int32 VertexID , double DotTolerance ,
GetTriNormalFuncType GetTriNormalFunc )
{
FVector3d Normal1 , Normal2 ;
int32 Normal1Count = 0 , Normal2Count = 0 , OtherCount = 0 ;
Mesh . EnumerateVertexTriangles ( VertexID , [ & ] ( int32 tid )
{
FVector3d TriNormal = GetTriNormalFunc ( tid ) ;
if ( Normal1Count = = 0 )
{
Normal1 = TriNormal ;
Normal1Count + + ;
return ;
}
if ( TriNormal . Dot ( Normal1 ) > DotTolerance )
{
Normal1Count + + ;
return ;
}
if ( Normal2Count = = 0 )
{
Normal2 = TriNormal ;
Normal2Count + + ;
return ;
}
if ( TriNormal . Dot ( Normal2 ) > DotTolerance )
{
Normal2Count + + ;
return ;
}
OtherCount + + ;
} ) ;
return OtherCount = = 0 ;
}
2023-05-26 13:57:25 -04:00
bool IsCollapsableDevelopableEdge ( const FDynamicMesh3 & Mesh ,
int32 CollapseEdgeID , int32 RemoveV , int32 KeepV , double DotTolerance ,
const TArray < FVector3d > & TriNormals , const TArray < bool > IsBoundaryVtxCache )
2020-06-23 18:40:00 -04:00
{
FIndex2i CollapseEdgeT = Mesh . GetEdgeT ( CollapseEdgeID ) ;
2023-05-26 13:57:25 -04:00
FVector3d Normal1 = TriNormals [ CollapseEdgeT . A ] ;
2020-06-23 18:40:00 -04:00
2023-05-26 13:57:25 -04:00
if ( CollapseEdgeT . B = = IndexConstants : : InvalidID )
{
// If we're collapsing a boundary edge, the only way to avoid changing the shape is for RemoveV
// to be flat and have exactly one other attached boundary edge that is colinear with this one.
// Start by finding the other boundary edge and making sure that there is only one.
bool bFoundSecondBoundaryEdge = false ;
for ( int32 Eid : Mesh . VtxEdgesItr ( RemoveV ) )
{
if ( Eid ! = CollapseEdgeID & & Mesh . IsBoundaryEdge ( Eid ) )
{
if ( bFoundSecondBoundaryEdge )
{
// Found more than one other boundary edge, so not collapsable
return false ;
}
bFoundSecondBoundaryEdge = true ;
// Verify that this second boundary edge is colinear with ours.
FVector3d KeepVert = Mesh . GetVertex ( KeepV ) ;
FVector3d RemoveVert = Mesh . GetVertex ( RemoveV ) ;
int32 OtherV = IndexUtil : : FindEdgeOtherVertex ( Mesh . GetEdgeV ( Eid ) , RemoveV ) ;
FVector3d OtherVert = Mesh . GetVertex ( OtherV ) ;
if ( ! ( Normalized ( RemoveVert - OtherVert ) . Dot ( Normalized ( KeepVert - RemoveVert ) ) > DotTolerance ) )
{
// Not colinear
return false ;
}
}
}
if ( ! bFoundSecondBoundaryEdge )
{
// Seems impossible for a vertex to have exactly one attached boundary edge
return ensure ( false ) ;
}
// If we got to here, we found the other boundary edge, and we'll check for planarity further below.
}
else
{
// If this is not a boundary edge, then remove V must not be a boundary vertex, else we
// would deform the boundary on collapse.
if ( IsBoundaryVtxCache [ RemoveV ] )
{
return false ;
}
}
FVector3d Normal2 = ( CollapseEdgeT . B = = IndexConstants : : InvalidID ) ? FVector3d : : ZeroVector : TriNormals [ CollapseEdgeT . B ] ;
2020-06-23 18:40:00 -04:00
// planar case
2023-05-26 13:57:25 -04:00
if ( CollapseEdgeT . B = = IndexConstants : : InvalidID | | Normal1 . Dot ( Normal2 ) > DotTolerance )
2020-06-23 18:40:00 -04:00
{
bool bIsFlat = true ;
Mesh . EnumerateVertexTriangles ( RemoveV , [ & ] ( int32 tid )
{
2023-05-26 13:57:25 -04:00
if ( TriNormals [ tid ] . Dot ( Normal1 ) < DotTolerance )
2020-06-23 18:40:00 -04:00
{
bIsFlat = false ;
}
} ) ;
return bIsFlat ;
}
// if we are not planar, we need to find the 'other' developable edge at RemoveV.
// This edge must be aligned w/ our collapse edge and have the same normals
FVector3d A = Mesh . GetVertex ( RemoveV ) , B = Mesh . GetVertex ( KeepV ) ;
2021-03-17 19:32:44 -04:00
FVector3d EdgeDir ( B - A ) ; Normalize ( EdgeDir ) ;
2020-06-23 18:40:00 -04:00
int32 FoldEdges = 0 , FlatEdges = 0 , OtherEdges = 0 ;
for ( int32 eid : Mesh . VtxEdgesItr ( RemoveV ) )
{
if ( eid ! = CollapseEdgeID )
{
FIndex2i EdgeT = Mesh . GetEdgeT ( eid ) ;
2020-12-09 14:18:38 -04:00
if ( EdgeT . B = = IndexConstants : : InvalidID )
{
2023-05-26 13:57:25 -04:00
// We already handled the cases where RemoveV is a boundary vert, so this shouldn't happen.
return ensure ( false ) ;
2020-12-09 14:18:38 -04:00
}
2023-05-26 13:57:25 -04:00
FVector3d Normal3 = TriNormals [ EdgeT . A ] ;
FVector3d Normal4 = TriNormals [ EdgeT . B ] ;
2020-06-23 18:40:00 -04:00
FIndex2i OtherEdgeV = Mesh . GetEdgeV ( eid ) ;
int32 OtherV = IndexUtil : : FindEdgeOtherVertex ( OtherEdgeV , RemoveV ) ;
FVector3d C = Mesh . GetVertex ( OtherV ) ;
2021-03-17 19:32:44 -04:00
if ( Normalized ( A - C ) . Dot ( EdgeDir ) > DotTolerance )
2020-06-23 18:40:00 -04:00
{
if ( ( Normal3 . Dot ( Normal1 ) > DotTolerance & & Normal4 . Dot ( Normal2 ) > DotTolerance ) | |
( Normal3 . Dot ( Normal2 ) > DotTolerance & & Normal4 . Dot ( Normal1 ) > DotTolerance ) )
{
FoldEdges + + ;
}
}
else if ( Normal3 . Dot ( Normal4 ) > DotTolerance )
{
FlatEdges + + ;
}
else
{
OtherEdges + + ;
}
}
}
return ( FoldEdges = = 1 & & OtherEdges = = 0 ) ;
}
template < typename QuadricErrorType >
2020-12-09 14:18:38 -04:00
void TMeshSimplification < QuadricErrorType > : : SimplifyToMinimalPlanar (
double CoplanarAngleTolDeg ,
TFunctionRef < bool ( int32 EdgeID ) > EdgeFilterPredicate )
2020-06-23 18:40:00 -04:00
{
# define RETURN_IF_CANCELLED if (Cancelled()) { return; }
if ( Mesh - > TriangleCount ( ) = = 0 ) // badness if we don't catch this...
{
return ;
}
2023-05-26 13:57:25 -04:00
// This function doesn't affect the shape of the boundary, so the value of bPreserveBoundaryShape
// shouldn't matter. Yet in practice, having bPreserveBoundaryShape be true is problematic because
// the related block in CollapseEdge() arbitrarily decides that just one of the verts of a boundary
// edge can be collapsed to.
// TODO: the above is probably a minor bug for other forms of simplification too, but fixing it requires
// going through the details of other simplification methods. For SimplifyToMinimalPlanar, the simplest
// solution is to just eliminate that factor as a concern, since it should be free to collapse along
// colinear boundaries unless they are explicitly constrained.
TGuardValue < bool > PreserveBoundaryShapeOverride ( bPreserveBoundaryShape , false ) ; // Sets to false, restores on exit
2020-06-23 18:40:00 -04:00
// keep triangle normals
TArray < FVector3d > TriNormals ;
TArray < bool > DevelopableVerts ;
ProfileBeginPass ( ) ;
ProfileBeginSetup ( ) ;
Precompute ( ) ;
RETURN_IF_CANCELLED ;
TriNormals . SetNum ( Mesh - > MaxTriangleID ( ) ) ;
ParallelFor ( Mesh - > MaxTriangleID ( ) , [ & ] ( int32 tid )
{
if ( Mesh - > IsTriangle ( tid ) )
{
TriNormals [ tid ] = Mesh - > GetTriNormal ( tid ) ;
}
} ) ;
RETURN_IF_CANCELLED ;
DevelopableVerts . SetNum ( Mesh - > MaxVertexID ( ) ) ;
double PlanarDotTol = FMathd : : Cos ( CoplanarAngleTolDeg * FMathd : : DegToRad ) ;
ParallelFor ( Mesh - > MaxVertexID ( ) , [ & ] ( int32 vid )
{
if ( Mesh - > IsVertex ( vid ) )
{
DevelopableVerts [ vid ] = IsDevelopableVertex ( * Mesh , vid , PlanarDotTol , [ & ] ( int32 tid ) { return TriNormals [ tid ] ; } ) ;
}
} ) ;
RETURN_IF_CANCELLED ;
ProfileEndSetup ( ) ;
ProfileBeginOps ( ) ;
ProfileBeginCollapse ( ) ;
TArray < int32 > CollapseEdges ;
int32 MaxRounds = 50 ;
int32 num_last_pass = 0 ;
for ( int ri = 0 ; ri < MaxRounds ; + + ri )
{
num_last_pass = 0 ;
// collect up edges we have identified for collapse
CollapseEdges . Reset ( ) ;
for ( int32 eid : Mesh - > EdgeIndicesItr ( ) )
{
2020-12-09 14:18:38 -04:00
if ( EdgeFilterPredicate ( eid ) = = false )
{
continue ;
}
2020-06-23 18:40:00 -04:00
FIndex2i ev = Mesh - > GetEdgeV ( eid ) ;
if ( DevelopableVerts [ ev . A ] | | DevelopableVerts [ ev . B ] )
{
CollapseEdges . Add ( eid ) ;
}
}
FVector3d va = FVector3d : : Zero ( ) , vb = FVector3d : : Zero ( ) ;
for ( int32 eid : CollapseEdges )
{
2023-05-26 13:57:25 -04:00
if ( ! Mesh - > IsEdge ( eid ) )
2020-06-23 18:40:00 -04:00
{
continue ;
}
RETURN_IF_CANCELLED ;
COUNT_ITERATIONS + + ;
FIndex2i ev = Mesh - > GetEdgeV ( eid ) ;
2023-05-26 13:57:25 -04:00
FDynamicMesh3 : : FEdgeCollapseInfo CollapseInfo ;
ESimplificationResult Result = ESimplificationResult : : Failed_OpNotSuccessful ;
// Try collapsing to vert B.
if ( DevelopableVerts [ ev . A ]
& & IsCollapsableDevelopableEdge ( * Mesh , eid , ev . A , ev . B , PlanarDotTol , TriNormals , IsBoundaryVtxCache ) )
2020-06-23 18:40:00 -04:00
{
2023-05-26 13:57:25 -04:00
Result = CollapseEdge ( eid , Mesh - > GetVertex ( ev . B ) , CollapseInfo , ev . B ) ;
}
// If that didn't work, try collapsing to vert A
if ( Result ! = ESimplificationResult : : Ok_Collapsed & & DevelopableVerts [ ev . B ]
& & IsCollapsableDevelopableEdge ( * Mesh , eid , ev . B , ev . A , PlanarDotTol , TriNormals , IsBoundaryVtxCache ) )
{
Result = CollapseEdge ( eid , Mesh - > GetVertex ( ev . A ) , CollapseInfo , ev . A ) ;
}
if ( Result = = ESimplificationResult : : Ok_Collapsed )
{
+ + num_last_pass ;
int vKeptID = CollapseInfo . KeptVertex ;
Mesh - > EnumerateVertexTriangles ( vKeptID , [ & ] ( int32 tid )
2020-06-23 18:40:00 -04:00
{
2023-05-26 13:57:25 -04:00
TriNormals [ tid ] = Mesh - > GetTriNormal ( tid ) ;
} ) ;
for ( int32 vid : Mesh - > VtxVerticesItr ( vKeptID ) )
2020-06-23 18:40:00 -04:00
{
2023-05-26 13:57:25 -04:00
DevelopableVerts [ vid ] = IsDevelopableVertex ( * Mesh , vid , PlanarDotTol , [ & ] ( int32 tid ) { return TriNormals [ tid ] ; } ) ;
2020-06-23 18:40:00 -04:00
}
2023-05-26 13:57:25 -04:00
DevelopableVerts [ vKeptID ] = IsDevelopableVertex ( * Mesh , vKeptID , PlanarDotTol , [ & ] ( int32 tid ) { return TriNormals [ tid ] ; } ) ;
2020-06-23 18:40:00 -04:00
}
}
if ( num_last_pass = = 0 ) // converged
{
break ;
}
}
ProfileEndCollapse ( ) ;
ProfileEndOps ( ) ;
RETURN_IF_CANCELLED ;
Reproject ( ) ;
ProfileEndPass ( ) ;
# undef RETURN_IF_CANCELLED
}
2019-09-13 14:24:58 -04:00
2019-05-24 12:36:32 -04:00
template < typename QuadricErrorType >
2022-02-24 23:46:55 -05:00
void TMeshSimplification < QuadricErrorType > : : FastCollapsePass ( double fMinEdgeLength , int nRounds , bool MeshIsClosedHint , uint32 MinTriangleCount )
2019-05-02 21:41:24 -04:00
{
2022-02-24 23:46:55 -05:00
if ( ( uint32 ) Mesh - > TriangleCount ( ) < = MinTriangleCount ) // badness if we don't catch this...
2019-05-02 21:41:24 -04:00
{
return ;
}
MinEdgeLength = fMinEdgeLength ;
double min_sqr = MinEdgeLength * MinEdgeLength ;
// we don't collapse on the boundary
bHaveBoundary = false ;
ProfileBeginPass ( ) ;
ProfileBeginSetup ( ) ;
Precompute ( MeshIsClosedHint ) ;
if ( Cancelled ( ) )
{
return ;
}
ProfileEndSetup ( ) ;
ProfileBeginOps ( ) ;
ProfileBeginCollapse ( ) ;
int N = Mesh - > MaxEdgeID ( ) ;
int num_last_pass = 0 ;
2019-05-24 12:36:32 -04:00
for ( int ri = 0 ; ri < nRounds ; + + ri )
2019-05-02 21:41:24 -04:00
{
num_last_pass = 0 ;
FVector3d va = FVector3d : : Zero ( ) , vb = FVector3d : : Zero ( ) ;
2019-05-24 12:36:32 -04:00
for ( int eid = 0 ; eid < N ; + + eid )
2019-05-02 21:41:24 -04:00
{
2019-05-24 12:36:32 -04:00
if ( ( ! Mesh - > IsEdge ( eid ) ) | | Mesh - > IsBoundaryEdge ( eid ) )
2019-05-02 21:41:24 -04:00
{
continue ;
}
2022-02-24 23:46:55 -05:00
if ( ( uint32 ) Mesh - > TriangleCount ( ) < = MinTriangleCount )
{
break ;
}
2019-05-02 21:41:24 -04:00
if ( Cancelled ( ) )
{
return ;
}
Mesh - > GetEdgeV ( eid , va , vb ) ;
2021-03-30 21:25:22 -04:00
if ( DistanceSquared ( va , vb ) > min_sqr )
2019-05-02 21:41:24 -04:00
{
continue ;
}
COUNT_ITERATIONS + + ;
FVector3d midpoint = ( va + vb ) * 0.5 ;
2020-08-11 01:36:57 -04:00
FDynamicMesh3 : : FEdgeCollapseInfo collapseInfo ;
ESimplificationResult result = CollapseEdge ( eid , midpoint , collapseInfo ) ;
2019-05-24 12:36:32 -04:00
if ( result = = ESimplificationResult : : Ok_Collapsed )
2019-05-02 21:41:24 -04:00
{
+ + num_last_pass ;
}
}
2022-02-24 23:46:55 -05:00
if ( num_last_pass = = 0 | | ( uint32 ) Mesh - > TriangleCount ( ) < = MinTriangleCount ) // converged
2019-05-02 21:41:24 -04:00
{
break ;
}
}
ProfileEndCollapse ( ) ;
ProfileEndOps ( ) ;
if ( Cancelled ( ) )
{
return ;
}
Reproject ( ) ;
ProfileEndPass ( ) ;
}
2021-02-23 20:17:08 -04:00
template < typename QuadricErrorType >
bool TMeshSimplification < QuadricErrorType > : : CanCollapseEdge ( int edgeID , int a , int b , int c , int d , int t0 , int t1 , int & collapse_to ) const
{
const bool bPreserveSeamTopology = ( CollapseMode = = ESimplificationCollapseModes : : MinimalExistingVertexError ) ;
2019-05-02 21:41:24 -04:00
2021-02-23 20:17:08 -04:00
if ( bAllowSeamCollapse & & ! bPreserveSeamTopology )
{
return CanCollapseVertex ( edgeID , a , b , collapse_to ) ;
}
2019-05-02 21:41:24 -04:00
2021-02-23 20:17:08 -04:00
// make sure that the retained edges from the collapsed triangles don't merge seams
bool bCanCollapse = FMeshRefinerBase : : CanCollapseEdge ( edgeID , a , b , c , d , t0 , t1 , collapse_to ) ;
2019-05-02 21:41:24 -04:00
2021-02-23 20:17:08 -04:00
// make sure more general seam topology is preserved
if ( bCanCollapse & & bAllowSeamCollapse & & bPreserveSeamTopology )
{
if ( ! Constraints )
{
return bCanCollapse ;
}
2019-05-02 21:41:24 -04:00
2021-02-23 20:17:08 -04:00
// NB: We have to be more restrictive with the MinimalQuadricPositionError mode
// in order to preclude the possibility of a seam moving during collapse.
2019-05-02 21:41:24 -04:00
2021-02-23 20:17:08 -04:00
// check if this edge is a seam
if ( Constraints - > HasEdgeConstraint ( edgeID ) )
{
// examine local topology
bool bCanCollapseSeam = true ;
if ( const FDynamicMeshAttributeSet * Attributes = Mesh - > Attributes ( ) )
{
for ( int i = 0 ; bCanCollapseSeam & & i < Attributes - > NumUVLayers ( ) ; + + i )
{
auto * Overlay = Attributes - > GetUVLayer ( i ) ;
bool bIsNonIntersecting ;
if ( Overlay - > IsSeamEdge ( edgeID , & bIsNonIntersecting ) )
{
bCanCollapseSeam = bCanCollapseSeam & & bIsNonIntersecting ;
}
}
for ( int i = 0 ; bCanCollapseSeam & & i < Attributes - > NumNormalLayers ( ) ; + + i )
{
auto * Overlay = Attributes - > GetNormalLayer ( i ) ;
bool bIsNonIntersecting ;
if ( Overlay - > IsSeamEdge ( edgeID , & bIsNonIntersecting ) )
{
bCanCollapseSeam = bCanCollapseSeam & & bIsNonIntersecting ;
}
}
}
bCanCollapse = bCanCollapseSeam ;
}
else
{
// this edge was not a seam, but need to check if one or both ends are part of other seams
// - this is done by checking for vertex constraint
bool bVertexAOnSeam = Constraints - > HasVertexConstraint ( a ) ;
bool bVertexBOnSeam = Constraints - > HasVertexConstraint ( b ) ;
if ( bVertexAOnSeam & & bVertexBOnSeam )
{
bCanCollapse = false ;
}
else if ( bVertexAOnSeam )
{
if ( collapse_to = = - 1 )
{
collapse_to = a ;
}
else if ( collapse_to ! = a )
{
bCanCollapse = false ;
}
}
else if ( bVertexBOnSeam )
{
if ( collapse_to = = - 1 )
{
collapse_to = b ;
}
else if ( collapse_to ! = b )
{
bCanCollapse = false ;
}
}
}
}
return bCanCollapse ;
}
2019-05-02 21:41:24 -04:00
2019-05-24 12:36:32 -04:00
template < typename QuadricErrorType >
2020-08-11 01:36:57 -04:00
ESimplificationResult TMeshSimplification < QuadricErrorType > : : CollapseEdge ( int edgeID , FVector3d vNewPos , FDynamicMesh3 : : FEdgeCollapseInfo & collapseInfo , int32 RequireKeepVert )
2019-05-02 21:41:24 -04:00
{
2020-08-11 01:36:57 -04:00
collapseInfo . KeptVertex = FDynamicMesh3 : : InvalidID ;
2019-05-02 21:41:24 -04:00
RuntimeDebugCheck ( edgeID ) ;
FEdgeConstraint constraint =
2020-01-27 20:11:15 -05:00
( ! Constraints ) ? FEdgeConstraint : : Unconstrained ( ) : Constraints - > GetEdgeConstraint ( edgeID ) ;
2019-05-02 21:41:24 -04:00
if ( constraint . NoModifications ( ) )
{
2019-05-24 12:36:32 -04:00
return ESimplificationResult : : Ignored_EdgeIsFullyConstrained ;
2019-05-02 21:41:24 -04:00
}
if ( constraint . CanCollapse ( ) = = false )
{
2019-05-24 12:36:32 -04:00
return ESimplificationResult : : Ignored_EdgeIsFullyConstrained ;
2019-05-02 21:41:24 -04:00
}
// look up verts and tris for this edge
if ( Mesh - > IsEdge ( edgeID ) = = false )
{
2019-05-24 12:36:32 -04:00
return ESimplificationResult : : Failed_NotAnEdge ;
2019-05-02 21:41:24 -04:00
}
2020-06-23 18:40:00 -04:00
const FDynamicMesh3 : : FEdge Edge = Mesh - > GetEdge ( edgeID ) ;
int a = Edge . Vert [ 0 ] , b = Edge . Vert [ 1 ] , t0 = Edge . Tri [ 0 ] , t1 = Edge . Tri [ 1 ] ;
2019-05-02 21:41:24 -04:00
bool bIsBoundaryEdge = ( t1 = = FDynamicMesh3 : : InvalidID ) ;
// look up 'other' verts c (from t0) and d (from t1, if it exists)
FIndex3i T0tv = Mesh - > GetTriangle ( t0 ) ;
int c = IndexUtil : : FindTriOtherVtx ( a , b , T0tv ) ;
2019-11-04 12:03:13 -05:00
FIndex3i T1tv = ( bIsBoundaryEdge ) ? FDynamicMesh3 : : InvalidTriangle : Mesh - > GetTriangle ( t1 ) ;
2019-05-02 21:41:24 -04:00
int d = ( bIsBoundaryEdge ) ? FDynamicMesh3 : : InvalidID : IndexUtil : : FindTriOtherVtx ( a , b , T1tv ) ;
FVector3d vA = Mesh - > GetVertex ( a ) ;
FVector3d vB = Mesh - > GetVertex ( b ) ;
double edge_len_sqr = ( vA - vB ) . SquaredLength ( ) ;
if ( edge_len_sqr > MinEdgeLength * MinEdgeLength )
{
2019-05-24 12:36:32 -04:00
return ESimplificationResult : : Ignored_EdgeTooLong ;
2019-05-02 21:41:24 -04:00
}
ProfileBeginCollapse ( ) ;
2020-08-11 01:36:57 -04:00
// check if we should collapse, and also find which vertex we should retain
2019-05-02 21:41:24 -04:00
// in cases where we have constraints/etc
int collapse_to = - 1 ;
2021-02-23 20:17:08 -04:00
bool bCanCollapse = CanCollapseEdge ( edgeID , a , b , c , d , t0 , t1 , collapse_to ) ;
2021-02-16 12:58:51 -04:00
2019-05-02 21:41:24 -04:00
if ( bCanCollapse = = false )
{
2019-05-24 12:36:32 -04:00
return ESimplificationResult : : Ignored_Constrained ;
2019-05-02 21:41:24 -04:00
}
2021-02-23 20:17:08 -04:00
const bool bMinimalVertexMode = ( CollapseMode = = ESimplificationCollapseModes : : MinimalExistingVertexError ) ;
2019-05-02 21:41:24 -04:00
// if we have a boundary, we want to collapse to boundary
2019-05-24 12:36:32 -04:00
if ( bPreserveBoundaryShape & & bHaveBoundary )
2019-05-02 21:41:24 -04:00
{
2019-05-24 12:36:32 -04:00
if ( collapse_to ! = - 1 )
2019-05-02 21:41:24 -04:00
{
if ( ( IsBoundaryVertex ( b ) & & collapse_to ! = b ) | |
( IsBoundaryVertex ( a ) & & collapse_to ! = a ) )
{
2019-05-24 12:36:32 -04:00
return ESimplificationResult : : Ignored_Constrained ;
2019-05-02 21:41:24 -04:00
}
}
2021-02-23 20:17:08 -04:00
if ( ! bMinimalVertexMode ) // the minimal existing vertex error has already resolved this with more complicated logic
2019-05-02 21:41:24 -04:00
{
2021-02-23 20:17:08 -04:00
if ( IsBoundaryVertex ( b ) )
{
collapse_to = b ;
}
else if ( IsBoundaryVertex ( a ) )
{
collapse_to = a ;
}
2019-05-02 21:41:24 -04:00
}
}
2020-06-23 18:40:00 -04:00
if ( RequireKeepVert = = a | | RequireKeepVert = = b )
{
2021-02-23 20:17:08 -04:00
if ( collapse_to ! = - 1 & & collapse_to ! = RequireKeepVert )
2020-06-23 18:40:00 -04:00
{
return ESimplificationResult : : Ignored_Constrained ;
}
collapse_to = RequireKeepVert ;
}
2019-05-02 21:41:24 -04:00
// 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-05-24 12:36:32 -04:00
ESimplificationResult retVal = ESimplificationResult : : Failed_OpNotSuccessful ;
2019-05-02 21:41:24 -04:00
int iKeep = b , iCollapse = a ;
2021-02-23 20:17:08 -04:00
bool bConstraintsSpecifyPosition = false ;
2020-08-11 01:36:57 -04:00
if ( collapse_to ! = - 1 )
{
iKeep = collapse_to ;
iCollapse = ( iKeep = = a ) ? b : a ;
2019-05-02 21:41:24 -04:00
2021-02-23 20:17:08 -04:00
// if constraints or collapse mode require a fixed position
2020-08-11 01:36:57 -04:00
if ( Constraints )
{
2021-02-23 20:17:08 -04:00
bConstraintsSpecifyPosition = bMinimalVertexMode | | ( ! Constraints - > GetVertexConstraint ( collapse_to ) . bCanMove ) ;
2020-08-11 01:36:57 -04:00
}
}
2019-05-02 21:41:24 -04:00
double collapse_t = 0 ;
2021-02-23 20:17:08 -04:00
if ( ! bConstraintsSpecifyPosition )
2019-05-02 21:41:24 -04:00
{
2021-02-23 20:17:08 -04:00
checkSlow ( ! bMinimalVertexMode | | ( vNewPos = = vA | | vNewPos = = vB ) ) ;
// [TODO] maybe skip Projection call when !bModeAllowsVertMovment.
2019-05-02 21:41:24 -04:00
vNewPos = GetProjectedCollapsePosition ( iKeep , vNewPos ) ;
2021-05-11 13:17:19 -04:00
double EdgeLength = Distance ( vA , vB ) ;
collapse_t = ( EdgeLength < FMathd : : ZeroTolerance ) ? 0.5 : ( Distance ( vNewPos , Mesh - > GetVertex ( iKeep ) ) ) / EdgeLength ;
2019-05-02 21:41:24 -04:00
collapse_t = VectorUtil : : Clamp ( collapse_t , 0.0 , 1.0 ) ;
2021-02-23 20:17:08 -04:00
if ( bMinimalVertexMode )
{
// this _should_ already be 0 or 1 with perfect precision.
// round here to make sure later attribute lerps don't change values
collapse_t = FMath : : RoundToDouble ( collapse_t ) ;
}
2021-05-11 13:17:19 -04:00
// If vertex is not explicitly constrained, and geometric error constraint is requested, we check it here.
// If the check with the predicted vNewPos fails, try a second time with the linear-interpolated point.
// (could optionally do a line search here, and/or be smarter about avoiding duplicate work, although
// it is small in the context of the larger algorithm)
if ( GeometricErrorConstraint ! = EGeometricErrorCriteria : : None )
{
if ( CheckIfCollapseWithinGeometricTolerance ( iKeep , iCollapse , vNewPos , t0 , t1 ) = = false )
{
// project new position back onto the edge
vNewPos = ( 1.0 - collapse_t ) * Mesh - > GetVertex ( iKeep ) + ( collapse_t ) * Mesh - > GetVertex ( iCollapse ) ;
if ( CheckIfCollapseWithinGeometricTolerance ( iKeep , iCollapse , vNewPos , t0 , t1 ) = = false )
{
ProfileEndCollapse ( ) ;
return ESimplificationResult : : Failed_GeometricDeviation ;
}
}
}
2019-05-02 21:41:24 -04:00
}
2020-08-11 01:36:57 -04:00
else
{
vNewPos = ( collapse_to = = a ) ? vA : vB ;
2023-03-28 12:24:01 -04:00
// If geometric error constraint is requested, ensure that the fixed vNewPos satisfies the constraint.
// This must be done, otherwise the free vertex being collapsed to a fixed vertex will be allowed
// to violate the geometric constraint
if ( GeometricErrorConstraint ! = EGeometricErrorCriteria : : None )
{
if ( CheckIfCollapseWithinGeometricTolerance ( iKeep , iCollapse , vNewPos , t0 , t1 ) = = false )
{
ProfileEndCollapse ( ) ;
return ESimplificationResult : : Failed_GeometricDeviation ;
}
}
2020-08-11 01:36:57 -04:00
}
2019-05-02 21:41:24 -04:00
// check if this collapse will create a normal flip. Also checks
// for invalid collapse nbrhood, since we are doing one-ring iter anyway.
// [TODO] could we skip this one-ring check in CollapseEdge? pass in hints?
2019-05-24 12:36:32 -04:00
if ( CheckIfCollapseCreatesFlipOrInvalid ( a , b , vNewPos , t0 , t1 ) | | CheckIfCollapseCreatesFlipOrInvalid ( b , a , vNewPos , t0 , t1 ) )
2019-05-02 21:41:24 -04:00
{
ProfileEndCollapse ( ) ;
2019-05-24 12:36:32 -04:00
return ESimplificationResult : : Ignored_CreatesFlip ;
2019-05-02 21:41:24 -04:00
}
2023-02-13 21:22:23 -05:00
if ( bPreventTinyTriangles & & ( CheckIfCollapseCreatesTinyTriangle ( a , b , vNewPos , t0 , t1 ) | | CheckIfCollapseCreatesTinyTriangle ( b , a , vNewPos , t0 , t1 ) ) )
{
ProfileEndCollapse ( ) ;
return ESimplificationResult : : Ignored_CreatesTinyTriangle ;
}
2019-05-02 21:41:24 -04:00
// lots of cases where we cannot collapse, but we should just let
// Mesh sort that out, right?
COUNT_COLLAPSES + + ;
2020-08-11 01:36:57 -04:00
2019-05-02 21:41:24 -04:00
EMeshResult result = Mesh - > CollapseEdge ( iKeep , iCollapse , collapse_t , collapseInfo ) ;
2019-05-24 12:36:32 -04:00
if ( result = = EMeshResult : : Ok )
2019-05-02 21:41:24 -04:00
{
Mesh - > SetVertex ( iKeep , vNewPos ) ;
2020-01-27 20:11:15 -05:00
if ( Constraints )
2019-05-02 21:41:24 -04:00
{
Constraints - > ClearEdgeConstraint ( edgeID ) ;
2021-02-16 12:58:51 -04:00
auto ConstraintUpdator = [ this ] ( int cur_eid ) - > void
{
// Seam edge can never flip, it is never fully unconstrained
EEdgeRefineFlags SeamEdgeConstraint = EEdgeRefineFlags : : NoFlip ;
if ( ! bAllowSeamCollapse )
{
SeamEdgeConstraint = EEdgeRefineFlags ( ( int ) SeamEdgeConstraint | ( int ) EEdgeRefineFlags : : NoCollapse ) ;
}
FEdgeConstraint UpdatedEdgeConstraint ;
FVertexConstraint UpdatedVertexConstraintA ;
FVertexConstraint UpdatedVertexConstraintB ;
bool bHaveUpdate =
FMeshConstraintsUtil : : ConstrainEdgeBoundariesAndSeams ( cur_eid ,
* Mesh ,
MeshBoundaryConstraint ,
GroupBoundaryConstraint ,
MaterialBoundaryConstraint ,
SeamEdgeConstraint ,
! bAllowSeamCollapse ,
UpdatedEdgeConstraint ,
UpdatedVertexConstraintA ,
UpdatedVertexConstraintB ) ;
if ( bHaveUpdate )
{
FIndex2i EdgeVerts = Mesh - > GetEdgeV ( cur_eid ) ;
Constraints - > SetOrUpdateEdgeConstraint ( cur_eid , UpdatedEdgeConstraint ) ;
UpdatedVertexConstraintA . CombineConstraint ( Constraints - > GetVertexConstraint ( EdgeVerts . A ) ) ;
Constraints - > SetOrUpdateVertexConstraint ( EdgeVerts . A , UpdatedVertexConstraintA ) ;
UpdatedVertexConstraintB . CombineConstraint ( Constraints - > GetVertexConstraint ( EdgeVerts . B ) ) ;
Constraints - > SetOrUpdateVertexConstraint ( EdgeVerts . B , UpdatedVertexConstraintB ) ;
}
} ;
if ( Constraints - > HasEdgeConstraint ( collapseInfo . RemovedEdges . A ) )
{
Constraints - > ClearEdgeConstraint ( collapseInfo . KeptEdges . A ) ;
Constraints - > ClearEdgeConstraint ( collapseInfo . RemovedEdges . A ) ;
ConstraintUpdator ( collapseInfo . KeptEdges . A ) ;
}
2019-05-02 21:41:24 -04:00
if ( collapseInfo . RemovedEdges . B ! = FDynamicMesh3 : : InvalidID )
{
2021-02-16 12:58:51 -04:00
if ( Constraints - > HasEdgeConstraint ( collapseInfo . RemovedEdges . B ) )
{
Constraints - > ClearEdgeConstraint ( collapseInfo . KeptEdges . B ) ;
Constraints - > ClearEdgeConstraint ( collapseInfo . RemovedEdges . B ) ;
ConstraintUpdator ( collapseInfo . KeptEdges . B ) ;
}
2019-05-02 21:41:24 -04:00
}
Constraints - > ClearVertexConstraint ( iCollapse ) ;
}
OnEdgeCollapse ( edgeID , iKeep , iCollapse , collapseInfo ) ;
DoDebugChecks ( ) ;
2019-05-24 12:36:32 -04:00
retVal = ESimplificationResult : : Ok_Collapsed ;
2019-05-02 21:41:24 -04:00
}
2021-02-16 12:58:51 -04:00
else if ( result = = EMeshResult : : Failed_CollapseTriangle )
{
retVal = ESimplificationResult : : Failed_IsolatedTriangle ;
}
2019-05-02 21:41:24 -04:00
ProfileEndCollapse ( ) ;
return retVal ;
}
2021-05-11 13:17:19 -04:00
template < typename QuadricErrorType >
bool TMeshSimplification < QuadricErrorType > : : CheckIfCollapseWithinGeometricTolerance ( int vKeep , int vRemove , const FVector3d & NewPosition , int tc , int td )
{
if ( GeometricErrorConstraint = = EGeometricErrorCriteria : : PredictedPointToProjectionTarget )
{
// currently assuming projection target is what we want to measure geometric error against
if ( ProjectionTarget ( ) ! = nullptr )
{
double ToleranceSqr = GeometricErrorTolerance * GeometricErrorTolerance ;
// test new position to see if it is within geometric tolerance of projection surface
FVector3d TargetPos = ProjectionTarget ( ) - > Project ( NewPosition ) ;
double DistSqr = DistanceSquared ( TargetPos , NewPosition ) ;
if ( DistSqr > ToleranceSqr )
{
return false ;
}
// test edge midpoints, except the edge being collapsed
int32 CollapseEdgeID = Mesh - > FindEdge ( vKeep , vRemove ) ;
auto EdgeMidpointsWithinTolerance = [ this , CollapseEdgeID , NewPosition , ToleranceSqr ] ( int32 vid ) {
for ( int32 eid : Mesh - > VtxEdgesItr ( vid ) )
{
if ( eid ! = CollapseEdgeID )
{
FIndex2i EdgeV = Mesh - > GetEdgeV ( eid ) ;
FVector3d OtherVertexPos = ( EdgeV . A = = vid ) ? Mesh - > GetVertex ( EdgeV . B ) : Mesh - > GetVertex ( EdgeV . A ) ;
FVector3d NewMidpoint = ( OtherVertexPos + NewPosition ) * 0.5 ;
FVector3d MidpointTargetPos = ProjectionTarget ( ) - > Project ( NewMidpoint ) ;
if ( DistanceSquared ( NewMidpoint , MidpointTargetPos ) > ToleranceSqr )
{
return false ;
}
}
}
return true ;
} ;
if ( EdgeMidpointsWithinTolerance ( vKeep ) = = false | | EdgeMidpointsWithinTolerance ( vRemove ) = = false )
{
return false ;
}
// check tri centers, except the triangles being collapsed
auto CentroidsWithinToleranceFunc = [ this , vKeep , vRemove , tc , td , NewPosition , ToleranceSqr ] ( int32 vid ) {
bool bInTolerance = true ;
Mesh - > EnumerateVertexTriangles ( vid , [ & ] ( int32 tid )
{
if ( bInTolerance & & tid ! = tc & & tid ! = td )
{
FIndex3i Tri = Mesh - > GetTriangle ( tid ) ;
FVector3d NewCentroid = FVector3d : : Zero ( ) ;
for ( int32 j = 0 ; j < 3 ; + + j )
{
NewCentroid + = ( Tri [ j ] = = vRemove | | Tri [ j ] = = vKeep ) ? NewPosition : Mesh - > GetVertex ( Tri [ j ] ) ;
}
NewCentroid * = ( 1.0 / 3.0 ) ;
FVector3d CentroidTargetPos = ProjectionTarget ( ) - > Project ( NewCentroid ) ;
if ( DistanceSquared ( NewCentroid , CentroidTargetPos ) > ToleranceSqr )
{
bInTolerance = false ;
}
}
} ) ;
return bInTolerance ;
} ;
if ( CentroidsWithinToleranceFunc ( vKeep ) = = false | | CentroidsWithinToleranceFunc ( vRemove ) = = false )
{
return false ;
}
}
}
return true ;
}
2019-05-02 21:41:24 -04:00
2021-02-16 12:58:51 -04:00
template < typename QuadricErrorType >
bool TMeshSimplification < QuadricErrorType > : : RemoveIsolatedTriangle ( int tID )
{
if ( ! Mesh - > IsTriangle ( tID ) ) return true ;
FIndex3i tv = Mesh - > GetTriangle ( tID ) ;
bool bIsIsolated = true ;
for ( int i = 0 ; i < 3 ; + + i )
{
for ( int nbtr : Mesh - > VtxTrianglesItr ( tv [ i ] ) )
{
bIsIsolated = bIsIsolated & & ( nbtr = = tID ) ;
}
}
2019-05-02 21:41:24 -04:00
2021-02-16 12:58:51 -04:00
if ( bIsIsolated )
{
const FIndex3i TriEdges = Mesh - > GetTriEdges ( tID ) ;
if ( Mesh - > RemoveTriangle ( tID ) = = EMeshResult : : Ok )
{
if ( Constraints )
{
Constraints - > ClearEdgeConstraint ( TriEdges . A ) ;
Constraints - > ClearEdgeConstraint ( TriEdges . B ) ;
Constraints - > ClearEdgeConstraint ( TriEdges . C ) ;
2019-05-02 21:41:24 -04:00
2021-02-16 12:58:51 -04:00
Constraints - > ClearVertexConstraint ( tv . A ) ;
Constraints - > ClearVertexConstraint ( tv . B ) ;
Constraints - > ClearVertexConstraint ( tv . C ) ;
}
}
OnRemoveIsolatedTriangle ( tID ) ;
}
return bIsIsolated ;
}
2019-05-02 21:41:24 -04:00
// Project vertices onto projection target.
// We can do projection in parallel if we have .net
2019-05-24 12:36:32 -04:00
template < typename QuadricErrorType >
void TMeshSimplification < QuadricErrorType > : : FullProjectionPass ( )
2019-05-02 21:41:24 -04:00
{
2019-05-24 12:36:32 -04:00
auto project = [ & ] ( int vID )
2019-05-02 21:41:24 -04:00
{
2020-08-11 01:36:57 -04:00
if ( IsVertexPositionConstrained ( vID ) )
2019-05-02 21:41:24 -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 ) ;
} ;
ApplyToProjectVertices ( project ) ;
2020-04-18 18:42:59 -04:00
// TODO: optionally do projection in parallel?
2019-05-02 21:41:24 -04:00
}
2019-05-24 12:36:32 -04:00
template < typename QuadricErrorType >
void TMeshSimplification < QuadricErrorType > : : ApplyToProjectVertices ( const TFunction < void ( int ) > & apply_f )
2019-05-02 21:41:24 -04:00
{
for ( int vid : Mesh - > VertexIndicesItr ( ) )
{
apply_f ( vid ) ;
}
}
2019-05-24 12:36:32 -04:00
template < typename QuadricErrorType >
void TMeshSimplification < QuadricErrorType > : : ProjectVertex ( int vID , IProjectionTarget * targetIn )
2019-05-02 21:41:24 -04:00
{
FVector3d curpos = Mesh - > GetVertex ( vID ) ;
FVector3d projected = targetIn - > Project ( curpos , vID ) ;
Mesh - > SetVertex ( vID , projected ) ;
}
// used by collapse-edge to get projected position for new vertex
2019-05-24 12:36:32 -04:00
template < typename QuadricErrorType >
FVector3d TMeshSimplification < QuadricErrorType > : : GetProjectedCollapsePosition ( int vid , const FVector3d & vNewPos )
2019-05-02 21:41:24 -04:00
{
2020-01-27 20:11:15 -05:00
if ( Constraints )
2019-05-02 21:41:24 -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-05-02 21:41:24 -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 ;
}
2019-05-24 12:36:32 -04:00
// Custom behavior for FAttrBasedQuadric simplifier.
template < >
2021-08-03 11:56:47 -04:00
void DYNAMICMESH_API TMeshSimplification < FAttrBasedQuadricErrord > : : OnEdgeCollapse ( int edgeID , int va , int vb , const FDynamicMesh3 : : FEdgeCollapseInfo & collapseInfo )
2019-05-24 12:36:32 -04:00
{
// Update the normal
FAttrBasedQuadricErrord & Quadric = EdgeQuadrics [ edgeID ] . q ;
FVector3d collapse_pt = EdgeQuadrics [ edgeID ] . collapse_pt ;
2021-09-23 19:38:55 -04:00
FVector3d UpdatedNormald ;
2019-05-24 12:36:32 -04:00
Quadric . ComputeAttributes ( collapse_pt , UpdatedNormald ) ;
2021-03-30 21:25:22 -04:00
FVector3f UpdatedNormal ( ( float ) UpdatedNormald . X , ( float ) UpdatedNormald . Y , ( float ) UpdatedNormald . Z ) ;
2021-03-17 19:32:44 -04:00
Normalize ( UpdatedNormal ) ;
2019-05-24 12:36:32 -04:00
if ( NormalOverlay ! = nullptr )
{
// Get all the elements associated with this vertex (could be more than one to account for split vertex data)
TArray < int > ElementIdArray ;
NormalOverlay - > GetVertexElements ( va , ElementIdArray ) ;
2020-08-11 01:36:57 -04:00
if ( ElementIdArray . Num ( ) > 1 )
{
// keep whatever split normals are currently in the overlay.
// @todo: normalize the split normals - since the values here result from a lerp
return ;
}
// at most one element
2019-05-24 12:36:32 -04:00
for ( int ElementId : ElementIdArray )
{
NormalOverlay - > SetElement ( ElementId , UpdatedNormal ) ;
}
}
else
{
Mesh - > SetVertexNormal ( va , UpdatedNormal ) ;
}
}
2021-03-09 19:33:56 -04:00
namespace UE
{
namespace Geometry
{
2019-05-24 12:36:32 -04:00
// These are explicit instantiations of the templates that are exported from the shared lib.
// Only these instantiations of the template can be used.
// This is necessary because we have placed most of the templated functions in this .cpp file, instead of the header.
template class DYNAMICMESH_API TMeshSimplification < FAttrBasedQuadricErrord > ;
template class DYNAMICMESH_API TMeshSimplification < FVolPresQuadricErrord > ;
template class DYNAMICMESH_API TMeshSimplification < FQuadricErrord > ;
2021-03-09 19:33:56 -04:00
} // end namespace UE::Geometry
} // end namespace UE