2021-09-08 11:15:58 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
2021-09-30 10:52:07 -04:00
# include "Operations/IntrinsicTriangulationMesh.h"
# include "Operations/MeshGeodesicSurfaceTracer.h"
2021-09-08 11:15:58 -04:00
# include "DynamicMesh/DynamicMeshAttributeSet.h"
# include "Util/IndexUtil.h"
# include "Algo/Reverse.h"
# include "Containers/BitArray.h"
2021-09-08 14:04:42 -04:00
# include "Containers/Queue.h"
2021-09-08 11:15:58 -04:00
# include "MathUtil.h"
# include "Async/ParallelFor.h"
2022-05-19 10:43:03 -04:00
# include "VectorTypes.h"
# include "MatrixTypes.h"
2021-09-08 11:15:58 -04:00
using namespace UE : : Geometry ;
/**
* Helper functions that use triangle edge lengths instead of vertex positions to do some euclidean triangle - based calculations .
* Many of these are based on the " law of cosines "
*/
namespace
{
/**
* Given a flat triangle with edges L0 , L1 , L2 in CCW order , compute the angle between
* the L0 and L2 edge in radians [ 0 , Pi ] range .
*
* used by Sharp , Soliman and Crane [ 2019 , ACM Transactions on Graphics ] section 3.2
*
* but can be understood as trivial application of " the law of cosines "
*/
double InteriorAngle ( const double L0 , const double L1 , const double L2 )
{
// V1 = V2 - V0. L1^2 = ||V1||^2 = ||V2||^2 + ||V0||^2 - 2 Dot(V2, V0) = L2^2 + L0^2 - 2 L2 L0 CosTheta
// CosTheta = (L0^2 + L1^2 - L2^2) / (2 L0 L1)
double CosTheta = ( L0 * L0 + L2 * L2 - L1 * L1 ) / ( 2. * L0 * L2 ) ;
// account for roundoff, theoretically this would already be in the [-1,1] range
CosTheta = FMath : : Clamp ( CosTheta , - 1. , 1. ) ;
return FMath : : Acos ( CosTheta ) ;
}
/**
* For a triangle , use the law of cos to compute the third edge length
* - Given interior angle in radians AngleR , and adjacent edge lengths L0 and L1 , this computes the opposing edge length .
*/
double LawOfCosLength ( const double AngleR , const double L0 , const double L1 )
{
double L2sqr = L0 * L0 + L1 * L1 - 2. * L0 * L1 * FMath : : Cos ( AngleR ) ;
// theoretically this is always positive, but protect round-off. Alternately, could compute (L0-L1)^2 + 4 L0 L1 Sin^2 ( AngleR/2)
L2sqr = FMath : : Max ( L2sqr , 0. ) ;
return FMath : : Sqrt ( L2sqr ) ;
}
/**
* Compute the unsigned distance between two points , described as barycentric coordinates
* relative to a triangle with side lengths L0 , L1 , L2 is CCW order .
* @ params L0 , L1 , L2 - triangle side lengths in CCW order
* @ param BCPoint0 - Barycentric coordinates for the first point
* @ param BCPoint1 - Barycentric coordinates for the second point
*
* NB : it is assumed that the points are within the triangle , and the triangle is valid ( i . e . lengths satisfy triangle inequality )
*/
double DistanceBetweenBarycentricPoints ( const double L0 , const double L1 , const double L2 , const FVector3d & BCPoint0 , const FVector3d & BCPoint1 )
{
// see Sharp, Soliman and Crane [2019, ACM Transactions on Graphics] or Schindler and Chen [2012, Section 3.2]
using Vector3Type = FVector3d ;
using ScalarType = double ;
const ScalarType Zero ( 0 ) ;
const Vector3Type u = BCPoint1 - BCPoint0 ;
const ScalarType dsqr = - ( L0 * L0 * u [ 0 ] * u [ 1 ] + L1 * L1 * u [ 1 ] * u [ 2 ] + L2 * L2 * u [ 2 ] * u [ 0 ] ) ;
const ScalarType d = ( dsqr < Zero ) ? Zero : sqrt ( dsqr ) ;
return d ;
}
/**
* Compute a vector ( in polar form ) from triangle corner to a point within the triangle .
* @ params L0 , L1 , L2 - triangle side lengths in CCW order
* @ param BarycetricPoint - interior point in triangle
*
* @ return , a polar vector from the vertex shared by L0 , L2 to the BarycentricPoint in the form
* Vector [ 0 ] = radial distance
* Vector [ 1 ] = angle measured from the L0 side of the triangle
*/
FVector2d VectorToPoint ( const double L0 , const double L1 , const double L2 , const FVector3d & BarycentricPoint )
{
FVector3d BCpi ( 1. , 0. , 0. ) ;
FVector3d BCpj ( 0. , 1. , 0. ) ;
double rpi = DistanceBetweenBarycentricPoints ( L0 , L1 , L2 , BCpi , BarycentricPoint ) ;
double rpj = DistanceBetweenBarycentricPoints ( L0 , L1 , L2 , BCpj , BarycentricPoint ) ;
double angle = InteriorAngle ( L0 , rpj , rpi ) ;
return FVector2d ( rpi , angle ) ;
}
/**
* Heron ' s formula for computing the area of a triangle given the lengths of all three sides .
*/
double TriangleArea ( const double L0 , const double L1 , const double L2 )
{
// note: since this is a triangle, the lengths should satisfy triangle inequality, meaning this should be positive
double AreaSqrTimes16 = FMath : : Max ( 0. , ( L0 + L1 + L2 ) * ( - L0 + L1 + L2 ) * ( L0 - L1 + L2 ) * ( L0 + L1 - L2 ) ) ;
return FMath : : Sqrt ( AreaSqrTimes16 ) * 0.25 ;
}
double TriangleArea ( const FVector3d & Ls )
{
return TriangleArea ( Ls [ 0 ] , Ls [ 1 ] , Ls [ 2 ] ) ;
}
/**
* Compute the cotangent of the interior angle opposite the edge L0 , give a triangle with edges { L0 , L1 , L2 } in CCW order
*/
double ComputeCotangent ( const double L0 , const double L1 , const double L2 )
{
const double Area = TriangleArea ( L0 , L1 , L2 ) ;
const double CT = 0.25 * ( - L0 * L0 + L1 * L1 + L2 * L2 ) / Area ;
return CT ;
}
/**
* Return the location of third vertex in a triangle in R2 with prescribed lengths
2024-02-07 10:10:37 -05:00
* such that the resulting triangle could be constructed as { ( 0 , 0 ) , ( L0 , 0 ) , Result } .
2021-09-08 11:15:58 -04:00
*
* NB : this assumes , but does not check , that the lengths satisfy the triangle inequality
*/
FVector2d ComputeOpposingVert2d ( const double L0 , const double L1 , const double L2 )
{
const double Area = TriangleArea ( L0 , L1 , L2 ) ;
const double Y = 2. * Area / L0 ;
2022-05-19 10:43:03 -04:00
const double X = FMath : : Sqrt ( FMath : : Max ( 0. , ( L2 - Y ) * ( L2 + Y ) ) ) ;
2021-09-08 11:15:58 -04:00
FVector2d ResultPos ( X , Y ) ;
// angle between L0 and L2 edges can be computed form 2 * L0 * L2 * Cos(theta) = L0^2 + L2^2 - L1^2
if ( L0 * L0 + L2 * L2 < L1 * L1 )
{
ResultPos . X = - X ; // angle > 90-degrees.
}
return ResultPos ;
}
/**
* Given three side lengths that satisfy the triangle inequality ,
* this updates the 2 d positions to be the vertices of a triangle ,
* the edge lengths of which ( when in CCW order ) match the prescribed lengths .
2024-02-07 10:10:37 -05:00
* with vertices { ( 0 , 0 ) , ( L0 , 0 ) , ( X , Y ) } where Y > 0
2021-09-08 11:15:58 -04:00
*/
void TriangleFromLengths ( const double L0 , const double L1 , const double L2 , FVector2d & p0 , FVector2d & p1 , FVector2d & p3 )
{
p0 = FVector2d ( 0. , 0. ) ;
2024-02-07 10:10:37 -05:00
p1 = FVector2d ( L0 , 0. ) ;
2021-09-08 11:15:58 -04:00
p3 = ComputeOpposingVert2d ( L0 , L1 , L2 ) ;
}
} ;
/**
* Utilities and generic implementation for flipping a mesh to Delaunay
*/
namespace
{
/**
* LIFO for unique DynamicMesh IDs , can be constructed from any FRefCountVector : : IndexEnumerable
* and supports a filtering function on construction . If the filtering function is absent , all
* unique elements will be added , otherwise only those selected by the filter .
*
* for example :
*
* auto AddToQueueFilter = [ ] ( int ID ) - > bool { . . . } ;
*
* FIndexLIFO EdgeQueueLIFO ( Mesh . EdgeIndicesItr ( ) , AddToQueueFilter ) ;
*/
class FIndexLIFO
{
public :
// constructor that adds all unique IDs from IndexEnumerable
FIndexLIFO ( FRefCountVector : : IndexEnumerable IDEnumerable )
: IsEnqueued ( false , ( int32 ) IDEnumerable . Vector - > GetMaxIndex ( ) )
{
const int32 NumIDs = ( int32 ) IDEnumerable . Vector - > GetCount ( ) ;
IDs . Reserve ( NumIDs ) ;
for ( int32 ID : IDEnumerable )
{
Enqueue ( ID ) ;
}
}
// filtering constructor that only adds IDs for which Filter(ID) is true
FIndexLIFO ( FRefCountVector : : IndexEnumerable IDEnumerable , const TFunctionRef < bool ( int32 ) > & Filter )
: IsEnqueued ( false , ( int32 ) IDEnumerable . Vector - > GetMaxIndex ( ) )
{
const int32 NumIDs = ( int32 ) IDEnumerable . Vector - > GetCount ( ) ;
IDs . Reserve ( NumIDs ) ;
for ( int32 ID : IDEnumerable )
{
if ( Filter ( ID ) )
{
Enqueue ( ID ) ;
}
}
}
bool Dequeue ( int32 & IDOut )
{
if ( IDs . Num ( ) )
{
2024-01-19 16:41:35 -05:00
IDOut = IDs . Pop ( EAllowShrinking : : No ) ;
2021-09-08 11:15:58 -04:00
IsEnqueued [ IDOut ] = false ;
return true ;
}
IDOut = - 1 ;
return false ;
}
void Enqueue ( int32 ID )
{
if ( ! IsEnqueued [ ID ] )
{
IDs . Add ( ID ) ;
IsEnqueued [ ID ] = true ;
}
}
private :
FIndexLIFO ( ) ;
TBitArray < FDefaultBitArrayAllocator > IsEnqueued ;
TArray < int32 > IDs ;
} ;
/**
* FIFO for unique Dynamic Mesh IDs - can be constructed from any FRefCountVector : : IndexEnumerable
* and supports a filtering function on construction . If the filtering function is absent , all
* unique elements will be added , otherwise only those selected by the filter .
*
* for example :
*
* auto AddToQueueFilter = [ ] ( int ID ) - > bool { . . . } ;
*
* FIndexFIFO EdgeQueueFIFO ( Mesh . EdgeIndicesItr ( ) , AddToQueueFilter ) ;
*/
class FIndexFIFO
{
public :
// constructor that adds all unique IDs from IndexEnumerable
FIndexFIFO ( FRefCountVector : : IndexEnumerable IDEnumerable )
: IsEnqueued ( false , ( int32 ) IDEnumerable . Vector - > GetMaxIndex ( ) )
{
const int32 NumEdges = ( int32 ) IDEnumerable . Vector - > GetMaxIndex ( ) ;
for ( int32 ID : IDEnumerable )
{
Enqueue ( ID ) ;
}
}
// filtering constructor that only adds IDs for which Filter(ID) is true Note: Filter is called in parallel.
FIndexFIFO ( FRefCountVector : : IndexEnumerable IDEnumerable , const TFunctionRef < bool ( int32 ) > & Filter )
: IsEnqueued ( false , ( int32 ) IDEnumerable . Vector - > GetMaxIndex ( ) )
{
// parallel evaluation that assumes Filter is expensive
const int32 MaxID = ( int32 ) IDEnumerable . Vector - > GetMaxIndex ( ) ;
TArray < int32 > ToInclude ;
ToInclude . AddZeroed ( MaxID ) ;
ParallelFor ( MaxID , [ & ToInclude , & Filter ] ( int32 ID )
{
if ( Filter ( ID ) )
{
ToInclude [ ID ] = 1 ;
}
} ) ;
for ( int32 ID = 0 ; ID < MaxID ; + + ID )
{
if ( ToInclude [ ID ] = = 1 )
{
Enqueue ( ID ) ;
}
}
}
bool Dequeue ( int32 & IDOut )
{
if ( IDs . Dequeue ( IDOut ) )
{
IsEnqueued [ IDOut ] = false ;
return true ;
}
IDOut = - 1 ;
return false ;
}
void Enqueue ( int32 ID )
{
if ( ! IsEnqueued [ ID ] )
{
IDs . Enqueue ( ID ) ;
IsEnqueued [ ID ] = true ;
}
}
private :
FIndexFIFO ( ) ;
TBitArray < FDefaultBitArrayAllocator > IsEnqueued ;
TQueue < int32 > IDs ;
} ;
/**
* Carry out edge flips on intrinsic mesh until either the MaxFlipCount is reached or the mesh is fully Delaunay and returns
* the number of flips .
* Note : There can be cases where an edge can not flip ( e . g . if the resulting edge already exists )
* - for this reason , there may be some " uncorrected " edges in the resulting mesh .
*
* @ param IntrinsicMesh - The mesh to be operated on .
* @ param Uncorrected - contains on return , all the edges that could not be flipped .
* @ param Threshold - edges with cotan weight less than threshold should be flipped .
*/
template < typename MeshType >
int32 FlipToDelaunayImpl ( MeshType & Mesh , TSet < int32 > & Uncorrected , const int32 MaxFlipCount , double Threshold = - TMathUtilConstants < double > : : ZeroTolerance )
{
Uncorrected . Empty ( ) ;
// returns true if an edge should be flipped.
auto EdgeShouldFlipFilter = [ & Mesh , Threshold ] ( int32 EID ) - > bool
{
if ( ! Mesh . IsEdge ( EID ) | | Mesh . IsBoundaryEdge ( EID ) )
{
return false ;
}
const double CotanWeightValue = Mesh . EdgeCotanWeight ( EID ) ;
// Delaunay test
return CotanWeightValue < Threshold ;
} ;
// Enqueue the "bad" edges.
FIndexFIFO EdgeQueue ( Mesh . EdgeIndicesItr ( ) , EdgeShouldFlipFilter ) ;
// flip away the bad edges
int32 FlipCount = 0 ;
int32 EID ;
while ( EdgeQueue . Dequeue ( EID ) & & FlipCount < MaxFlipCount )
{
if ( EdgeShouldFlipFilter ( EID ) )
{
FIntrinsicTriangulation : : FEdgeFlipInfo EdgeFlipInfo ;
const EMeshResult Result = Mesh . FlipEdge ( EID , EdgeFlipInfo ) ;
if ( Result = = EMeshResult : : Ok )
{
FlipCount + + ;
for ( int32 t = 0 ; t < 2 ; + + t )
{
const FIndex3i TriEIDs = Mesh . GetTriEdges ( EdgeFlipInfo . Triangles [ t ] ) ;
int32 IndexOf = TriEIDs . IndexOf ( EID ) ;
EdgeQueue . Enqueue ( TriEIDs [ ( IndexOf + 1 ) % 3 ] ) ;
EdgeQueue . Enqueue ( TriEIDs [ ( IndexOf + 2 ) % 3 ] ) ;
}
}
else
{
Uncorrected . Add ( EID ) ;
}
}
}
return FlipCount ;
}
} ;
2021-11-10 14:22:10 -05:00
namespace SignpostSufaceTraceUtil
{
using namespace IntrinsicCorrespondenceUtils ;
/**
* helper code that uses FSignpost to trace from a specified intrinsic mesh vertex in a given direction .
*
* Note : this doesn ' t check the validity of the start point - the calling code should do that
*/
UE : : Geometry : : FMeshGeodesicSurfaceTracer TraceFromIntrinsicVert ( const FSignpost & SignpostData , const int32 TraceStartVID ,
const double TracePolarAngle , const double TraceDist )
{
const FDynamicMesh3 * SurfaceMesh = SignpostData . SurfaceMesh ;
const FSurfacePoint & StartSurfacePoint = SignpostData . IntrinsicVertexPositions [ TraceStartVID ] ;
const FSurfacePoint : : FSurfacePositionUnion & SurfacePosition = StartSurfacePoint . Position ;
double ActualDist = 0. ;
UE : : Geometry : : FMeshGeodesicSurfaceTracer SurfaceTracer ( * SurfaceMesh ) ;
switch ( StartSurfacePoint . PositionType )
{
case FSurfacePoint : : EPositionType : : Vertex :
{
// convert vertex location
const int32 ExtrinsicVID = StartSurfacePoint . Position . VertexPosition . VID ;
const int32 ExtrinsicRefEID = SignpostData . VIDToReferenceEID [ ExtrinsicVID ] ;
const FMeshGeodesicSurfaceTracer : : FMeshTangentDirection TangentDirection = { ExtrinsicVID , ExtrinsicRefEID , TracePolarAngle } ;
ActualDist = SurfaceTracer . TraceMeshFromVertex ( TangentDirection , TraceDist ) ;
}
break ;
case FSurfacePoint : : EPositionType : : Edge :
{
// convert edge location - Not used, and might have bugs
const int32 RefSurfaceEID = SurfacePosition . EdgePosition . EdgeID ;
double Alpha = SurfacePosition . EdgePosition . Alpha ;
// convert to BC
const int32 SurfaceTID = SurfaceMesh - > GetEdgeT ( RefSurfaceEID ) . A ;
const FIndex2i SurfaceEdgeV = SurfaceMesh - > GetEdgeV ( RefSurfaceEID ) ;
const int32 IndexOf = SurfaceMesh - > GetTriEdges ( SurfaceTID ) . IndexOf ( RefSurfaceEID ) ;
const bool bSameOrientation = ( SurfaceMesh - > GetTriangle ( SurfaceTID ) [ IndexOf ] = = SurfaceEdgeV . A ) ;
FVector3d BaryPoint ( 0. , 0. , 0. ) ;
if ( ! bSameOrientation )
{
Alpha = 1. - Alpha ;
}
BaryPoint [ IndexOf ] = Alpha ;
BaryPoint [ ( IndexOf + 1 ) % 3 ] = 1. - Alpha ;
FMeshGeodesicSurfaceTracer : : FMeshSurfaceDirection DirectionOnSurface ( RefSurfaceEID , TracePolarAngle ) ;
ActualDist = SurfaceTracer . TraceMeshFromBaryPoint ( SurfaceTID , BaryPoint , DirectionOnSurface , TraceDist ) ;
}
break ;
case FSurfacePoint : : EPositionType : : Triangle :
{
// trace from BC point in triangle
const int32 SurfaceTID = SurfacePosition . TriPosition . TriID ;
2022-05-19 10:43:03 -04:00
const FVector3d BaryPoint = SurfacePosition . TriPosition . BarycentricCoords ;
2021-11-10 14:22:10 -05:00
const int32 RefSurfaceEID = SignpostData . TIDToReferenceEID [ SurfaceTID ] ;
FMeshGeodesicSurfaceTracer : : FMeshSurfaceDirection DirectionOnSurface ( RefSurfaceEID , TracePolarAngle ) ;
SurfaceTracer . TraceMeshFromBaryPoint ( SurfaceTID , BaryPoint , DirectionOnSurface , TraceDist ) ;
}
break ;
default :
{
// not possible
check ( 0 ) ;
}
}
// RVO..
return SurfaceTracer ;
}
} ;
namespace FNormalCoordSurfaceTraceImpl
{
using namespace IntrinsicCorrespondenceUtils ;
2022-05-19 10:43:03 -04:00
// @return ID of a vertex-adjacent edge that will allow all adjacent edges to be visited in CCW order.
template < typename MeshType >
int32 IdentifyInitialAdjacentEdge ( const MeshType & Mesh , const int32 VID )
2021-11-10 14:22:10 -05:00
{
2022-05-19 10:43:03 -04:00
if ( ! Mesh . IsVertex ( VID ) )
{
return MeshType : : InvalidID ;
}
if ( Mesh . IsBoundaryVertex ( VID ) )
{
// find the clockwise-most edge
for ( int32 NbrEID : Mesh . VtxEdgesItr ( VID ) )
{
if ( Mesh . IsBoundaryEdge ( NbrEID ) )
{
const int32 NbrTID = Mesh . GetEdgeT ( NbrEID ) . A ;
const int32 IndexOf = Mesh . GetTriEdges ( NbrTID ) . IndexOf ( NbrEID ) ;
if ( Mesh . GetTriangle ( NbrTID ) [ IndexOf ] = = VID )
{
return NbrEID ;
}
}
}
return MeshType : : InvalidID ; // shouldn't reach
}
else
{
for ( int32 NbrEID : Mesh . VtxEdgesItr ( VID ) )
{
// get the first
return NbrEID ;
}
return MeshType : : InvalidID ; // shouldn't reach here
}
}
/**
* Low - level method for use with normal - coordinate equipped intrinsic meshes .
*
* Given a surface edge that intersects ( but does not terminate at ) an intrinsic vertex ' IntrinsicVID '
* this function identifies the adjacent intrinsic mesh elements ( i . e . edges or faces ) also containing that surface edge .
*
* This is used when following a surface mesh edge across an intrinsic mesh ( using normal coordinates ) .
* Note : this situation is the result of an edge split of an intrinsic edge that was initially aligned with a surface edge .
*/
template < typename IntrinsicMeshType >
bool GetAdjElementsContainingSurfaceEdge ( const IntrinsicMeshType & IntrinsicMesh , const int32 IntrinsicVID , TArray < TTuple < int32 , int32 > > & IntrisicTIDs , TArray < int32 > & IntrisicEIDs )
{
checkSlow ( IsEdgePoint ( IntrinsicMesh . GetVertexSurfacePoint ( IntrinsicVID ) ) ) ;
const FNormalCoordinates & NCoords = IntrinsicMesh . GetNormalCoordinates ( ) ;
for ( const int32 EID : IntrinsicMesh . VtxEdgesItr ( IntrinsicVID ) )
{
if ( NCoords . IsSurfaceEdgeSegment ( EID ) )
{
IntrisicEIDs . Add ( EID ) ;
}
// triangle that has EID as an outgoing edge
const int32 TID = [ & ]
{
const FIndex2i AdjTris = IntrinsicMesh . GetEdgeT ( EID ) ;
for ( int32 i = 0 ; i < 2 ; + + i )
{
const int32 TID = AdjTris [ i ] ;
if ( TID = = IntrinsicMeshType : : InvalidID )
{
continue ;
}
const FIndex3i TriEIDs = IntrinsicMesh . GetTriEdges ( TID ) ;
const int32 IndexOf = TriEIDs . IndexOf ( EID ) ;
const bool bIsEIDOutgoing = ( IntrinsicMesh . GetTriangle ( TID ) [ IndexOf ] = = IntrinsicVID ) ;
if ( bIsEIDOutgoing )
{
return TID ;
}
}
return IntrinsicMeshType : : InvalidID ;
} ( ) ;
if ( TID = = IntrinsicMeshType : : InvalidID )
{
continue ;
}
const FIndex3i TriEIDs = IntrinsicMesh . GetTriEdges ( TID ) ;
const int32 IndexOf = TriEIDs . IndexOf ( EID ) ;
// permuted triangle is {a, b, c} where a is VID.
// given a surface edge crosses 'a' it will traverse the face of this triangle iff it exits face bc.
const FIndex3i PermutedEIDs = IntrinsicMesh . Permute ( IndexOf , TriEIDs ) ;
const int32 Nbc = NCoords . NumEdgeCrossing ( PermutedEIDs [ 1 ] ) ; // number of surface edges that exits face bc
if ( Nbc = = 0 ) // a surface edge that crosses 'a' doesn't exit this triangle face bc
{
continue ;
}
const int32 Eca_b = NCoords . NumCornerEmanatingRefEdges ( PermutedEIDs , 1 ) ; // number of surface edges that exit corner b and exit ca
if ( Eca_b > 0 ) // a surface edge that crosses 'a' is blocked from exiting this triangle by other surface edges
{
continue ;
}
const int32 Eab_c = NCoords . NumCornerEmanatingRefEdges ( PermutedEIDs , 2 ) ; // number of surface edges that exit corner c and exit ab
if ( Eab_c > 0 ) // a surface edge that crosses 'a' is blocked from exiting this triangle by other surface edges
{
continue ;
}
const int32 Cb = NCoords . NumCornerCrossingRefEdges ( PermutedEIDs , 1 ) ; // number of surface edges that cross corner b
const int32 Cc = NCoords . NumCornerCrossingRefEdges ( PermutedEIDs , 2 ) ; // number of surface edges that cross corner c
if ( Nbc > Cb + Cc ) // a surface edge that crosses 'a' must exit face bc
{
IntrisicTIDs . Add ( TTuple < int32 , int32 > ( TID , IndexOf ) ) ;
}
}
return ( IntrisicTIDs . Num ( ) > 0 | | IntrisicEIDs . Num ( ) > 0 ) ;
}
/**
* Low - level method for use with normal - coordinate equipped intrinsic meshes .
*
* Given an intrinsic mesh vertex that is situated on a surface mesh edge and information that specifies the incoming section of the surface edge ,
* this function computes the crossing point where the outgoing section of the surface edge exits the intrinsic vertex 1 - ring .
*
* if bIncomingIsEdge = = true , then the incoming section of the surface edge is along the intrinsic edge EdgeID = IncomingID ,
* otherwise it crossed the intrinsic triangle TriID = IncomingID .
*
* on return the IncomingID and bIncomingIsEdge have been updated with the surface mesh elements traversed by the path to next crossing ,
* and the crossing itself is encoded in the returned FEdgeAndCrossing struct . for convenience VID is also updated if the next crossing is an intrinsic vertx .
*/
template < typename IntrinsicMeshType >
FNormalCoordinates : : FEdgeAndCrossingIdx NextCrossingFromEdgePoint ( const IntrinsicMeshType & IntrinsicMesh , int32 & VID , int32 & IncomingID , bool & bIncomingIsEdge )
{
checkSlow ( IsEdgePoint ( IntrinsicMesh . GetVertexSurfacePoint ( VID ) ) ) ;
// four cases: the outer product of "enters {on intrinsic edge, across intrinsic tri}" with "exits {on intrinsic edge, across intrinsic tri}"
// find intrinsic mesh elements adjacent to VID that support the surface edge
TArray < TTuple < int32 , int32 > > TIDs ; TArray < int32 > EIDs ;
GetAdjElementsContainingSurfaceEdge ( IntrinsicMesh , VID , TIDs , EIDs ) ;
// only one edge crosses the implicit vert, so there can only be 2 sides to that edge ..
checkSlow ( TIDs . Num ( ) + EIDs . Num ( ) = = 2 ) ;
auto EncodeExitsOnEdge = [ & ] ( const int32 ExitEID , FNormalCoordinates : : FEdgeAndCrossingIdx & Xing )
{
const typename IntrinsicMeshType : : FEdge ExitEdge = IntrinsicMesh . GetEdge ( ExitEID ) ;
const int32 AdjTID = ExitEdge . Tri [ 0 ] ;
const int32 IndexOfe = IntrinsicMesh . GetTriEdges ( AdjTID ) . IndexOf ( ExitEID ) ;
const int32 IndexOfNextV = ( IntrinsicMesh . GetTriangle ( AdjTID ) [ IndexOfe ] = = VID ) ? ( IndexOfe + 1 ) % 3 : IndexOfe ;
Xing . TID = ExitEdge . Tri [ 0 ] ;
Xing . EID = IndexOfNextV ;
Xing . CIdx = 0 ;
return Xing ;
} ;
auto EncodeExitsAcrossTri = [ & ] ( const TTuple < int32 , int32 > & TridAndIdx , FNormalCoordinates : : FEdgeAndCrossingIdx & Xing )
{
const FNormalCoordinates & NCoords = IntrinsicMesh . GetNormalCoordinates ( ) ;
const int32 ExitTID = TridAndIdx . Get < 0 > ( ) ;
const int32 OutgoingIndex = TridAndIdx . Get < 1 > ( ) ;
// when counting the surface edge that cross the triangle face opposite to VID, what number is the single edge that eminates from VID?
const FIndex3i TriEIDs = IntrinsicMesh . GetTriEdges ( ExitTID ) ;
// permuted triangle is {a, b, c} where a is VID.
// given a surface edge crosses 'a' it will traverse the face of this triangle iff it exits face bc.
const FIndex3i PermutedEIDs = IntrinsicMesh . Permute ( OutgoingIndex , TriEIDs ) ;
const int32 Cc = NCoords . NumCornerCrossingRefEdges ( PermutedEIDs , 2 ) ; // number of surface edges that cross corner c
// counting from corner c towards b, this is the 'CC +1'-th surface edge to cross the intrinsic edge
const int32 CIdx = Cc + 1 ;
Xing . TID = ExitTID ;
Xing . EID = PermutedEIDs [ 1 ] ;
Xing . CIdx = CIdx ;
return Xing ;
} ;
FNormalCoordinates : : FEdgeAndCrossingIdx Xing ( { - 1 , - 1 , - 1 } ) ;
if ( bIncomingIsEdge ) // entered on an intrinsic edge
{
const int32 EnterEID = IncomingID ;
const bool bExitsOnEdge = ( EIDs . Num ( ) = = 2 ) ;
const bool bExitsAccrosTri = ( TIDs . Num ( ) > 0 ) ;
if ( bExitsOnEdge )
{
checkSlow ( ! bExitsAccrosTri ) ;
const int32 Id = ( EIDs [ 0 ] = = EnterEID ) ? 1 : 0 ;
checkSlow ( EIDs [ 1 - Id ] = = EnterEID ) ;
const int32 ExitEID = EIDs [ Id ] ;
EncodeExitsOnEdge ( ExitEID , Xing ) ;
VID = IntrinsicMesh . GetTriangle ( Xing . TID ) [ Xing . EID ] ;
IncomingID = ExitEID ;
bIncomingIsEdge = true ;
}
else if ( bExitsAccrosTri )
{
checkSlow ( ! bExitsOnEdge ) ;
checkSlow ( TIDs . Num ( ) = = 1 ) ; // can only traverse a single tri ( we already know it entered by an edge)
EncodeExitsAcrossTri ( TIDs [ 0 ] , Xing ) ;
IncomingID = TIDs [ 0 ] . Get < 0 > ( ) ;
bIncomingIsEdge = false ;
}
else
{
checkSlow ( 0 ) ;
}
}
else // entered across and intrinsic triangle
{
const int32 EnterTID = IncomingID ;
const bool bExitsOnEdge = ( EIDs . Num ( ) = = 1 ) ;
const bool bExitsAcrossTri = ( TIDs . Num ( ) = = 2 ) ;
if ( bExitsOnEdge )
{
const int32 ExitEID = EIDs [ 0 ] ;
EncodeExitsOnEdge ( ExitEID , Xing ) ;
VID = IntrinsicMesh . GetTriangle ( Xing . TID ) [ Xing . EID ] ;
IncomingID = ExitEID ;
bIncomingIsEdge = true ;
}
else if ( bExitsAcrossTri )
{
const int32 Id = ( TIDs [ 0 ] . Get < 0 > ( ) = = EnterTID ) ? 1 : 0 ;
EncodeExitsAcrossTri ( TIDs [ Id ] , Xing ) ;
IncomingID = TIDs [ 0 ] . Get < 0 > ( ) ;
bIncomingIsEdge = false ;
}
else
{
checkSlow ( 0 ) ;
}
}
return Xing ;
}
template < typename IntrinsicMeshType >
void ContinueFromEdgePointCrossing ( const IntrinsicMeshType & IntrinsicMesh , int32 VID , int32 IncomingID , bool bIncomingIsEdge , TArray < FNormalCoordinates : : FEdgeAndCrossingIdx > & Crossings )
{
int32 CIdx = 0 ;
while ( CIdx = = 0 & & IsEdgePoint ( IntrinsicMesh . GetVertexSurfacePoint ( VID ) ) )
{
FNormalCoordinates : : FEdgeAndCrossingIdx Xing = NextCrossingFromEdgePoint ( IntrinsicMesh , VID , IncomingID , bIncomingIsEdge ) ;
Crossings . Add ( Xing ) ;
CIdx = Xing . CIdx ;
}
}
/**
* Low - level method for use with normal - coordinate equipped intrinsic meshes .
* Traces the p - th surface edge crossing of the intrinsic edge edgeID across the adjacent intrinsic triangle TriID
* here ' P ' is counted is the CCW direction ( relative to TriID ) .
*
* on returns TTuple < in32 , int32 > = { EdgeID , P }
* on return if ' P ' = = 0 , the path exits a vertex otherwise it exits the updated EdgeID ( with index P relative to the next triangle it enters )
*
* This is adapted from Algorithm 2 of Gillespi et al , 2020
*/
template < typename IntrinsicMeshType >
TTuple < int32 , int32 > GetCrossingExit ( const IntrinsicMeshType & IntrinsicMesh , const FNormalCoordinates & NCoords , const int32 TriID , const int32 EdgeIDin , const int32 Pin )
{
TTuple < int32 , int32 > ExitCoord ( EdgeIDin , Pin ) ;
int32 & EdgeID = ExitCoord . Get < 0 > ( ) ;
int32 & P = ExitCoord . Get < 1 > ( ) ;
const FIndex3i TriEIDs = IntrinsicMesh . GetTriEdges ( TriID ) ;
const int32 IndexOf = TriEIDs . IndexOf ( EdgeID ) ;
const int32 Edge_ji = EdgeID ;
const int32 Edge_il = TriEIDs [ ( IndexOf + 1 ) % 3 ] ;
const int32 Edge_lj = TriEIDs [ ( IndexOf + 2 ) % 3 ] ;
const int32 N_ji = NCoords . NumEdgeCrossing ( Edge_ji ) ;
const int32 N_il = NCoords . NumEdgeCrossing ( Edge_il ) ;
const int32 N_lj = NCoords . NumEdgeCrossing ( Edge_lj ) ;
if ( N_ji > N_lj + N_il ) // case 1
{
// some of the N_ji edges that cross edge IJ must connect with the vertex at L
if ( P < = N_il )
{
// exits side il
EdgeID = Edge_il ;
P = Pin ;
}
else if ( ( N_il < P ) & & ( P < = N_ji - N_lj ) )
{
// this path terminates at vertex
// this is a slight abuse. The convention, when CrossingIndex = 0, we specify the vertex by the edge that originates there.
EdgeID = TriEIDs . IndexOf ( Edge_lj ) ; // GetTriange()[EdgeID] = vert. note changed from Edge_lj where i = GetTriangle()[TriEIDs.IndexOf(EdgeID_lj)] will be the vertex.
P = 0 ;
}
else
{
EdgeID = Edge_lj ;
P = Pin - ( N_ji - N_lj ) ;
}
}
else if ( N_lj > N_ji + N_il ) // case 2
{
EdgeID = Edge_lj ;
P = Pin + ( N_lj - N_ji ) ;
}
else if ( N_il > N_ji + N_lj ) // case 3
{
EdgeID = Edge_il ;
P = Pin ;
}
else // case 4
{
const int32 Cij_l = ( N_lj + N_il - N_ji ) / 2 ;
if ( P < = N_il - Cij_l )
{
EdgeID = Edge_il ;
P = Pin ;
}
else
{
const int32 Clj_i = ( N_il + N_ji - N_lj ) / 2 ;
EdgeID = Edge_lj ;
P = Pin - Clj_i + Cij_l ;
}
}
return ExitCoord ;
}
/**
* Low - level method : code that uses normal coordinates to continues tracing a surface edge across an intrinsic mesh until it encounters a vertex .
*
*
* This function requires that the fist edge crossing has already been computed will populate the a sequence of intrinsic edge crossings for the surface mesh edge
* until the path encounters a vertex . The actual crossings are recorded as a list of the edges crossed , and not the location on the individual edges .
*/
template < typename IntrinsicMeshType >
void ContinueTraceSurfaceEdgeAcrossFaces ( const IntrinsicMeshType & IntrinsicMesh , TArray < FNormalCoordinates : : FEdgeAndCrossingIdx > & Crossings )
{
const FNormalCoordinates : : FEdgeAndCrossingIdx & LastXing = Crossings . Last ( ) ;
checkSlow ( LastXing . CIdx ! = 0 ) ; // continuing a face crossing
const int32 StartTID = LastXing . TID ;
const int32 StartEID = LastXing . EID ;
const int32 StartP = LastXing . CIdx ;
2021-11-10 14:22:10 -05:00
const FNormalCoordinates & NCoords = IntrinsicMesh . GetNormalCoordinates ( ) ;
if ( ! IntrinsicMesh . IsTriangle ( StartTID ) )
{
return ;
}
const FIndex3i StartTriEIDs = IntrinsicMesh . GetTriEdges ( StartTID ) ;
checkSlow ( StartTriEIDs . IndexOf ( StartEID ) ! = - 1 ) ;
2022-05-19 10:43:03 -04:00
const int32 NumXStartEID = NCoords . NumEdgeCrossing ( StartEID ) ;
2021-11-10 14:22:10 -05:00
if ( StartP > NumXStartEID | | StartP < 1 ) // note the actual permitted range of P is smaller..
{
checkSlow ( 0 ) ; // this shouldn't happen
return ;
}
// This is adapted from Algorithm 2 of Gillespi et al, 2020
auto GetNextCrossing = [ & IntrinsicMesh , & NCoords ] ( int32 & TriID , int32 & EdgeID , int32 & P )
2022-05-19 10:43:03 -04:00
{
// get next tri
TriID = [ & ]
{
const FIndex2i EdgeT = IntrinsicMesh . GetEdgeT ( EdgeID ) ;
return ( EdgeT . A = = TriID ) ? EdgeT . B : EdgeT . A ;
} ( ) ;
checkSlow ( TriID ! = - 1 ) ;
2021-11-10 14:22:10 -05:00
2022-05-19 10:43:03 -04:00
const TTuple < int32 , int32 > ExitEIDandP = GetCrossingExit ( IntrinsicMesh , NCoords , TriID , EdgeID , P ) ;
EdgeID = ExitEIDandP . Get < 0 > ( ) ;
P = ExitEIDandP . Get < 1 > ( ) ;
} ;
2021-11-10 14:22:10 -05:00
int32 P = StartP ;
int32 EID = StartEID ;
int32 TID = StartTID ;
do
{
GetNextCrossing ( TID , EID , P ) ;
auto & Xing = Crossings . AddZeroed_GetRef ( ) ;
Xing . TID = TID ;
Xing . EID = EID ;
Xing . CIdx = P ;
checkSlow ( P > - 1 ) ;
} while ( P ! = 0 ) ; // P = 0 when the path terminates ( paths terminate at vertices only)
}
2022-05-19 10:43:03 -04:00
/**
* Low - level method for use with normal - coordinate equipped intrinsic meshes .
*
* Given a partial trace of a surface edge , continue the trace across the intrinsic mesh until the path terminates at one end
* of the surface edge .
*
* Crossings . Last ( ) must be an edge crossing , and not a vertex point .
*/
template < typename IntrinsicMeshType >
void ContinueTraceSurfaceEdge ( const IntrinsicMeshType & IntrinsicMesh , TArray < FNormalCoordinates : : FEdgeAndCrossingIdx > & Crossings )
{
if ( Crossings . Num ( ) = = 0 )
{
return ;
}
auto IsIntrinsicVertex = [ ] ( const FNormalCoordinates : : FEdgeAndCrossingIdx & Xing ) - > bool
{
return ( Xing . CIdx = = 0 ) ;
} ;
checkSlow ( ! IsIntrinsicVertex ( Crossings . Last ( ) ) ) ;
auto IsSurfaceVertex = [ & IntrinsicMesh , & IsIntrinsicVertex ] ( const FNormalCoordinates : : FEdgeAndCrossingIdx & Xing ) - > bool
{
if ( ! IsIntrinsicVertex ( Xing ) )
{
return false ;
}
// convert to intrinsic VID
const int32 IndexOfV = Xing . EID ;
const int32 IntrinsicVID = IntrinsicMesh . GetTriangle ( Xing . TID ) [ IndexOfV ] ;
return IsVertexPoint ( IntrinsicMesh . GetVertexSurfacePoint ( IntrinsicVID ) ) ;
} ;
while ( ! IsSurfaceVertex ( Crossings . Last ( ) ) )
{
const FNormalCoordinates : : FEdgeAndCrossingIdx & LastXing = Crossings . Last ( ) ;
if ( ! IsIntrinsicVertex ( LastXing ) )
{
// trace enters intrinsic triangle edge - continue across intrinsic triangle faces.
ContinueTraceSurfaceEdgeAcrossFaces ( IntrinsicMesh , Crossings ) ;
}
else
{
// trace hit an intrinsic vertex, coming from an intrinsic triangle face.
// convert to intrinsic VID
const int32 IndexOfV = LastXing . EID ;
const int32 IntrinsicVID = IntrinsicMesh . GetTriangle ( LastXing . TID ) [ IndexOfV ] ;
checkSlow ( IsEdgePoint ( IntrinsicMesh . GetVertexSurfacePoint ( IntrinsicVID ) ) ) ;
// advance along the surface edge until it either enters an intrinsic triangle or
// follows on more intrinsic edges to terminate at a surface vertex.
const bool bEnterTypeEdge = false ; // we entered across a triangle face
const int32 EnterEID = LastXing . TID ;
ContinueFromEdgePointCrossing ( IntrinsicMesh , IntrinsicVID , EnterEID , bEnterTypeEdge , Crossings ) ;
}
}
}
/**
* Low - level method for use with normal - coordinate equipped intrinsic meshes .
*
* Traces a surface edge across an intrinsic mesh , the result is an array of intrinsic edges crossed and an integer coordinates
* that specify the intrinsic edge specific index of the crossing .
*
* The surface edge to trace is specified by the triangle adjacent to the edge , and the local index of the edge within that triangle
*/
template < typename IntrinsicMeshType >
TArray < FNormalCoordinates : : FEdgeAndCrossingIdx > TraceSurfaceEdge ( const IntrinsicMeshType & IntrinsicMesh ,
2021-11-10 14:22:10 -05:00
const int32 SurfaceTID , const int32 IndexOf )
{
const FNormalCoordinates & NCoords = IntrinsicMesh . GetNormalCoordinates ( ) ;
const FDynamicMesh3 & SurfaceMesh = * NCoords . SurfaceMesh ;
if ( ! SurfaceMesh . IsTriangle ( SurfaceTID ) | | IndexOf > 2 | | IndexOf < 0 )
{
TArray < FNormalCoordinates : : FEdgeAndCrossingIdx > EmptyCrossings ;
return EmptyCrossings ;
}
// the origin vertex
const int32 StartVID = SurfaceMesh . GetTriangle ( SurfaceTID ) [ IndexOf ] ;
// the surface edge we are tracing
const int32 TraceEID = SurfaceMesh . GetTriEdge ( SurfaceTID , IndexOf ) ;
2022-05-19 10:43:03 -04:00
2021-11-10 14:22:10 -05:00
const int32 RefEID = NCoords . VIDToReferenceEID [ StartVID ] ;
const int32 OrderOfTraceEID = NCoords . GetEdgeOrder ( StartVID , TraceEID ) ;
const int32 ValenceOfStartVID = NCoords . RefVertDegree [ StartVID ] ;
checkSlow ( OrderOfTraceEID ! = - 1 ) ;
// data to gather about the intrinsic triangle that where the trace starts.
struct
{
bool bIsAlsoSurfaceEdge = false ;
int32 TID = - 1 ;
int32 EID = - 1 ;
int32 IdxOf = - 1 ;
int32 FirstRoundabout = - 1 ;
int32 SecondRoundabout = - 1 ;
} FinderInfo ;
// Identify the target intrinsic triangle where this edge trace starts.
{
// where to start when visiting the intrinsic triangles that are adjacent to the startVID
2022-05-19 10:43:03 -04:00
const int32 VistorStartEID = IdentifyInitialAdjacentEdge ( IntrinsicMesh , StartVID ) ;
2021-11-10 14:22:10 -05:00
2022-05-24 11:10:33 -04:00
// test for the special case when the StartVID has valence one on the intrinsic mesh (ie a single triangle is wrapped into a cone)
const FIndex2i IntrinsicEdgeT = IntrinsicMesh . GetEdgeT ( VistorStartEID ) ;
if ( IntrinsicEdgeT . A = = IntrinsicEdgeT . B )
{
const int32 IntrinsicEID = VistorStartEID ;
const int32 IntrinsicTID = IntrinsicEdgeT . A ;
const FIndex3i IntrinsicVIDs = IntrinsicMesh . GetTriangle ( IntrinsicTID ) ;
const int32 IdxOf = IntrinsicVIDs . IndexOf ( StartVID ) ;
const int32 ThisRoundabout = NCoords . RoundaboutOrder [ IntrinsicTID ] [ IdxOf ] ;
const bool bOnSurfaceEdge = NCoords . IsSurfaceEdgeSegment ( IntrinsicEID ) ;
const bool bEquivalentToSurface = bOnSurfaceEdge & & ( ThisRoundabout = = OrderOfTraceEID ) ;
2021-11-10 14:22:10 -05:00
2022-05-24 11:10:33 -04:00
FinderInfo . bIsAlsoSurfaceEdge = bEquivalentToSurface ;
FinderInfo . TID = IntrinsicTID ;
FinderInfo . EID = IntrinsicEID ;
FinderInfo . IdxOf = IdxOf ;
FinderInfo . FirstRoundabout = ThisRoundabout ;
FinderInfo . SecondRoundabout = ThisRoundabout ;
}
else
{
// when visiting each adjacent intrinsic triangle (in CCW order) test if the surface edge we trace is inside this triangle.
auto EdgeFinder = [ & ] ( int32 IntrinsicTID , int32 IntrinsicEID , int32 IdxOf ) - > bool
2022-05-19 10:43:03 -04:00
{
2021-11-10 14:22:10 -05:00
2022-05-24 11:10:33 -04:00
const FIndex3i IntrinsicTriEIDs = IntrinsicMesh . GetTriEdges ( IntrinsicTID ) ;
const int32 ThisRoundabout = NCoords . RoundaboutOrder [ IntrinsicTID ] [ IdxOf ] ;
const bool bOnSurfaceEdge = NCoords . IsSurfaceEdgeSegment ( IntrinsicEID ) ;
const bool bEquivalentToSurface = bOnSurfaceEdge & & ( ThisRoundabout = = OrderOfTraceEID ) ;
int32 NextRoundabout = - 1 ;
bool bShouldBreak = false ;
if ( bEquivalentToSurface ) // test if the current intrinsic edge is the same as the surface mesh trace edge
2022-05-19 10:43:03 -04:00
{
2022-05-24 11:10:33 -04:00
bShouldBreak = true ;
}
else // test if the edge starts in this intrinsic triangle
{
const int32 NextIntrinsicEID = IntrinsicTriEIDs [ ( IdxOf + 2 ) % 3 ] ;
const FIndex2i NextIntrinsicEdgeT = IntrinsicMesh . GetEdgeT ( NextIntrinsicEID ) ;
const int32 NextIntrinsicTID = ( NextIntrinsicEdgeT . A = = IntrinsicTID ) ? NextIntrinsicEdgeT . B : NextIntrinsicEdgeT . A ;
if ( NextIntrinsicTID ! = - 1 )
2022-05-19 10:43:03 -04:00
{
2022-05-24 11:10:33 -04:00
const int32 NextIndexOf = IntrinsicMesh . GetTriEdges ( NextIntrinsicTID ) . IndexOf ( NextIntrinsicEID ) ;
NextRoundabout = NCoords . RoundaboutOrder [ NextIntrinsicTID ] [ NextIndexOf ] ;
if ( NextRoundabout < ThisRoundabout ) // Order = {NextRO |zero cut | ThisRO}
{
// we have crossed the zero roundabout cut ( e.g. NextRo = 2oclock and ThisRo = 11oclock)
if ( NextRoundabout > OrderOfTraceEID ) // Order = {NextRO, Trace | zero cut | ThisRO}
{
bShouldBreak = true ;
}
else
{
NextRoundabout + = ValenceOfStartVID ; // Order = {NextRO, | zero cut |, Trace(?), ThisRO}
}
}
checkSlow ( IntrinsicMesh . GetTriangle ( NextIntrinsicTID ) [ NextIndexOf ] = = StartVID ) ;
//checkSlow(NextRoundabout != 0 || ThisRoundabout != 0)
if ( ( NextRoundabout > OrderOfTraceEID ) & & ( OrderOfTraceEID > = ThisRoundabout ) )
2022-05-19 10:43:03 -04:00
{
bShouldBreak = true ;
}
}
2022-05-24 11:10:33 -04:00
else // mesh boundary case and we made it to the last triangle w/o finding this edge. it must be in this one.
2022-05-19 10:43:03 -04:00
{
bShouldBreak = true ;
}
}
2022-05-24 11:10:33 -04:00
if ( bShouldBreak )
2022-05-19 10:43:03 -04:00
{
2022-05-24 11:10:33 -04:00
FinderInfo . bIsAlsoSurfaceEdge = bEquivalentToSurface ;
FinderInfo . TID = IntrinsicTID ;
FinderInfo . EID = IntrinsicEID ;
FinderInfo . IdxOf = IdxOf ;
FinderInfo . FirstRoundabout = ThisRoundabout ;
FinderInfo . SecondRoundabout = NextRoundabout ;
2022-05-19 10:43:03 -04:00
}
2021-11-10 14:22:10 -05:00
2022-05-24 11:10:33 -04:00
return bShouldBreak ;
} ;
VisitVertexAdjacentElements ( IntrinsicMesh , StartVID , VistorStartEID , EdgeFinder ) ;
2021-11-10 14:22:10 -05:00
2022-05-24 11:10:33 -04:00
checkSlow ( FinderInfo . TID ! = - 1 ) ; // should have found something!
}
2021-11-10 14:22:10 -05:00
}
TArray < FNormalCoordinates : : FEdgeAndCrossingIdx > Crossings ;
// add start vertex location
auto & StartXing = Crossings . AddZeroed_GetRef ( ) ;
StartXing . TID = FinderInfo . TID ;
StartXing . EID = FinderInfo . IdxOf ; // TriVIDs(IdxOf) = StartVID
StartXing . CIdx = 0 ;
2022-05-19 10:43:03 -04:00
if ( FinderInfo . bIsAlsoSurfaceEdge )
2021-11-10 14:22:10 -05:00
{
2022-05-19 10:43:03 -04:00
// the surface edge we are tracing is initially coincident with an intrinsic edge.
// add end intrinsic vertex location
2021-11-10 14:22:10 -05:00
auto & EndXing = Crossings . AddZeroed_GetRef ( ) ;
EndXing . TID = FinderInfo . TID ;
EndXing . EID = ( FinderInfo . IdxOf + 1 ) % 3 ; // TriVIDs( (IdxOf +1)%3) = EndVID
EndXing . CIdx = 0 ;
2022-05-19 10:43:03 -04:00
const int32 IndexOfV = ( FinderInfo . IdxOf + 1 ) % 3 ;
const int32 EndIntrinsicVID = IntrinsicMesh . GetTriangle ( FinderInfo . TID ) [ IndexOfV ] ;
// the surface edge will continue if the end intrinsic vertex is not a surface vertex.
// advance along the surface edge until it either enters an intrinsic triangle or
// follows on more intrinsic edges to terminate at a surface vertex.
if ( IsEdgePoint ( IntrinsicMesh . GetVertexSurfacePoint ( EndIntrinsicVID ) ) )
{
const bool bEnterTypeEdge = true ;
const int32 EnterEID = FinderInfo . EID ;
ContinueFromEdgePointCrossing ( IntrinsicMesh , EndIntrinsicVID , EnterEID , bEnterTypeEdge , Crossings ) ;
const bool bVertexTerminated = ( Crossings . Last ( ) . CIdx = = 0 ) ;
if ( bVertexTerminated )
{
// Made it to the end of the surface edge since the trace must have hit an intrinsic vertex that is also a surface vertex.
return MoveTemp ( Crossings ) ;
}
else
{
// continue across intrinsic faces
ContinueTraceSurfaceEdge ( IntrinsicMesh , Crossings ) ;
}
}
else
{
// Made it to the end of the surface edge since the trace must have hit an intrinsic vertex that is also a surface vertex.
return MoveTemp ( Crossings ) ;
}
2021-11-10 14:22:10 -05:00
}
else
{
// edge trace starts at StartVID and exits the opposite edge of intrinsic Tri TID (but it isn't an edge of the intrinsic tri).
const FIndex3i TriEIDs = IntrinsicMesh . GetTriEdges ( FinderInfo . TID ) ;
const int32 OppEID = TriEIDs [ ( FinderInfo . IdxOf + 1 ) % 3 ] ;
2022-05-19 10:43:03 -04:00
2021-11-10 14:22:10 -05:00
const int32 CrossingIdx = [ & ]
2022-05-19 10:43:03 -04:00
{
const int32 AdvanceFromFirstRO = ( ( ValenceOfStartVID + OrderOfTraceEID ) - FinderInfo . FirstRoundabout ) % ValenceOfStartVID ;
if ( NCoords . IsSurfaceEdgeSegment ( FinderInfo . EID ) )
{
return AdvanceFromFirstRO ;
}
else
{
const int32 NumCrossings = NCoords . NumEdgeCrossing ( FinderInfo . EID ) ;
return AdvanceFromFirstRO + 1 + NumCrossings ;
}
} ( ) ;
2021-11-10 14:22:10 -05:00
checkSlow ( CrossingIdx > 0 ) ; // would be zero if the edge was also a surface edge, but that case is handled above
2022-05-19 10:43:03 -04:00
// the first intrinsic edge crossing. Need this to jump-start the continuation.
auto & Xing = Crossings . AddZeroed_GetRef ( ) ;
Xing . TID = FinderInfo . TID ;
Xing . EID = OppEID ;
Xing . CIdx = CrossingIdx ;
2021-11-10 14:22:10 -05:00
2022-05-19 10:43:03 -04:00
ContinueTraceSurfaceEdge ( IntrinsicMesh , Crossings ) ;
2021-11-10 14:22:10 -05:00
}
return MoveTemp ( Crossings ) ;
}
2022-05-19 10:43:03 -04:00
/**
* Low - level method for use with normal - coordinate equipped intrinsic meshes .
*
* Traces a surface edge across an intrinsic mesh , the result is an array of intrinsic edges crossed each with a corresponding
* integer . The integer is used to disambiguate when a single intrinsic edge is crossed by multiple surface edges .
*/
template < typename IntrinsicMeshType >
TArray < FNormalCoordinates : : FEdgeAndCrossingIdx > TraceSurfaceEdge ( const IntrinsicMeshType & IntrinsicMesh ,
2021-11-10 14:22:10 -05:00
const int32 SurfaceEID , const bool bReverse )
{
const FNormalCoordinates & NCoords = IntrinsicMesh . GetNormalCoordinates ( ) ;
const FDynamicMesh3 & SurfaceMesh = * NCoords . SurfaceMesh ;
if ( ! SurfaceMesh . IsEdge ( SurfaceEID ) )
{
TArray < FNormalCoordinates : : FEdgeAndCrossingIdx > Crossings ;
return MoveTemp ( Crossings ) ;
}
const FIndex2i EdgeV = SurfaceMesh . GetEdgeV ( SurfaceEID ) ;
const int32 StartVID = ( bReverse ) ? EdgeV . B : EdgeV . A ;
2022-05-19 10:43:03 -04:00
2021-11-10 14:22:10 -05:00
2022-05-19 10:43:03 -04:00
// if this surface edge corresponds to an intrinsic edge, then the trace is trivial
2021-11-10 14:22:10 -05:00
{
2022-05-19 10:43:03 -04:00
// note this test relies on the fact that intrinsic vertices that sit on surface mesh vertices
// will have the same vertex IDs by construction.
int32 EquivalentIntrinsicEID = - 1 ;
const int32 EndVID = ( bReverse ) ? EdgeV . A : EdgeV . B ;
for ( int32 IntrinsicEID : IntrinsicMesh . VtxEdgesItr ( StartVID ) )
{
const FIndex2i IntrinsicEdgeV = IntrinsicMesh . GetEdgeV ( IntrinsicEID ) ;
if ( IntrinsicEdgeV . IndexOf ( EndVID ) ! = - 1 & & NCoords . IsSurfaceEdgeSegment ( IntrinsicEID ) )
{
EquivalentIntrinsicEID = IntrinsicEID ;
break ;
}
}
2021-11-10 14:22:10 -05:00
2022-05-19 10:43:03 -04:00
if ( EquivalentIntrinsicEID ! = - 1 )
{
checkSlow ( IntrinsicMesh . IsEdge ( EquivalentIntrinsicEID ) ) ;
const int32 IntrinsicEID = EquivalentIntrinsicEID ;
const int32 IntrinsicTID = IntrinsicMesh . GetEdgeT ( IntrinsicEID ) . A ;
const int32 IndexOf = IntrinsicMesh . GetTriEdges ( IntrinsicTID ) . IndexOf ( IntrinsicEID ) ;
const bool bRerverseIntrinsic = ! ( IntrinsicMesh . GetTriangle ( IntrinsicTID ) [ IndexOf ] = = StartVID ) ;
2021-11-10 14:22:10 -05:00
2022-05-19 10:43:03 -04:00
TArray < FNormalCoordinates : : FEdgeAndCrossingIdx > Crossings ;
Crossings . SetNumUninitialized ( 2 ) ;
auto & XingStart = Crossings [ 0 ] ;
XingStart . TID = IntrinsicTID ;
XingStart . EID = ( bRerverseIntrinsic ) ? ( IndexOf + 1 ) % 3 : IndexOf ;
XingStart . CIdx = 0 ;
2021-11-10 14:22:10 -05:00
2022-05-19 10:43:03 -04:00
auto & XingEnd = Crossings [ 1 ] ;
XingEnd . TID = IntrinsicTID ;
XingEnd . EID = ( bRerverseIntrinsic ) ? IndexOf : ( IndexOf + 1 ) % 3 ;
XingEnd . CIdx = 0 ;
2021-11-10 14:22:10 -05:00
2022-05-19 10:43:03 -04:00
return MoveTemp ( Crossings ) ;
}
2021-11-10 14:22:10 -05:00
}
2022-05-19 10:43:03 -04:00
// the edge isn't equivalent to an intrinsic edge: encode its direction by identifying it as an edge of a triangle and do the trace
2021-11-10 14:22:10 -05:00
{
const FIndex2i EdgeT = SurfaceMesh . GetEdgeT ( SurfaceEID ) ;
int32 TID = EdgeT . A ;
int32 IndexOf = SurfaceMesh . GetTriEdges ( TID ) . IndexOf ( SurfaceEID ) ;
if ( SurfaceMesh . GetTriangle ( TID ) [ IndexOf ] ! = StartVID )
{
2022-05-19 10:43:03 -04:00
if ( EdgeT . B ! = - 1 )
{
TID = EdgeT . B ;
IndexOf = SurfaceMesh . GetTriEdges ( TID ) . IndexOf ( SurfaceEID ) ;
checkSlow ( SurfaceMesh . GetTriangle ( TID ) [ IndexOf ] = = StartVID ) ;
return TraceSurfaceEdge ( IntrinsicMesh , TID , IndexOf ) ;
}
else
{
TArray < FNormalCoordinates : : FEdgeAndCrossingIdx > TraceResult = TraceSurfaceEdge ( IntrinsicMesh , TID , IndexOf ) ;
Algo : : Reverse ( TraceResult ) ;
return MoveTemp ( TraceResult ) ;
}
}
else
{
return TraceSurfaceEdge ( IntrinsicMesh , TID , IndexOf ) ;
2021-11-10 14:22:10 -05:00
}
}
}
/**
2022-05-19 10:43:03 -04:00
* Low - level utility for use with normal - coordinate equipped intrinsic meshes .
*
* Utility to trace an edge defined on the TraceMesh across the HostMesh , given a list of host mesh edges intersected by the trace mesh .
* this assumes the host mesh and trace mesh come from an intrinsic mesh , surface mesh pair .
*
* From the list of edges being crossed , this creates a 2 d triangle strip of the triangles being traversed , and translates Trace { Start , End } SurfacePosition
* to this space so it can compute the actual intersection locations of the trace with the triangle edges ( stored line - based barycentric coord , alpha ) .
*
* Note : this assumes , that StartSurfacePoint and EndSurfacePoints are actually points on the edge , and HostXing lists the Ids of the edges
* being crossed in order .
2021-11-10 14:22:10 -05:00
*/
2022-05-19 10:43:03 -04:00
template < typename HostMeshType , typename TraceMeshType , typename TraceVIDToSurfacePointFtor >
void ConvertEdgesCrossed ( const FSurfacePoint & StartSurfacePoint , const FSurfacePoint & EndSurfacePoint ,
const TArray < int32 > & HostEdgesCrossed , const HostMeshType & HostMesh ,
const TraceMeshType & TraceMesh , double CoalesceThreshold ,
const TraceVIDToSurfacePointFtor & VIDToSurfacePoint ,
TArray < FSurfacePoint > & EdgeTrace )
{
if ( HostEdgesCrossed . Num ( ) = = 0 )
{
// no edge crossings to convert.
return ;
}
// utility to keep from adding duplicate VIDs. Duplicate VIDs could result when Coalescing..
auto AddVIDToTrace = [ & EdgeTrace ] ( const int32 HostVID )
{
if ( EdgeTrace . Num ( ) = = 0 )
{
EdgeTrace . Emplace ( HostVID ) ;
}
else
{
const FSurfacePoint & LastPoint = EdgeTrace . Last ( ) ;
const bool bSameAsLast = ( IsVertexPoint ( LastPoint )
& & LastPoint . Position . VertexPosition . VID = = HostVID ) ;
if ( ! bSameAsLast )
{
EdgeTrace . Emplace ( HostVID ) ;
}
}
} ;
// knowing the host mesh edges this trace edge crosses, we make a 2d triangle strip by unfolding the 3d triangles the edge crosses
// and solve a 2x2 problem for each 2d edge crossed.
// struct holds bare-bones 2d triangle strip and ability to map vertexIDs from the src mesh
struct
{
TMap < int32 , int32 > ToStripIndexMap ; // maps from mesh VID to triangle strip VID.
TArray < FVector2d > StripVertexBuffer ;
} TriangleStrip ;
TriangleStrip . StripVertexBuffer . Reserve ( 3 + HostEdgesCrossed . Num ( ) ) ;
// Functor to add a triangle to the triangle strip. This assumes that at least one shared edge of this triangle already exits in the strip
auto AddTriangleToStrip = [ & HostMesh , & TriangleStrip ] ( const int32 HostTID )
{
FIndex3i TriVIDs = HostMesh . GetTriangle ( HostTID ) ;
// two of the tri vertices are already in the buffer. Identify the new one.
int32 IndexOf = - 1 ;
for ( int32 i = 0 ; i < 3 ; + + i )
{
const int32 VID = TriVIDs [ i ] ;
const int32 * StripVID = TriangleStrip . ToStripIndexMap . Find ( VID ) ;
if ( ! StripVID )
{
IndexOf = i ;
break ;
}
}
// no need to do anything if all vertices had previously been added,
if ( IndexOf = = - 1 )
{
return ;
}
const FVector3d Verts [ 3 ] = { HostMesh . GetVertex ( TriVIDs [ 0 ] ) , HostMesh . GetVertex ( TriVIDs [ 1 ] ) , HostMesh . GetVertex ( TriVIDs [ 2 ] ) } ;
// with new vert last (i.e. V2).
const FIndex3i Reordered ( ( IndexOf + 1 ) % 3 , ( IndexOf + 2 ) % 3 , IndexOf ) ;
// the spanning vectors
const FVector3d E1 = Verts [ Reordered [ 1 ] ] - Verts [ Reordered [ 0 ] ] ;
const FVector3d E2 = Verts [ Reordered [ 2 ] ] - Verts [ Reordered [ 0 ] ] ;
// coordinates of V2 relative to the direction of E1, and its orthogonal complement.
const double E1DotE2 = E2 . Dot ( E1 ) ;
const double E1LengthSqr = FMath : : Max ( E1 . SizeSquared ( ) , TMathUtilConstants < double > : : ZeroTolerance ) ;
const double E1Length = FMath : : Sqrt ( E1LengthSqr ) ;
const double E1Dist = E2 . Dot ( E1 ) / E1Length ;
const double OrthE1DistSqr = FMath : : Max ( 0. , E2 . SizeSquared ( ) - E1DotE2 * E1DotE2 / E1LengthSqr ) ;
const double OrthE1Dist = FMath : : Sqrt ( OrthE1DistSqr ) ;
// 2d version of E1
const int32 * StripTriV0 = TriangleStrip . ToStripIndexMap . Find ( TriVIDs [ Reordered [ 0 ] ] ) ;
const int32 * StripTriV1 = TriangleStrip . ToStripIndexMap . Find ( TriVIDs [ Reordered [ 1 ] ] ) ;
checkSlow ( StripTriV0 ) ; checkSlow ( StripTriV1 ) ;
const FVector2d & StripTriVert0 = TriangleStrip . StripVertexBuffer [ * StripTriV0 ] ;
const FVector2d & StripTriVert1 = TriangleStrip . StripVertexBuffer [ * StripTriV1 ] ;
const FVector2d StripE1 = ( StripTriVert1 - StripTriVert0 ) ;
const FVector2d StripE1Perp ( - StripE1 [ 1 ] , StripE1 [ 0 ] ) ; // Rotate StripE1 90 CCW.
// new vertex 2d position
const FVector2d StripTriVert2 = StripTriVert0 + ( StripE1 ) * E1DotE2 / E1LengthSqr + ( StripE1Perp / E1Length ) * OrthE1Dist ;
TriangleStrip . StripVertexBuffer . Add ( StripTriVert2 ) ;
const int32 StripVID = TriangleStrip . StripVertexBuffer . Num ( ) - 1 ;
const int32 SurfaceVID = TriVIDs [ IndexOf ] ;
TriangleStrip . ToStripIndexMap . Add ( SurfaceVID , StripVID ) ;
} ;
// find host triangle that contains both the StartVID and the first host edge we cross.
const int32 FirstHostTID = [ & ]
{
if ( IsVertexPoint ( StartSurfacePoint ) )
{
const int32 HostEID = HostEdgesCrossed [ 0 ] ;
const FIndex2i HostEdgeT = HostMesh . GetEdgeT ( HostEID ) ;
const FIndex3i TriAVIDs = HostMesh . GetTriangle ( HostEdgeT . A ) ;
const FIndex3i TriBVIDs = HostMesh . GetTriangle ( HostEdgeT . B ) ;
const int32 HostStartVID = StartSurfacePoint . Position . VertexPosition . VID ;
2022-05-24 11:10:33 -04:00
const int32 HostTID = ( TriAVIDs . IndexOf ( HostStartVID ) ! = - 1 ) ? HostEdgeT . A : HostEdgeT . B ;
checkSlow ( HostMesh . GetTriangle ( HostTID ) . IndexOf ( HostStartVID ) ! = - 1 ) ;
return HostTID ;
2022-05-19 10:43:03 -04:00
}
else if ( IsEdgePoint ( StartSurfacePoint ) )
{
const int32 StartEID = StartSurfacePoint . Position . EdgePosition . EdgeID ;
const int32 ExitEID = HostEdgesCrossed [ 0 ] ;
const FIndex2i AdjTris = HostMesh . GetEdgeT ( StartEID ) ;
const int32 NumAdj = ( AdjTris [ 1 ] = = - 1 ) ? 1 : 2 ;
for ( int i = 0 ; i < NumAdj ; + + i )
{
int32 TID = AdjTris [ i ] ;
if ( HostMesh . GetTriEdges ( TID ) . IndexOf ( ExitEID ) ! = - 1 )
{
return TID ;
}
}
return - 1 ;
}
else
{
checkSlow ( IsFacePoint ( StartSurfacePoint ) ) ;
return StartSurfacePoint . Position . TriPosition . TriID ;
}
} ( ) ;
// jump-start the process of making the triangle strip by adding the first 2 verts
// that define the edge prior (in the ccw sense) to the first edge crossing.
{
const FIndex3i TriVIDs = HostMesh . GetTriangle ( FirstHostTID ) ;
const FIndex3i TriEIDs = HostMesh . GetTriEdges ( FirstHostTID ) ;
const int32 FirstEIDXed = HostEdgesCrossed [ 0 ] ;
const int32 IndexOfe = TriEIDs . IndexOf ( FirstEIDXed ) ;
const int32 FirstVID = TriVIDs [ ( IndexOfe + 2 ) % 3 ] ;
const int32 SecondVID = TriVIDs [ ( IndexOfe ) ] ;
const FVector3d Vert0 = HostMesh . GetVertex ( FirstVID ) ;
const FVector3d Vert1 = HostMesh . GetVertex ( SecondVID ) ;
const double LSqr = FMath : : Max ( ( Vert0 - Vert1 ) . SizeSquared ( ) , TMathUtilConstants < double > : : ZeroTolerance ) ;
const double L = FMath : : Sqrt ( LSqr ) ;
const FVector2d StripVert0 ( 0. , 0. ) ;
const FVector2d StripVert1 ( L , 0. ) ;
TriangleStrip . StripVertexBuffer . Add ( StripVert0 ) ;
TriangleStrip . ToStripIndexMap . Add ( FirstVID , 0 ) ;
TriangleStrip . StripVertexBuffer . Add ( StripVert1 ) ;
TriangleStrip . ToStripIndexMap . Add ( SecondVID , 1 ) ;
}
// unfold the triangle strip, add the first triangle and then all subsequent ones
AddTriangleToStrip ( FirstHostTID ) ;
{
int32 LastHostTID = FirstHostTID ;
for ( int32 HostEID : HostEdgesCrossed )
{
const FIndex2i EdgeT = HostMesh . GetEdgeT ( HostEID ) ;
LastHostTID = ( EdgeT . A = = LastHostTID ) ? EdgeT . B : EdgeT . A ;
AddTriangleToStrip ( LastHostTID ) ;
}
}
// translate a trace point on the surface of the host mesh, to Fvector2d relative to the 2d triangle strip
auto To2dStripPosition = [ & ] ( const FSurfacePoint & TraceSurfacePoint ) - > FVector2d
{
if ( IsVertexPoint ( TraceSurfacePoint ) )
{
// end point is a vertex in the host mesh
const auto & HostVertexPosition = TraceSurfacePoint . Position . VertexPosition ;
const int32 HostVID = HostVertexPosition . VID ;
const int32 * StripVID = TriangleStrip . ToStripIndexMap . Find ( HostVID ) ;
checkSlow ( StripVID ) ;
return TriangleStrip . StripVertexBuffer [ * StripVID ] ;
}
if ( IsEdgePoint ( TraceSurfacePoint ) )
{
// end point lies on an edge of the host mesh. identify the edge vertices in the 2d-triangle strip
// and reconstruct the point from alpha
const auto & HostEdgePosition = TraceSurfacePoint . Position . EdgePosition ;
const double Alpha = HostEdgePosition . Alpha ;
const FIndex2i HostEdgeV = HostMesh . GetEdgeV ( HostEdgePosition . EdgeID ) ;
const FIndex2i StripEdgeV ( * TriangleStrip . ToStripIndexMap . Find ( HostEdgeV . A ) ,
* TriangleStrip . ToStripIndexMap . Find ( HostEdgeV . B ) ) ;
const FVector2d StripEdgePos [ 2 ] = { TriangleStrip . StripVertexBuffer [ StripEdgeV . A ] ,
TriangleStrip . StripVertexBuffer [ StripEdgeV . B ] } ;
return Alpha * StripEdgePos [ 0 ] + ( 1. - Alpha ) * StripEdgePos [ 1 ] ;
}
else
{
// end point lies in the face of the last triangle. identify the tri vertices in the 2d-triangle strip and
// reconstruct the point from the barycentric coordinates.
checkSlow ( IsFacePoint ( TraceSurfacePoint ) ) ;
const auto & HostTriPosition = TraceSurfacePoint . Position . TriPosition ;
const FVector3d Barycentric = HostTriPosition . BarycentricCoords ;
const FIndex3i HostTri = HostMesh . GetTriangle ( HostTriPosition . TriID ) ;
const FIndex3i StripTri ( * TriangleStrip . ToStripIndexMap . Find ( HostTri [ 0 ] ) ,
* TriangleStrip . ToStripIndexMap . Find ( HostTri [ 1 ] ) ,
* TriangleStrip . ToStripIndexMap . Find ( HostTri [ 2 ] ) ) ;
const FVector2d StripTriPos [ 3 ] = { TriangleStrip . StripVertexBuffer [ StripTri [ 0 ] ] ,
TriangleStrip . StripVertexBuffer [ StripTri [ 1 ] ] ,
TriangleStrip . StripVertexBuffer [ StripTri [ 2 ] ] } ;
return Barycentric [ 0 ] * StripTriPos [ 0 ] + Barycentric [ 1 ] * StripTriPos [ 1 ] + Barycentric [ 2 ] * StripTriPos [ 2 ] ;
}
} ;
// translate the end of the trace edge to a position in the unfolded triangle strip
const FVector2d TraceStartVertex = To2dStripPosition ( StartSurfacePoint ) ;
const FVector2d TraceEndVertex = To2dStripPosition ( EndSurfacePoint ) ;
const FVector2d TraceVector = TraceEndVertex - TraceStartVertex ;
// loop over the two-d versions of the host edges the path crosses and find the intersection.
for ( int32 HostEID : HostEdgesCrossed )
{
const FIndex2i EdgeV = HostMesh . GetEdgeV ( HostEID ) ;
const int32 * StripA = TriangleStrip . ToStripIndexMap . Find ( EdgeV . A ) ;
const int32 * StripB = TriangleStrip . ToStripIndexMap . Find ( EdgeV . B ) ;
checkSlow ( StripA ) ; checkSlow ( StripB ) ;
const FIndex2i StripEdgeV ( * StripA , * StripB ) ;
const FVector2d StripVertA = TriangleStrip . StripVertexBuffer [ StripEdgeV . A ] ;
const FVector2d StripVertB = TriangleStrip . StripVertexBuffer [ StripEdgeV . B ] ;
// solve Alpha*StripVertA + (1-Alpha)StripVertB = Gamma Start + (1-Gamma)End.
// i.e.
// Alpha * (StripVertA - StripVertB) + Gamma * (End - Start) = End - StripVertB.
// here End - Start = TraceVector
// write this as 2x2 matrix problem M.x = b solving for unknown vector x=(alpha, gamma).
// matrix
double m [ 2 ] [ 2 ] ;
m [ 0 ] [ 0 ] = ( StripVertA - StripVertB ) [ 0 ] ; m [ 0 ] [ 1 ] = TraceVector [ 0 ] ;
m [ 1 ] [ 0 ] = ( StripVertA - StripVertB ) [ 1 ] ; m [ 1 ] [ 1 ] = TraceVector [ 1 ] ;
// b-vector
const FVector2d b = TraceEndVertex - StripVertB ;
const double Det = m [ 0 ] [ 0 ] * m [ 1 ] [ 1 ] - m [ 0 ] [ 1 ] * m [ 1 ] [ 0 ] ;
// inverse: not yet scaled by 1/det
double invm [ 2 ] [ 2 ] ;
invm [ 0 ] [ 0 ] = m [ 1 ] [ 1 ] ; invm [ 0 ] [ 1 ] = - m [ 0 ] [ 1 ] ;
invm [ 1 ] [ 0 ] = - m [ 1 ] [ 0 ] ; invm [ 1 ] [ 1 ] = m [ 0 ] [ 0 ] ;
// solve for alpha only ( don't care about gamma )
double alpha = invm [ 0 ] [ 0 ] * b [ 0 ] + invm [ 0 ] [ 1 ] * b [ 1 ] ;
if ( FMath : : Abs ( Det ) < TMathUtilConstants < double > : : ZeroTolerance )
{
// this surface edge and the intrinsic edge that intersects it are nearly parallel.
// Assume the crossing happens at the farther vertex.
const double dA = StripVertA . SquaredLength ( ) ;
const double dB = StripVertB . SquaredLength ( ) ;
alpha = ( dA > dB ) ? 1. : 0. ;
}
else
{
alpha = FMath : : Clamp ( alpha / Det , 0. , 1. ) ;
}
// may want to convert this edge crossing to a vertex crossing, if it is close enough.
int32 CoalesceVID = - 1 ;
bool bSnapToVert = false ;
if ( alpha < CoalesceThreshold )
{
CoalesceVID = EdgeV . B ;
bSnapToVert = true ;
}
else if ( ( 1. - alpha ) < CoalesceThreshold )
{
CoalesceVID = EdgeV . A ;
bSnapToVert = true ;
}
if ( bSnapToVert )
{
AddVIDToTrace ( CoalesceVID ) ;
}
else
{
EdgeTrace . Emplace ( HostEID , alpha ) ;
}
}
}
/**
* Utility to trace an edge defined ( by TraceEID ) on the TraceMesh across the HostMesh , given a list of host mesh edges intersected by the trace mesh .
* this assumes the host mesh and trace mesh come from an intrinsic mesh , surface mesh pair .
*/
template < typename HostMeshType , typename TraceMeshType , typename TraceVIDToSurfacePointFtor >
TArray < FSurfacePoint > TraceEdgeOverHost ( const int32 TraceEID , const TArray < int32 > & HostEdgesCrossed , const HostMeshType & HostMesh ,
const TraceMeshType & TraceMesh , double CoalesceThreshold , bool bReverse ,
const TraceVIDToSurfacePointFtor & VIDToSurfacePoint )
2021-11-10 14:22:10 -05:00
{
TArray < FSurfacePoint > EdgeTrace ;
if ( ! TraceMesh . IsEdge ( TraceEID ) )
{
// empty array..
return MoveTemp ( EdgeTrace ) ;
}
2022-05-19 10:43:03 -04:00
EdgeTrace . Reserve ( HostEdgesCrossed . Num ( ) + 2 ) ;
// find start (.A) and end (.B) trace mesh vids for this edge
const FIndex2i OrderedTraceEdgeV = [ & ]
{
// by convention the intrinsic edge direction will be defined by the first adj triangle
const int32 AdjTID = TraceMesh . GetEdgeT ( TraceEID ) . A ;
const int32 IndexOf = TraceMesh . GetTriEdges ( AdjTID ) . IndexOf ( TraceEID ) ;
const FIndex3i TriVIDs = TraceMesh . GetTriangle ( AdjTID ) ;
return FIndex2i ( TriVIDs [ IndexOf ] , TriVIDs [ ( IndexOf + 1 ) % 3 ] ) ;
} ( ) ;
// translate to points on the surface of the host mesh
const FSurfacePoint TraceStartSurfacePoint = VIDToSurfacePoint ( OrderedTraceEdgeV . A ) ;
const FSurfacePoint TraceEndSurfacePoint = VIDToSurfacePoint ( OrderedTraceEdgeV . B ) ;
// add the first point in the trace.
EdgeTrace . Add ( TraceStartSurfacePoint ) ;
// compute (and add) the crossing locations relative to the edges of the host mesh
ConvertEdgesCrossed ( TraceStartSurfacePoint , TraceEndSurfacePoint , HostEdgesCrossed , HostMesh , TraceMesh , CoalesceThreshold , VIDToSurfacePoint , EdgeTrace ) ;
// maybe add end point, if it doesn't duplicate the last one already
2021-11-10 14:22:10 -05:00
{
2022-05-19 10:43:03 -04:00
const FSurfacePoint & LastPoint = EdgeTrace . Last ( ) ;
const bool bSameAsLast = IsVertexPoint ( TraceEndSurfacePoint )
& & IsVertexPoint ( LastPoint )
& & ( LastPoint . Position . VertexPosition . VID = = TraceEndSurfacePoint . Position . VertexPosition . VID ) ;
if ( ! bSameAsLast )
2021-11-10 14:22:10 -05:00
{
2022-05-19 10:43:03 -04:00
EdgeTrace . Add ( TraceEndSurfacePoint ) ;
2021-11-10 14:22:10 -05:00
}
}
// correct trace results order so it is either EdgeV order or reversed as requested
2022-05-19 10:43:03 -04:00
const bool bHasEdgeVOrder = ( TraceMesh . GetEdgeV ( TraceEID ) . A = = OrderedTraceEdgeV . A ) ;
2021-11-10 14:22:10 -05:00
const bool bNeedToReverse = ( bHasEdgeVOrder & & bReverse ) | | ( ! bHasEdgeVOrder & & ! bReverse ) ;
if ( bNeedToReverse )
{
Algo : : Reverse ( EdgeTrace ) ;
}
return MoveTemp ( EdgeTrace ) ;
}
} ;
2021-09-08 11:15:58 -04:00
2022-05-19 10:43:03 -04:00
namespace FNormalCoordIntrinsicTraceImpl
{
using namespace IntrinsicCorrespondenceUtils ;
template < typename IntrinsicMeshType >
TArray < FSurfacePoint > TraceEdge ( const IntrinsicMeshType & IntrinsicMesh , int32 IntrinsicEID , double CoalesceThreshold , bool bReverse )
{
using FEdgeAndCrossingIdx = FNormalCoordinates : : FEdgeAndCrossingIdx ;
TArray < FSurfacePoint > Result ;
if ( ! IntrinsicMesh . IsEdge ( IntrinsicEID ) )
{
return MoveTemp ( Result ) ;
}
const FNormalCoordinates & NormalCoordinates = IntrinsicMesh . GetNormalCoordinates ( ) ;
TArray < int32 > SurfXings ;
const int32 NumSurfXings = NormalCoordinates . NumEdgeCrossing ( IntrinsicEID ) ;
if ( NumSurfXings > 0 )
{
SurfXings . Reserve ( NumSurfXings ) ;
}
const FDynamicMesh3 & SurfaceMesh = * IntrinsicMesh . GetExtrinsicMesh ( ) ;
const FIndex2i IntrinsicEdgeT = IntrinsicMesh . GetEdgeT ( IntrinsicEID ) ;
// need to create a list of surface mesh edges that cross this edge. To do this we need to follow
// each curve that crosses this intrinsic edge to the vertices where it terminates ( thus identifying the surface edge )
for ( int32 p = 1 ; p < NumSurfXings + 1 ; + + p )
{
const FIndex2i SurfaceEdgeV = [ & ]
{
TArray < FEdgeAndCrossingIdx > Crossings ;
FIndex2i Verts ;
// follow surface curve (i.e. surface edge) forward to the end and identify the vertex
{
Crossings . Add ( FEdgeAndCrossingIdx ( { IntrinsicEdgeT . A , IntrinsicEID , p } ) ) ;
FNormalCoordSurfaceTraceImpl : : ContinueTraceSurfaceEdgeAcrossFaces ( IntrinsicMesh , Crossings ) ;
const FEdgeAndCrossingIdx & LastXing = Crossings . Last ( ) ;
const FIndex3i TriEIDs = IntrinsicMesh . GetTriEdges ( LastXing . TID ) ;
Verts . A = IntrinsicMesh . GetTriangle ( LastXing . TID ) [ LastXing . EID ] ;
}
Crossings . Reset ( ) ;
// follow surface curve (i.e. surface edge) backward to other end and identify the vertex
{
Crossings . Add ( FEdgeAndCrossingIdx ( { IntrinsicEdgeT . B , IntrinsicEID , NumSurfXings + 1 - p } ) ) ;
FNormalCoordSurfaceTraceImpl : : ContinueTraceSurfaceEdgeAcrossFaces ( IntrinsicMesh , Crossings ) ;
const FEdgeAndCrossingIdx & LastXing = Crossings . Last ( ) ;
const FIndex3i TriEIDs = IntrinsicMesh . GetTriEdges ( LastXing . TID ) ;
Verts . B = IntrinsicMesh . GetTriangle ( LastXing . TID ) [ LastXing . EID ] ;
}
return Verts ;
} ( ) ;
// identify the surface edge from its endpoints.
const int32 SurfaceEID = SurfaceMesh . FindEdge ( SurfaceEdgeV . A , SurfaceEdgeV . B ) ;
checkSlow ( SurfaceEID ! = IndexConstants : : InvalidID ) ;
SurfXings . Add ( SurfaceEID ) ;
}
// do the actual trace - this identifies the surface mesh triangles crossed by the intrinsic edge and unfolds them into a triangle strip where the trace is performed
Result = FNormalCoordSurfaceTraceImpl : : TraceEdgeOverHost ( IntrinsicEID , SurfXings , SurfaceMesh , IntrinsicMesh , CoalesceThreshold , bReverse ,
[ & IntrinsicMesh ] ( int32 VID ) { return IntrinsicMesh . GetVertexSurfacePoint ( VID ) ; } ) ;
return MoveTemp ( Result ) ;
}
}
2021-09-08 11:15:58 -04:00
int32 UE : : Geometry : : FlipToDelaunay ( FIntrinsicTriangulation & IntrinsicMesh , TSet < int32 > & Uncorrected , const int32 MaxFlipCount )
{
return FlipToDelaunayImpl ( IntrinsicMesh , Uncorrected , MaxFlipCount ) ;
}
2021-11-10 14:22:10 -05:00
int32 UE : : Geometry : : FlipToDelaunay ( FSimpleIntrinsicEdgeFlipMesh & IntrinsicMesh , TSet < int32 > & Uncorrected , const int32 MaxFlipCount )
{
return FlipToDelaunayImpl ( IntrinsicMesh , Uncorrected , MaxFlipCount ) ;
}
2022-05-19 10:43:03 -04:00
int32 UE : : Geometry : : FlipToDelaunay ( FSimpleIntrinsicMesh & IntrinsicMesh , TSet < int32 > & Uncorrected , const int32 MaxFlipCount )
{
return FlipToDelaunayImpl ( IntrinsicMesh , Uncorrected , MaxFlipCount ) ;
}
int32 UE : : Geometry : : FlipToDelaunay ( FIntrinsicMesh & IntrinsicMesh , TSet < int32 > & Uncorrected , const int32 MaxFlipCount )
{
return FlipToDelaunayImpl ( IntrinsicMesh , Uncorrected , MaxFlipCount ) ;
}
2021-09-08 11:15:58 -04:00
int32 UE : : Geometry : : FlipToDelaunay ( FIntrinsicEdgeFlipMesh & IntrinsicMesh , TSet < int32 > & Uncorrected , const int32 MaxFlipCount )
{
return FlipToDelaunayImpl ( IntrinsicMesh , Uncorrected , MaxFlipCount ) ;
}
/**------------------------------------------------------------------------------
2021-11-10 14:22:10 -05:00
* FSimpleIntrinsicEdgeFlipMesh Methods
2021-09-08 11:15:58 -04:00
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2022-05-24 11:10:33 -04:00
FSimpleIntrinsicEdgeFlipMesh : : FSimpleIntrinsicEdgeFlipMesh ( const FDynamicMesh3 & SrcMesh )
2021-09-08 11:15:58 -04:00
{
2022-05-24 11:10:33 -04:00
Reset ( SrcMesh ) ;
}
void FSimpleIntrinsicEdgeFlipMesh : : Clear ( )
{
Vertices . Clear ( ) ;
VertexRefCounts . Clear ( ) ;
VertexEdgeLists . Reset ( ) ;
Triangles . Clear ( ) ;
TriangleRefCounts . Clear ( ) ;
TriangleEdges . Clear ( ) ;
Edges . Clear ( ) ;
EdgeRefCounts . Clear ( ) ;
EdgeLengths . Clear ( ) ;
InternalAngles . Clear ( ) ;
}
void FSimpleIntrinsicEdgeFlipMesh : : Reset ( const FDynamicMesh3 & SrcMesh )
{
Clear ( ) ;
Vertices = SrcMesh . GetVerticesBuffer ( ) ;
VertexRefCounts = SrcMesh . GetVerticesRefCounts ( ) ;
VertexEdgeLists = SrcMesh . GetVertexEdges ( ) ;
Triangles = SrcMesh . GetTrianglesBuffer ( ) ;
TriangleRefCounts = SrcMesh . GetTrianglesRefCounts ( ) ;
TriangleEdges = SrcMesh . GetTriangleEdges ( ) ;
Edges = SrcMesh . GetEdgesBuffer ( ) ;
EdgeRefCounts = SrcMesh . GetEdgesRefCounts ( ) ;
2021-09-08 11:15:58 -04:00
const int32 MaxEID = MaxEdgeID ( ) ;
EdgeLengths . SetNum ( MaxEID ) ;
for ( int32 EID = 0 ; EID < MaxEID ; + + EID )
{
if ( ! IsEdge ( EID ) )
{
continue ;
}
const FIndex2i EdgeV = GetEdgeV ( EID ) ;
const FVector3d Pos [ 2 ] = { GetVertex ( EdgeV . A ) , GetVertex ( EdgeV . B ) } ;
EdgeLengths [ EID ] = ( Pos [ 1 ] - Pos [ 0 ] ) . Length ( ) ;
}
const int32 MaxTriID = MaxTriangleID ( ) ;
InternalAngles . SetNum ( MaxTriID ) ;
for ( int32 TID = 0 ; TID < MaxTriID ; + + TID )
{
if ( ! IsTriangle ( TID ) )
{
continue ;
}
// angles at v0, v1, v2, in that order
InternalAngles [ TID ] = ComputeTriInternalAnglesR ( TID ) ;
}
}
2021-11-10 14:22:10 -05:00
FIndex2i FSimpleIntrinsicEdgeFlipMesh : : GetEdgeOpposingV ( int32 EID ) const
2021-09-08 11:15:58 -04:00
{
const FEdge & Edge = Edges [ EID ] ;
FIndex2i Result ( InvalidID , InvalidID ) ;
for ( int32 i = 0 ; i < 2 ; + + i )
{
int32 TriID = Edge . Tri [ i ] ;
if ( TriID = = InvalidID ) continue ;
const FIndex3i TriEIDs = GetTriEdges ( TriID ) ;
2021-11-10 14:22:10 -05:00
const int32 IndexOf = TriEIDs . IndexOf ( EID ) ;
2021-09-08 11:15:58 -04:00
const FIndex3i TriVIDs = GetTriangle ( TriID ) ;
Result [ i ] = TriVIDs [ AddTwoModThree [ IndexOf ] ] ;
}
return Result ;
}
2021-11-10 14:22:10 -05:00
FIndex2i FSimpleIntrinsicEdgeFlipMesh : : GetOrientedEdgeV ( int32 EID , int32 TID ) const
2021-09-08 11:15:58 -04:00
{
int32 IndexOf = GetTriEdges ( TID ) . IndexOf ( EID ) ;
checkSlow ( IndexOf ! = InvalidID ) ;
FIndex3i TriVIDs = GetTriangle ( TID ) ;
return FIndex2i ( TriVIDs [ IndexOf ] , TriVIDs [ AddOneModThree [ IndexOf ] ] ) ;
}
2021-11-10 14:22:10 -05:00
int32 FSimpleIntrinsicEdgeFlipMesh : : ReplaceEdgeTriangle ( int32 eID , int32 tOld , int32 tNew )
2021-09-08 11:15:58 -04:00
{
FIndex2i & Tris = Edges [ eID ] . Tri ;
int32 a = Tris [ 0 ] , b = Tris [ 1 ] ;
if ( a = = tOld ) {
if ( tNew = = InvalidID )
{
Tris [ 0 ] = b ;
Tris [ 1 ] = InvalidID ;
}
else
{
Tris [ 0 ] = tNew ;
}
return 0 ;
}
else if ( b = = tOld )
{
Tris [ 1 ] = tNew ;
return 1 ;
}
else
{
return - 1 ;
}
}
2021-11-10 14:22:10 -05:00
EMeshResult FSimpleIntrinsicEdgeFlipMesh : : FlipEdgeTopology ( int32 eab , FEdgeFlipInfo & FlipInfo )
2021-09-08 11:15:58 -04:00
{
if ( ! IsEdge ( eab ) )
{
return EMeshResult : : Failed_NotAnEdge ;
}
if ( IsBoundaryEdge ( eab ) )
{
return EMeshResult : : Failed_IsBoundaryEdge ;
}
// find oriented edge [a,b], tris t0,t1, and other verts c in t0, d in t1
const FEdge Edge = Edges [ eab ] ;
int32 t0 = Edge . Tri [ 0 ] , t1 = Edge . Tri [ 1 ] ;
FIndex2i oppV = GetEdgeOpposingV ( eab ) ;
FIndex2i orientedV = GetOrientedEdgeV ( eab , t0 ) ;
int32 a = orientedV . A , b = orientedV . B ;
if ( oppV [ 0 ] = = InvalidID | | oppV [ 1 ] = = InvalidID )
{
return EMeshResult : : Failed_BrokenTopology ;
}
int32 c = oppV [ 0 ] ;
int32 d = oppV [ 1 ] ;
const FIndex3i T0te = GetTriEdges ( t0 ) ;
const FIndex3i T1te = GetTriEdges ( t1 ) ;
const int32 T0IndexOf = T0te . IndexOf ( eab ) ;
const int32 T1IndexOf = T1te . IndexOf ( eab ) ;
// find edges bc, ca, ad, db
const int32 ebc = T0te [ AddOneModThree [ T0IndexOf ] ] ;
const int32 eca = T0te [ AddTwoModThree [ T0IndexOf ] ] ;
2024-02-07 10:10:37 -05:00
int32 ead = T1te [ AddOneModThree [ T1IndexOf ] ] ;
int32 edb = T1te [ AddTwoModThree [ T1IndexOf ] ] ;
if ( ! FlipInfo . bHadSameOrientations )
{
Swap ( ead , edb ) ;
}
2021-09-08 11:15:58 -04:00
// update triangles
Triangles [ t0 ] = FIndex3i ( c , d , b ) ;
Triangles [ t1 ] = FIndex3i ( d , c , a ) ;
// update edge AB, which becomes flipped edge CD
2024-02-07 10:10:37 -05:00
SetEdgeVerticesInternal ( eab , c , d ) ; // Edge.Vert = (c, d) or (d, c)
SetEdgeTrianglesInternal ( eab , t0 , t1 ) ; // Edge.Tri = (t0, t1)
2021-09-08 11:15:58 -04:00
const int32 ecd = eab ;
// update the two other edges whose triangle nbrs have changed
if ( ReplaceEdgeTriangle ( eca , t0 , t1 ) = = - 1 )
{
2021-11-10 14:22:10 -05:00
checkfSlow ( false , TEXT ( " FSimpleIntrinsicEdgeFlipMesh.FlipEdge: first ReplaceEdgeTriangle failed " ) ) ;
2021-09-08 11:15:58 -04:00
return EMeshResult : : Failed_UnrecoverableError ;
}
if ( ReplaceEdgeTriangle ( edb , t1 , t0 ) = = - 1 )
{
2021-11-10 14:22:10 -05:00
checkfSlow ( false , TEXT ( " FSimpleIntrinsicEdgeFlipMesh.FlipEdge: second ReplaceEdgeTriangle failed " ) ) ;
2021-09-08 11:15:58 -04:00
return EMeshResult : : Failed_UnrecoverableError ;
}
// update triangle nbr lists (these are edges)
TriangleEdges [ t0 ] = FIndex3i ( ecd , edb , ebc ) ;
TriangleEdges [ t1 ] = FIndex3i ( ecd , eca , ead ) ;
// remove old eab from verts a and b, and Decrement ref counts
if ( VertexEdgeLists . Remove ( a , eab ) = = false )
{
2021-11-10 14:22:10 -05:00
checkfSlow ( false , TEXT ( " FSimpleIntrinsicEdgeFlipMesh.FlipEdge: first edge list remove failed " ) ) ;
2021-09-08 11:15:58 -04:00
return EMeshResult : : Failed_UnrecoverableError ;
}
VertexRefCounts . Decrement ( a ) ;
if ( a ! = b )
{
if ( VertexEdgeLists . Remove ( b , eab ) = = false )
{
2021-11-10 14:22:10 -05:00
checkfSlow ( false , TEXT ( " FSimpleIntrinsicEdgeFlipMesh.FlipEdge: second edge list remove failed " ) ) ;
2021-09-08 11:15:58 -04:00
return EMeshResult : : Failed_UnrecoverableError ;
}
VertexRefCounts . Decrement ( b ) ;
}
if ( IsVertex ( a ) = = false | | IsVertex ( b ) = = false )
{
2021-11-10 14:22:10 -05:00
checkfSlow ( false , TEXT ( " FSimpleIntrinsicEdgeFlipMesh.FlipEdge: either a or b is not a vertex? " ) ) ;
2021-09-08 11:15:58 -04:00
return EMeshResult : : Failed_UnrecoverableError ;
}
// add edge ecd to verts c and d, and increment ref counts
VertexEdgeLists . Insert ( c , ecd ) ;
VertexRefCounts . Increment ( c ) ;
if ( c ! = d )
{
VertexEdgeLists . Insert ( d , ecd ) ;
VertexRefCounts . Increment ( d ) ;
}
// success! collect up results
FlipInfo . EdgeID = eab ;
FlipInfo . OriginalVerts = FIndex2i ( a , b ) ;
FlipInfo . OpposingVerts = FIndex2i ( c , d ) ;
FlipInfo . Triangles = FIndex2i ( t0 , t1 ) ;
return EMeshResult : : Ok ;
}
2021-11-10 14:22:10 -05:00
FVector3d FSimpleIntrinsicEdgeFlipMesh : : ComputeTriInternalAnglesR ( const int32 TID ) const
2021-09-08 11:15:58 -04:00
{
const FVector3d Lengths = GetTriEdgeLengths ( TID ) ;
FVector3d Angles ;
for ( int32 v = 0 ; v < 3 ; + + v )
{
Angles [ v ] = InteriorAngle ( Lengths [ v ] , Lengths [ AddOneModThree [ v ] ] , Lengths [ AddTwoModThree [ v ] ] ) ;
}
return Angles ;
}
2021-11-10 14:22:10 -05:00
double FSimpleIntrinsicEdgeFlipMesh : : GetOpposingVerticesDistance ( int32 EID ) const
2021-09-08 11:15:58 -04:00
{
2024-02-07 10:10:37 -05:00
const bool bHasSameOrientation = AreSameOrientation ( EID ) ;
2021-11-10 14:22:10 -05:00
const double OrgLength = GetEdgeLength ( EID ) ;
2021-09-08 11:15:58 -04:00
const FIndex2i EdgeTris = GetEdgeT ( EID ) ;
checkSlow ( EdgeTris [ 1 ] ! = FDynamicMesh3 : : InvalidID ) ;
// compute 2d locations of the verts opposite the EID edge.
FVector2d Opp2dVerts [ 2 ] ;
for ( int32 i = 0 ; i < 2 ; + + i )
{
const FIndex3i TriEIDs = GetTriEdges ( EdgeTris [ i ] ) ;
const FVector3d Ls = GetEdgeLengthTriple ( TriEIDs ) ;
const int32 IndexOf = TriEIDs . IndexOf ( EID ) ;
const FVector3d PermutedLs = Permute ( IndexOf , Ls ) ;
Opp2dVerts [ i ] = ComputeOpposingVert2d ( PermutedLs [ 0 ] , PermutedLs [ 1 ] , PermutedLs [ 2 ] ) ;
}
// rotate second tri so the shared edge aligns
Opp2dVerts [ 1 ] . Y = - Opp2dVerts [ 1 ] . Y ;
2024-02-07 10:10:37 -05:00
if ( bHasSameOrientation )
{
Opp2dVerts [ 1 ] . X = OrgLength - Opp2dVerts [ 1 ] . X ;
}
2021-09-08 11:15:58 -04:00
return ( Opp2dVerts [ 0 ] - Opp2dVerts [ 1 ] ) . Length ( ) ;
}
2021-11-10 14:22:10 -05:00
EMeshResult FSimpleIntrinsicEdgeFlipMesh : : FlipEdge ( int32 EID , FEdgeFlipInfo & EdgeFlipInfo )
2021-09-08 11:15:58 -04:00
{
if ( ! IsEdge ( EID ) )
{
return EMeshResult : : Failed_NotAnEdge ;
}
if ( IsBoundaryEdge ( EID ) )
{
return EMeshResult : : Failed_IsBoundaryEdge ;
}
2024-02-07 10:10:37 -05:00
const bool bHasSameOrientation = AreSameOrientation ( EID ) ;
EdgeFlipInfo . bHadSameOrientations = bHasSameOrientation ;
2021-09-08 11:15:58 -04:00
// prohibit case where the edge is shared by a non-convex pair of triangles.
{
// original triangles
FDynamicMesh3 : : FEdge OrgEdge = GetEdge ( EID ) ;
2024-02-07 10:10:37 -05:00
// total interior angles at the edge vertices as ordered by the first triangle.
2021-09-08 11:15:58 -04:00
double TotalAngleAtOrgVert [ 2 ] = { 0. , 0. } ;
2024-02-07 10:10:37 -05:00
{
// accumulate interior angles at edge vertices in first triangle.
{
const int32 TriID = OrgEdge . Tri [ 0 ] ;
const int32 IndexOf = GetTriEdges ( TriID ) . IndexOf ( EID ) ;
TotalAngleAtOrgVert [ 0 ] + = InternalAngles [ TriID ] [ IndexOf ] ;
TotalAngleAtOrgVert [ 1 ] + = InternalAngles [ TriID ] [ AddOneModThree [ IndexOf ] ] ;
}
// accumulate interior angles at the edge vertices in the second triangle
{
const int32 TriID = OrgEdge . Tri [ 1 ] ;
const int32 IndexOf = GetTriEdges ( TriID ) . IndexOf ( EID ) ;
if ( bHasSameOrientation )
{
TotalAngleAtOrgVert [ 1 ] + = InternalAngles [ TriID ] [ IndexOf ] ;
TotalAngleAtOrgVert [ 0 ] + = InternalAngles [ TriID ] [ AddOneModThree [ IndexOf ] ] ;
}
else
{
TotalAngleAtOrgVert [ 0 ] + = InternalAngles [ TriID ] [ IndexOf ] ;
TotalAngleAtOrgVert [ 1 ] + = InternalAngles [ TriID ] [ AddOneModThree [ IndexOf ] ] ;
}
}
2021-09-08 11:15:58 -04:00
}
if ( TotalAngleAtOrgVert [ 0 ] > TMathUtilConstants < double > : : Pi - TMathUtilConstants < double > : : ZeroTolerance | | TotalAngleAtOrgVert [ 1 ] > TMathUtilConstants < double > : : Pi - TMathUtilConstants < double > : : ZeroTolerance )
{
return EMeshResult : : Failed_Unsupported ;
}
}
2022-05-24 11:10:33 -04:00
// prohibit case where one of the ends of the edge has degree one (this looks like a triangle rolled into a cone ).
{
FIndex2i EdgeT = GetEdgeT ( EID ) ;
if ( EdgeT . A = = EdgeT . B )
{
return EMeshResult : : Failed_Unsupported ;
}
}
2021-09-08 11:15:58 -04:00
// compute the length of edge after flip
const double PostFlipLength = GetOpposingVerticesDistance ( EID ) ;
// flip edge in the underlying mesh
const EMeshResult MeshFlipResult = FlipEdgeTopology ( EID , EdgeFlipInfo ) ;
if ( MeshFlipResult ! = EMeshResult : : Ok )
{
// could fail for many reasons, e.g. a boundary edge or the new edge already exists.
return MeshFlipResult ;
}
// update the intrinsic edge length
EdgeLengths [ EID ] = PostFlipLength ;
// update interior angles for the tris
for ( int32 t = 0 ; t < 2 ; + + t )
{
const int32 TID = EdgeFlipInfo . Triangles [ t ] ;
InternalAngles [ TID ] = ComputeTriInternalAnglesR ( TID ) ;
}
return MeshFlipResult ;
}
2021-11-10 14:22:10 -05:00
FVector2d FSimpleIntrinsicEdgeFlipMesh : : EdgeOpposingAngles ( int32 EID ) const
2021-09-08 11:15:58 -04:00
{
FVector2d Result ;
FDynamicMesh3 : : FEdge Edge = GetEdge ( EID ) ;
{
2021-11-10 14:22:10 -05:00
const int32 IndexOf = GetTriEdges ( Edge . Tri . A ) . IndexOf ( EID ) ;
2021-09-08 11:15:58 -04:00
const int32 IndexOfOpp = AddTwoModThree [ IndexOf ] ;
2021-11-10 14:22:10 -05:00
const double Angle = GetTriInternalAngleR ( Edge . Tri . A , IndexOfOpp ) ;
2021-09-08 11:15:58 -04:00
Result [ 0 ] = Angle ;
}
if ( Edge . Tri . B ! = FDynamicMesh3 : : InvalidID )
{
2021-11-10 14:22:10 -05:00
const int32 IndexOf = GetTriEdges ( Edge . Tri . B ) . IndexOf ( EID ) ;
2021-09-08 11:15:58 -04:00
const int32 IndexOfOpp = AddTwoModThree [ IndexOf ] ;
2021-11-10 14:22:10 -05:00
const double Angle = GetTriInternalAngleR ( Edge . Tri . B , IndexOfOpp ) ;
2021-09-08 11:15:58 -04:00
Result [ 1 ] = Angle ;
}
else
{
Result [ 1 ] = - TMathUtilConstants < double > : : MaxReal ;
}
return Result ;
}
2021-11-10 14:22:10 -05:00
double FSimpleIntrinsicEdgeFlipMesh : : EdgeCotanWeight ( int32 EID ) const
2021-09-08 11:15:58 -04:00
{
auto ComputeCotanOppAngle = [ this ] ( int32 EID , int32 TID ) - > double
{
const FIndex3i TriEIDs = GetTriEdges ( TID ) ;
2021-11-10 14:22:10 -05:00
const int32 IOf = TriEIDs . IndexOf ( EID ) ;
const FVector3d TriLs = GetEdgeLengthTriple ( TriEIDs ) ;
2021-09-08 11:15:58 -04:00
const FVector3d Ls ( TriLs [ IOf ] , TriLs [ AddOneModThree [ IOf ] ] , TriLs [ AddTwoModThree [ IOf ] ] ) ;
return ComputeCotangent ( Ls [ 0 ] , Ls [ 1 ] , Ls [ 2 ] ) ;
} ;
FDynamicMesh3 : : FEdge Edge = GetEdge ( EID ) ;
double Result = ComputeCotanOppAngle ( EID , Edge . Tri . A ) ;
if ( Edge . Tri . B ! = FDynamicMesh3 : : InvalidID )
{
Result + = ComputeCotanOppAngle ( EID , Edge . Tri . B ) ;
Result / = 2 ;
}
return Result ;
}
2022-05-19 10:43:03 -04:00
/**------------------------------------------------------------------------------
* FSimpleIntrinsicMesh Methods
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
UE : : Geometry : : EMeshResult UE : : Geometry : : FSimpleIntrinsicMesh : : PokeTriangleTopology ( int32 TriangleID , FPokeTriangleInfo & PokeResult )
{
PokeResult = FPokeTriangleInfo ( ) ;
if ( ! IsTriangle ( TriangleID ) )
{
return EMeshResult : : Failed_NotATriangle ;
}
FIndex3i tv = GetTriangle ( TriangleID ) ;
FIndex3i te = GetTriEdges ( TriangleID ) ;
// create vertex with averaged position..
FVector3d vPos = ( 1. / 3. ) * ( GetVertex ( tv [ 0 ] ) + GetVertex ( tv [ 1 ] ) + GetVertex ( tv [ 2 ] ) ) ;
int32 newVertID = AppendVertex ( vPos ) ;
// add in edges to center vtx, do not connect to triangles yet
int32 eAN = AddEdgeInternal ( tv [ 0 ] , newVertID , - 1 , - 1 ) ;
int32 eBN = AddEdgeInternal ( tv [ 1 ] , newVertID , - 1 , - 1 ) ;
int32 eCN = AddEdgeInternal ( tv [ 2 ] , newVertID , - 1 , - 1 ) ;
VertexRefCounts . Increment ( tv [ 0 ] ) ;
VertexRefCounts . Increment ( tv [ 1 ] ) ;
VertexRefCounts . Increment ( tv [ 2 ] ) ;
VertexRefCounts . Increment ( newVertID , 3 ) ;
// old triangle becomes tri along first edge
Triangles [ TriangleID ] = FIndex3i ( tv [ 0 ] , tv [ 1 ] , newVertID ) ;
TriangleEdges [ TriangleID ] = FIndex3i ( te [ 0 ] , eBN , eAN ) ;
// add two triangles
int32 t1 = AddTriangleInternal ( tv [ 1 ] , tv [ 2 ] , newVertID , te [ 1 ] , eCN , eBN ) ;
int32 t2 = AddTriangleInternal ( tv [ 2 ] , tv [ 0 ] , newVertID , te [ 2 ] , eAN , eCN ) ;
// second and third edges of original tri have neighbors
ReplaceEdgeTriangle ( te [ 1 ] , TriangleID , t1 ) ;
ReplaceEdgeTriangle ( te [ 2 ] , TriangleID , t2 ) ;
// set the triangles for the edges we created above
SetEdgeTrianglesInternal ( eAN , TriangleID , t2 ) ;
SetEdgeTrianglesInternal ( eBN , TriangleID , t1 ) ;
SetEdgeTrianglesInternal ( eCN , t1 , t2 ) ;
PokeResult . OriginalTriangle = TriangleID ;
PokeResult . TriVertices = tv ;
PokeResult . NewVertex = newVertID ;
PokeResult . NewTriangles = FIndex2i ( t1 , t2 ) ;
PokeResult . NewEdges = FIndex3i ( eAN , eBN , eCN ) ;
PokeResult . BaryCoords = FVector3d ( 1. / 3. , 1. / 3. , 1. / 3. ) ;
return EMeshResult : : Ok ;
}
UE : : Geometry : : EMeshResult UE : : Geometry : : FSimpleIntrinsicMesh : : SplitEdgeTopology ( int32 eab , FEdgeSplitInfo & SplitInfo )
{
SplitInfo = FEdgeSplitInfo ( ) ;
if ( ! IsEdge ( eab ) )
{
return EMeshResult : : Failed_NotAnEdge ;
}
// look up primary edge & triangle
const FEdge Edge = Edges [ eab ] ;
const int32 t0 = Edge . Tri [ 0 ] ;
if ( t0 = = InvalidID )
{
return EMeshResult : : Failed_BrokenTopology ;
}
const FIndex3i T0tv = GetTriangle ( t0 ) ;
const FIndex3i T0te = GetTriEdges ( t0 ) ;
const int32 IndexOfe = T0te . IndexOf ( eab ) ;
const int32 a = T0tv [ IndexOfe ] ;
const int32 b = T0tv [ AddOneModThree [ IndexOfe ] ] ;
const int32 c = T0tv [ AddTwoModThree [ IndexOfe ] ] ;
// look up edge bc, which needs to be modified
const int32 ebc = T0te [ AddOneModThree [ IndexOfe ] ] ;
// RefCount overflow check. Conservatively leave room for
// extra increments from other operations.
if ( VertexRefCounts . GetRawRefCount ( c ) > FRefCountVector : : INVALID_REF_COUNT - 3 )
{
return EMeshResult : : Failed_HitValenceLimit ;
}
SplitInfo . OriginalEdge = eab ;
SplitInfo . OriginalVertices = FIndex2i ( a , b ) ; // this is the oriented a,b
SplitInfo . OriginalTriangles = FIndex2i ( t0 , InvalidID ) ;
SplitInfo . SplitT = 0.5 ;
// quite a bit of code is duplicated between boundary and non-boundary case, but it
// is too hard to follow later if we factor it out...
if ( IsBoundaryEdge ( eab ) )
{
// create vertex
const FVector3d vNew = 0.5 * ( GetVertex ( a ) + GetVertex ( b ) ) ;
const int32 f = AppendVertex ( vNew ) ;
// rewrite existing triangle
Triangles [ t0 ] [ AddOneModThree [ IndexOfe ] ] = f ;
// add second triangle
const int32 t2 = AddTriangleInternal ( f , b , c , InvalidID , InvalidID , InvalidID ) ;
// rewrite edge bc, create edge af
ReplaceEdgeTriangle ( ebc , t0 , t2 ) ;
const int32 eaf = eab ;
Edges [ eaf ] . Vert = FIndex2i ( FMath : : Min ( a , f ) , FMath : : Max ( a , f ) ) ;
//ReplaceEdgeVertex(eaf, b, f);
if ( a ! = b )
{
VertexEdgeLists . Remove ( b , eab ) ;
}
VertexEdgeLists . Insert ( f , eaf ) ;
// create edges fb and fc
const int32 efb = AddEdgeInternal ( f , b , t2 ) ;
const int32 efc = AddEdgeInternal ( f , c , t0 , t2 ) ;
// update triangle edge-nbrs
ReplaceTriangleEdge ( t0 , ebc , efc ) ;
TriangleEdges [ t2 ] = FIndex3i ( efb , ebc , efc ) ;
// update vertex refcounts
VertexRefCounts . Increment ( c ) ;
VertexRefCounts . Increment ( f , 2 ) ;
SplitInfo . bIsBoundary = true ;
SplitInfo . OtherVertices = FIndex2i ( c , InvalidID ) ;
SplitInfo . NewVertex = f ;
SplitInfo . NewEdges = FIndex3i ( efb , efc , InvalidID ) ;
SplitInfo . NewTriangles = FIndex2i ( t2 , InvalidID ) ;
return EMeshResult : : Ok ;
}
else // interior triangle branch
{
// look up other triangle
const int32 t1 = Edges [ eab ] . Tri [ 1 ] ;
SplitInfo . OriginalTriangles . B = t1 ;
const FIndex3i T1tv = GetTriangle ( t1 ) ;
const FIndex3i T1te = GetTriEdges ( t1 ) ;
const int32 T1IndexOfe = T1te . IndexOf ( eab ) ;
checkSlow ( T1tv [ T1IndexOfe ] = = b ) ;
const int32 d = T1tv [ AddTwoModThree [ T1IndexOfe ] ] ;
const int32 edb = T1te [ AddTwoModThree [ T1IndexOfe ] ] ;
// RefCount overflow check. Conservatively leave room for
// extra increments from other operations.
if ( VertexRefCounts . GetRawRefCount ( d ) > FRefCountVector : : INVALID_REF_COUNT - 3 )
{
return EMeshResult : : Failed_HitValenceLimit ;
}
// create vertex
FVector3d vNew = 0.5 * ( GetVertex ( a ) + GetVertex ( b ) ) ;
int32 f = AppendVertex ( vNew ) ;
// rewrite existing triangles, replacing b with f
Triangles [ t0 ] [ AddOneModThree [ IndexOfe ] ] = f ;
Triangles [ t1 ] [ T1IndexOfe ] = f ;
// add two triangles to close holes we just created
int32 t2 = AddTriangleInternal ( f , b , c , InvalidID , InvalidID , InvalidID ) ;
int32 t3 = AddTriangleInternal ( f , d , b , InvalidID , InvalidID , InvalidID ) ;
// update the edges we found above, to point to triangles
ReplaceEdgeTriangle ( ebc , t0 , t2 ) ;
ReplaceEdgeTriangle ( edb , t1 , t3 ) ;
// edge eab became eaf
int32 eaf = eab ; //Edge * eAF = eAB;
Edges [ eaf ] . Vert = FIndex2i ( FMath : : Min ( a , f ) , FMath : : Max ( a , f ) ) ;
// update a/b/f vertex-edges
if ( a ! = b )
{
VertexEdgeLists . Remove ( b , eab ) ;
}
VertexEdgeLists . Insert ( f , eaf ) ;
// create edges connected to f (also updates vertex-edges)
int32 efb = AddEdgeInternal ( f , b , t2 , t3 ) ;
int32 efc = AddEdgeInternal ( f , c , t0 , t2 ) ;
int32 edf = AddEdgeInternal ( d , f , t1 , t3 ) ;
// update triangle edge-nbrs
ReplaceTriangleEdge ( t0 , ebc , efc ) ;
ReplaceTriangleEdge ( t1 , edb , edf ) ;
TriangleEdges [ t2 ] = FIndex3i ( efb , ebc , efc ) ;
TriangleEdges [ t3 ] = FIndex3i ( edf , edb , efb ) ;
// update vertex refcounts
VertexRefCounts . Increment ( c ) ;
VertexRefCounts . Increment ( d ) ;
VertexRefCounts . Increment ( f , 4 ) ;
SplitInfo . bIsBoundary = false ;
SplitInfo . OtherVertices = FIndex2i ( c , d ) ;
SplitInfo . NewVertex = f ;
SplitInfo . NewEdges = FIndex3i ( efb , efc , edf ) ;
SplitInfo . NewTriangles = FIndex2i ( t2 , t3 ) ;
return EMeshResult : : Ok ;
}
}
UE : : Geometry : : EMeshResult UE : : Geometry : : FSimpleIntrinsicMesh : : PokeTriangle ( int32 TID , const FVector3d & BaryCoordinates , FPokeTriangleInfo & PokeInfo )
{
if ( ! IsTriangle ( TID ) )
{
return EMeshResult : : Failed_NotATriangle ;
}
// state before the poke
const FIndex3i OriginalVIDs = GetTriangle ( TID ) ;
const FIndex3i OriginalEdges = GetTriEdges ( TID ) ;
const FVector3d OriginalTriEdgeLengths = GetTriEdgeLengths ( TID ) ;
// Add a new vertex and faces to the IntrinsicMesh.
// Note: the r3 position will be wrong initially since this poke will just interpolate the corners of the tri.
// we fix this position as the last step in this function
EMeshResult PokeResult = PokeTriangleTopology ( TID , PokeInfo ) ;
if ( PokeResult ! = EMeshResult : : Ok )
{
return PokeResult ;
}
FIndex3i NewTris ( TID , PokeInfo . NewTriangles [ 0 ] , PokeInfo . NewTriangles [ 1 ] ) ;
const int32 NewVID = PokeInfo . NewVertex ;
// Need to update intrinsic information for the 3 triangles that resulted from the poke
// 1) update the edge lengths for the triangles
// 2) update the internal angles for the triangles
// (1) compute intrinsic edge lengths for the 3 new edges
{
FVector3d DistancesFromOldCorner ;
for ( int32 v = 0 ; v < 3 ; + + v )
{
FVector3d BaryCoordVertex ( 0. , 0. , 0. ) ; BaryCoordVertex [ v ] = 1. ;
const double Distance = DistanceBetweenBarycentricPoints ( OriginalTriEdgeLengths [ 0 ] ,
OriginalTriEdgeLengths [ 1 ] ,
OriginalTriEdgeLengths [ 2 ] ,
BaryCoordVertex , BaryCoordinates ) ;
DistancesFromOldCorner [ v ] = Distance ;
}
{
// original tri is and verts (a, b,c) and edges (ab, bc, ca)
// the updated tri has verts (a, b, new) and edges (ab, bnew, newa)
FIndex3i T0EIDs = GetTriEdges ( NewTris [ 0 ] ) ;
EdgeLengths . InsertAt ( DistancesFromOldCorner [ 1 ] , T0EIDs [ 1 ] ) ;
EdgeLengths . InsertAt ( DistancesFromOldCorner [ 0 ] , T0EIDs [ 2 ] ) ;
// new tri[0] has verts (b, c, new) and edges (bc, cnew, newb)
FIndex3i T1EIDs = GetTriEdges ( PokeInfo . NewTriangles [ 0 ] ) ;
EdgeLengths . InsertAt ( DistancesFromOldCorner [ 2 ] , T1EIDs [ 1 ] ) ;
}
}
// (2) update internal angles for the triangles.
{
InternalAngles . InsertAt ( ComputeTriInternalAnglesR ( NewTris [ 2 ] ) , NewTris [ 2 ] ) ;
InternalAngles . InsertAt ( ComputeTriInternalAnglesR ( NewTris [ 1 ] ) , NewTris [ 1 ] ) ;
InternalAngles [ NewTris [ 0 ] ] = ComputeTriInternalAnglesR ( NewTris [ 0 ] ) ;
}
// record the coordinate actually used.
PokeInfo . BaryCoords = BaryCoordinates ;
return EMeshResult : : Ok ;
}
UE : : Geometry : : EMeshResult UE : : Geometry : : FSimpleIntrinsicMesh : : SplitEdge ( int32 EdgeAB , FEdgeSplitInfo & SplitInfo , double SplitParameterT )
{
SplitParameterT = FMath : : Clamp ( SplitParameterT , 0. , 1. ) ;
if ( ! IsEdge ( EdgeAB ) )
{
return EMeshResult : : Failed_NotAnEdge ;
}
const FEdge OriginalEdge = GetEdge ( EdgeAB ) ;
if ( IsBoundaryEdge ( EdgeAB ) )
{
// state before the split
const int32 TID = OriginalEdge . Tri [ 0 ] ;
const int32 IndexOfe = GetTriEdges ( TID ) . IndexOf ( EdgeAB ) ;
// Info about the original T0 tri, reordered to make the split edge the first edge..
const FVector3d OriginalT0EdgeLengths = Permute ( IndexOfe , GetTriEdgeLengths ( TID ) ) ; // as (|ab|, |bc|, |ca|)
// Update the connectivity with the edge split
EMeshResult Result = SplitEdgeTopology ( EdgeAB , SplitInfo ) ;
if ( Result ! = EMeshResult : : Ok )
{
return Result ;
}
const int32 NewVID = SplitInfo . NewVertex ;
const int32 NewTID = SplitInfo . NewTriangles [ 0 ] ; // edges {fb, bc, cf}
const int32 NewEdgeFB = SplitInfo . NewEdges [ 0 ] ;
const int32 NewEdgeFC = SplitInfo . NewEdges [ 1 ] ;
const double DistAF = SplitParameterT * OriginalT0EdgeLengths [ 0 ] ;
const double DistFB = FMath : : Max ( 0. , OriginalT0EdgeLengths [ 0 ] - DistAF ) ;
// new edge length
const double DistFC = DistanceBetweenBarycentricPoints ( OriginalT0EdgeLengths [ 0 ] ,
OriginalT0EdgeLengths [ 1 ] ,
OriginalT0EdgeLengths [ 2 ] ,
FVector3d ( SplitParameterT , 1. - SplitParameterT , 0. ) ,
FVector3d ( 0. , 0. , 1. ) ) ;
EdgeLengths . InsertAt ( DistFC , NewEdgeFC ) ;
EdgeLengths . InsertAt ( DistFB , NewEdgeFB ) ;
EdgeLengths [ EdgeAB ] = DistAF ;
// update the internal angles
InternalAngles . InsertAt ( ComputeTriInternalAnglesR ( NewTID ) , NewTID ) ;
InternalAngles [ TID ] = ComputeTriInternalAnglesR ( TID ) ;
return Result ;
}
else
{
// state before the split
const int32 TID = OriginalEdge . Tri [ 0 ] ;
const int32 IndexOfe = GetTriEdges ( TID ) . IndexOf ( EdgeAB ) ;
// Info about the original T0 tri, reordered to make the split edge the first edge..
const FVector3d OriginalT0EdgeLengths = Permute ( IndexOfe , GetTriEdgeLengths ( TID ) ) ; // as (|ab|, |bc|, |ca|)
// Info about the original T1 tri, reordered to make the split edge the first edge..
const int32 TID1 = OriginalEdge . Tri [ 1 ] ;
const int32 IndexOfT1e = GetTriEdges ( TID1 ) . IndexOf ( EdgeAB ) ;
const FVector3d OriginalT1EdgeLengths = Permute ( IndexOfT1e , GetTriEdgeLengths ( TID1 ) ) ; // as (|ba|, |ad|, |db})
// Update the connectivity with the edge split
EMeshResult Result = SplitEdgeTopology ( EdgeAB , SplitInfo ) ;
if ( Result ! = EMeshResult : : Ok )
{
return Result ;
}
const int32 NewVID = SplitInfo . NewVertex ;
const int32 NewTID0 = SplitInfo . NewTriangles [ 0 ] ; // edges {fb, bc, cf}
const int32 NewTID1 = SplitInfo . NewTriangles [ 1 ] ; // edges {fd, db, bc}
const int32 EdgeAF = EdgeAB ;
const int32 NewEdgeFB = SplitInfo . NewEdges [ 0 ] ;
const int32 NewEdgeFC = SplitInfo . NewEdges [ 1 ] ;
const int32 NewEdgeFD = SplitInfo . NewEdges [ 2 ] ;
const double DistAF = SplitParameterT * OriginalT0EdgeLengths [ 0 ] ;
const double DistFB = FMath : : Max ( 0. , OriginalT0EdgeLengths [ 0 ] - DistAF ) ;
// new edge length
const double DistFC = DistanceBetweenBarycentricPoints ( OriginalT0EdgeLengths [ 0 ] ,
OriginalT0EdgeLengths [ 1 ] ,
OriginalT0EdgeLengths [ 2 ] ,
FVector3d ( SplitParameterT , 1. - SplitParameterT , 0. ) ,
FVector3d ( 0. , 0. , 1. ) ) ;
// new edge length
const double DistFD = DistanceBetweenBarycentricPoints ( OriginalT1EdgeLengths [ 0 ] ,
OriginalT1EdgeLengths [ 1 ] ,
OriginalT1EdgeLengths [ 2 ] ,
FVector3d ( 1. - SplitParameterT , SplitParameterT , 0. ) ,
FVector3d ( 0. , 0. , 1. ) ) ;
EdgeLengths . InsertAt ( DistFD , NewEdgeFD ) ;
EdgeLengths . InsertAt ( DistFC , NewEdgeFC ) ;
EdgeLengths . InsertAt ( DistFB , NewEdgeFB ) ;
EdgeLengths [ EdgeAF ] = DistAF ;
// update the internal angles (this uses edge lengths)
InternalAngles . InsertAt ( ComputeTriInternalAnglesR ( NewTID1 ) , NewTID1 ) ;
InternalAngles . InsertAt ( ComputeTriInternalAnglesR ( NewTID0 ) , NewTID0 ) ;
InternalAngles [ TID ] = ComputeTriInternalAnglesR ( TID ) ;
InternalAngles [ TID1 ] = ComputeTriInternalAnglesR ( TID1 ) ;
return Result ;
}
}
2021-09-08 11:15:58 -04:00
/**------------------------------------------------------------------------------
* FIntrinsicTriangulation Methods
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
UE : : Geometry : : FIntrinsicTriangulation : : FIntrinsicTriangulation ( const FDynamicMesh3 & SrcMesh )
: MyBase ( SrcMesh )
2021-11-10 14:22:10 -05:00
, SignpostData ( SrcMesh )
2021-09-08 11:15:58 -04:00
{
}
2021-10-14 12:02:29 -04:00
TArray < FIntrinsicTriangulation : : FSurfacePoint > UE : : Geometry : : FIntrinsicTriangulation : : TraceEdge ( int32 EID , double CoalesceThreshold , bool bReverse ) const
{
TArray < FIntrinsicTriangulation : : FSurfacePoint > SurfacePoints ;
if ( ! IsEdge ( EID ) )
{
return SurfacePoints ;
}
const FDynamicMesh3 * SurfaceMesh = GetExtrinsicMesh ( ) ;
const double IntrinsicEdgeLength = GetEdgeLength ( EID ) ;
const FIndex2i EdgeV = GetEdgeV ( EID ) ;
int32 StartVID = EdgeV . A ;
int32 EndVID = EdgeV . B ;
if ( bReverse )
{
StartVID = EdgeV . B ;
EndVID = EdgeV . A ;
}
FIndex2i AdjTIDs = GetEdgeT ( EID ) ;
// look-up the polar angle of this edge as it leaves StartVID ( this angle is relative to a specified reference mesh edge )
const double PolarAngle =
[ & ] {
const int32 AdjTID = AdjTIDs . A ;
const int32 IndexOf = GetTriEdges ( AdjTID ) . IndexOf ( EID ) ;
checkSlow ( IndexOf > - 1 ) ;
if ( GetTriangle ( AdjTID ) [ IndexOf ] = = StartVID )
{
// IntrinsicEdgeAngles hold the local angle of each out-going edge relative to the vertex the edge exits
2021-11-10 14:22:10 -05:00
return SignpostData . IntrinsicEdgeAngles [ AdjTID ] [ IndexOf ] ;
2021-10-14 12:02:29 -04:00
}
else
{
2021-11-10 14:22:10 -05:00
const double ToRadians = SignpostData . GeometricVertexInfo [ StartVID ] . ToRadians ;
2021-10-14 12:02:29 -04:00
// polar angle of prev edge just before (clockwise) the edge we want
2021-11-10 14:22:10 -05:00
const double PrevPolarAngle = SignpostData . IntrinsicEdgeAngles [ AdjTID ] [ ( IndexOf + 1 ) % 3 ] ;
2021-10-14 12:02:29 -04:00
// angle between previous edge and this one
const double InternalAngle = InternalAngles [ AdjTID ] [ ( IndexOf + 1 ) % 3 ] ;
// add the internal angle to rotate from Prev Edge to this edge
return ToRadians * InternalAngle + PrevPolarAngle ;
}
} ( ) ;
// Trace the surface mesh from the intrinsic StartVID, in the PolarAngle direction, a distance of IntrinsicEdgeLength.
//
2021-11-10 14:22:10 -05:00
// Note: this intrinsic vertex may or may not correspond to a vertex in the surface mesh if vertices were added to the intrinsic mesh
2021-10-14 12:02:29 -04:00
// by doing an edge split or a triangle poke.
2021-11-10 14:22:10 -05:00
FMeshGeodesicSurfaceTracer SurfaceTracer = SignpostSufaceTraceUtil : : TraceFromIntrinsicVert ( SignpostData , StartVID , PolarAngle , IntrinsicEdgeLength ) ;
2021-10-14 12:02:29 -04:00
// util to convert the trace result to a surface point, potentially snapping to a vertex if within the coalesce threshold
auto TraceResultToSurfacePoint = [ CoalesceThreshold , SurfaceMesh ] ( const FMeshGeodesicSurfaceTracer : : FTraceResult & TraceResult ) - > FSurfacePoint
{
if ( TraceResult . bIsEdgePoint )
{
// TraceResult alpha is defined as EdgeV.A * (1-Alpha) + EdgeV.B Alpha; This is the complement of what we want
const double Alpha = FMath : : Clamp ( ( 1. - TraceResult . EdgeAlpha ) , 0. , 1. ) ;
const int32 EID = TraceResult . EdgeID ;
if ( Alpha < = 0.5 & & Alpha < CoalesceThreshold )
{
return FSurfacePoint ( SurfaceMesh - > GetEdgeV ( EID ) . B ) ;
}
else if ( Alpha > 0.5 & & ( 1. - Alpha ) < CoalesceThreshold )
{
return FSurfacePoint ( SurfaceMesh - > GetEdgeV ( EID ) . A ) ;
}
return FSurfacePoint ( EID , Alpha ) ;
}
else
{
// TODO - should snap to vertex / edge if close?
const int32 TID = TraceResult . TriID ;
const FVector3d BC = TraceResult . Barycentric ;
return FSurfacePoint ( TID , BC ) ;
}
} ;
// Add surface point to the outgoing array, but don't allow for duplicate vertex points
auto AddSurfacePoint = [ & SurfacePoints ] ( const FSurfacePoint & PointA )
{
if ( SurfacePoints . Num ( ) = = 0 )
{
SurfacePoints . Add ( PointA ) ;
}
else
{
const FSurfacePoint & PointB = SurfacePoints . Last ( ) ;
const bool bAreSameVertexPoint = ( PointA . PositionType = = FSurfacePoint : : EPositionType : : Vertex & &
PointB . PositionType = = FSurfacePoint : : EPositionType : : Vertex & &
PointA . Position . VertexPosition . VID = = PointB . Position . VertexPosition . VID ) ;
if ( ! bAreSameVertexPoint )
{
SurfacePoints . Add ( PointA ) ;
}
}
} ;
// package the surface trace results as series of surface points. Note, because this is a trace along an intrinsic edge
// the first and last result will be an intrinsic vertex (StartVID and EndVID)
TArray < FMeshGeodesicSurfaceTracer : : FTraceResult > & TraceResults = SurfaceTracer . GetTraceResults ( ) ;
{
int32 NumTraceResults = TraceResults . Num ( ) ;
AddSurfacePoint ( GetVertexSurfacePoint ( StartVID ) ) ;
for ( int32 i = 1 ; i < NumTraceResults - 1 ; + + i )
{
FMeshGeodesicSurfaceTracer : : FTraceResult & TraceResult = TraceResults [ i ] ;
FSurfacePoint SurfacePoint = TraceResultToSurfacePoint ( TraceResult ) ;
AddSurfacePoint ( SurfacePoint ) ;
}
AddSurfacePoint ( GetVertexSurfacePoint ( EndVID ) ) ;
}
return SurfacePoints ;
}
2021-09-08 11:15:58 -04:00
EMeshResult UE : : Geometry : : FIntrinsicTriangulation : : FlipEdge ( int32 EID , FEdgeFlipInfo & EdgeFlipInfo )
{
if ( IsBoundaryEdge ( EID ) )
{
return EMeshResult : : Failed_IsBoundaryEdge ;
}
// capture the state of the triangles before the flip.
2021-11-10 14:22:10 -05:00
const FIndex2i Tris = GetEdgeT ( EID ) ;
const FIndex2i PreFlipIndexOf ( GetTriEdges ( Tris [ 0 ] ) . IndexOf ( EID ) , GetTriEdges ( Tris [ 1 ] ) . IndexOf ( EID ) ) ;
2021-09-08 11:15:58 -04:00
// flip edge in the underlying mesh
// this updates the edge lengths and the interior angles.
const EMeshResult MeshFlipResult = MyBase : : FlipEdge ( EID , EdgeFlipInfo ) ;
if ( MeshFlipResult ! = EMeshResult : : Ok )
{
// could fail for many reasons, e.g. a boundary edge
return MeshFlipResult ;
}
2021-11-10 14:22:10 -05:00
// internal angles at the verts that are now connected by the flipped edge
const double NewAngleAtC = InternalAngles [ Tris [ 1 ] ] [ 1 ] ;
const double NewAngleAtD = InternalAngles [ Tris [ 0 ] ] [ 1 ] ;
2021-09-08 11:15:58 -04:00
2021-11-10 14:22:10 -05:00
// update signpost
SignpostData . OnFlipEdge ( EID , Tris , EdgeFlipInfo . OpposingVerts , PreFlipIndexOf , NewAngleAtC , NewAngleAtD ) ;
2021-09-08 11:15:58 -04:00
return EMeshResult : : Ok ;
}
double UE : : Geometry : : FIntrinsicTriangulation : : UpdateVertexByEdgeTrace ( const int32 NewVID , const int32 TraceStartVID , const double TracePolarAngle , const double TraceDist )
{
2021-11-10 14:22:10 -05:00
using FSurfaceTraceResult = FSignpost : : FSurfaceTraceResult ;
2021-09-08 11:15:58 -04:00
2021-11-10 14:22:10 -05:00
FMeshGeodesicSurfaceTracer SurfaceTracer = SignpostSufaceTraceUtil : : TraceFromIntrinsicVert ( SignpostData , TraceStartVID , TracePolarAngle , TraceDist ) ;
2021-09-08 11:15:58 -04:00
2021-11-10 14:22:10 -05:00
TArray < FMeshGeodesicSurfaceTracer : : FTraceResult > & TraceResultArray = SurfaceTracer . GetTraceResults ( ) ;
// need to convert the result of the trace into the correct form
2021-09-08 11:15:58 -04:00
2021-11-10 14:22:10 -05:00
const FMeshGeodesicSurfaceTracer : : FTraceResult & TraceResult = TraceResultArray . Last ( ) ;
const FSurfacePoint TraceResultPosition ( TraceResult . TriID , TraceResult . Barycentric ) ;
2021-09-08 11:15:58 -04:00
// fix directions relative to local reference edge on the extrinsic mesh
// by finding direction of the TraceEID edge indecent on NewVID
2021-11-10 14:22:10 -05:00
const double AngleOffset = [ & ]
2021-09-08 11:15:58 -04:00
{
FVector2d Dir = TraceResult . SurfaceDirection . Dir ;
// translate to Dir about the reference edge for this triangle
2021-11-10 14:22:10 -05:00
const int32 EndRefEID = SignpostData . TIDToReferenceEID [ TraceResult . TriID ] ;
2021-09-08 11:15:58 -04:00
const FMeshGeodesicSurfaceTracer : : FTangentTri2 & TangentTri2 = SurfaceTracer . GetLastTri ( ) ;
// convert to local basis for first edge of tri2
if ( TangentTri2 . EdgeOrientationSign [ 0 ] = = - 1 )
{
Dir = - Dir ;
}
const int32 IndexOfEndRefEID = TangentTri2 . PermutedTriEIDs . IndexOf ( EndRefEID ) ;
FVector2d DirRelToRefEID = TangentTri2 . ChangeBasis ( Dir , IndexOfEndRefEID ) ;
if ( TangentTri2 . EdgeOrientationSign [ IndexOfEndRefEID ] = = - 1 )
{
DirRelToRefEID = - DirRelToRefEID ;
}
// angle of (new edge) path to NewVert relative to Ref edge
const double AngleToNewVert = FMath : : Atan2 ( DirRelToRefEID . Y , DirRelToRefEID . X ) ;
// reverse direction of path because we NewVert is the local origin for polar angles around new vert
const double AngleFromNewVert = AngleToNewVert + TMathUtilConstants < double > : : Pi ;
2021-11-10 14:22:10 -05:00
return AsZeroToTwoPi ( AngleFromNewVert ) ;
} ( ) ;
2021-09-08 11:15:58 -04:00
2021-11-10 14:22:10 -05:00
// Trace result as position and angle.
FSurfaceTraceResult SurfaceTraceResult = { TraceResultPosition , AngleOffset } ;
// As R3
bool bTmpIsValid ;
const FVector3d TraceResultPos = AsR3Position ( SurfaceTraceResult . SurfacePoint , * SignpostData . SurfaceMesh , bTmpIsValid ) ;
// update the R3 for NewVID in the intrinsic mesh
Vertices [ NewVID ] = TraceResultPos ;
// store the surface position for the intrinsic vertex in the signpost data
// Currently we are storing every new intrinsic surface position as a point on a surface tri
// TODO: is there any advantage to classify as "Edge" position if very close to edge ?
SignpostData . IntrinsicVertexPositions . InsertAt ( SurfaceTraceResult . SurfacePoint , NewVID ) ;
// return relative angle of trace
return SurfaceTraceResult . Angle ;
2021-09-08 11:15:58 -04:00
}
UE : : Geometry : : EMeshResult UE : : Geometry : : FIntrinsicTriangulation : : PokeTriangle ( int32 TID , const FVector3d & BaryCoordinates , FPokeTriangleInfo & PokeInfo )
{
if ( ! IsTriangle ( TID ) )
{
return EMeshResult : : Failed_NotATriangle ;
}
// state before the poke
const FIndex3i OriginalVIDs = GetTriangle ( TID ) ;
2021-11-10 14:22:10 -05:00
const FVector3d OriginalEdgeDirs = SignpostData . IntrinsicEdgeAngles [ TID ] ;
2021-09-08 11:15:58 -04:00
// Add a new vertex and faces to the IntrinsicMesh.
2022-05-19 10:43:03 -04:00
// this operation will update the internal angles and the edge lengths, but the actual R3 positon
// of the vertex will need to be updated as will any signpost data.
EMeshResult PokeResult = MyBase : : PokeTriangle ( TID , BaryCoordinates , PokeInfo ) ;
2021-09-08 11:15:58 -04:00
if ( PokeResult ! = EMeshResult : : Ok )
{
return PokeResult ;
}
FIndex3i NewTris ( TID , PokeInfo . NewTriangles [ 0 ] , PokeInfo . NewTriangles [ 1 ] ) ;
const int32 NewVID = PokeInfo . NewVertex ;
// Need to update intrinsic information for the 3 triangles that resulted from the poke
2022-05-19 10:43:03 -04:00
// 1) update Signpost data ( the edge directions for the triangles )
// 2) update the surface point for the new vertex by tracing one of the new edges.
2021-09-08 11:15:58 -04:00
// also update the edge directions leaving the new vertex to be relative to the direction defined on the extrinsic mesh
2022-05-19 10:43:03 -04:00
// (1) update the edge directions
2021-09-08 11:15:58 -04:00
FVector3d TriEdgeDir [ 3 ] ; // one for each tri in order (TID, NewTris[0], NewTris[1] )
2022-05-19 10:43:03 -04:00
{
// edges around the boundary of the original tri
TriEdgeDir [ 0 ] [ 0 ] = OriginalEdgeDirs [ 0 ] ; // AtoB dir
TriEdgeDir [ 1 ] [ 0 ] = OriginalEdgeDirs [ 1 ] ; // BtoC dir
TriEdgeDir [ 2 ] [ 0 ] = OriginalEdgeDirs [ 2 ] ; // CtoA dir
2021-09-08 11:15:58 -04:00
2022-05-19 10:43:03 -04:00
// edges from the corners of the original tri towards the new vertex at the "center"
const double ToRadiansAtA = SignpostData . GeometricVertexInfo [ OriginalVIDs [ 0 ] ] . ToRadians ;
const double ToRadiansAtB = SignpostData . GeometricVertexInfo [ OriginalVIDs [ 1 ] ] . ToRadians ;
const double ToRadiansAtC = SignpostData . GeometricVertexInfo [ OriginalVIDs [ 2 ] ] . ToRadians ;
TriEdgeDir [ 0 ] [ 1 ] = AsZeroToTwoPi ( OriginalEdgeDirs [ 1 ] + ToRadiansAtB * InternalAngles [ NewTris [ 1 ] ] [ 0 ] ) ; // BtoNew dir
TriEdgeDir [ 1 ] [ 1 ] = AsZeroToTwoPi ( OriginalEdgeDirs [ 2 ] + ToRadiansAtC * InternalAngles [ NewTris [ 2 ] ] [ 0 ] ) ; // CtoNew dir
TriEdgeDir [ 2 ] [ 1 ] = AsZeroToTwoPi ( OriginalEdgeDirs [ 0 ] + ToRadiansAtA * InternalAngles [ NewTris [ 0 ] ] [ 0 ] ) ; // AtoNew dir
2021-09-08 11:15:58 -04:00
2022-05-19 10:43:03 -04:00
// edges from new vertex to a, to b, and to c, using new-to-A as the zero direction. These will be updated later when we learn the correct angle for new-to-A
TriEdgeDir [ 0 ] [ 2 ] = 0. ; // NewToA dir
TriEdgeDir [ 1 ] [ 2 ] = InternalAngles [ NewTris [ 0 ] ] [ 2 ] ; // NewToB dir
TriEdgeDir [ 2 ] [ 2 ] = InternalAngles [ NewTris [ 0 ] ] [ 2 ] + InternalAngles [ NewTris [ 1 ] ] [ 2 ] ; // NewToC dir
}
2021-11-10 14:22:10 -05:00
SignpostData . GeometricVertexInfo . InsertAt ( FSignpost : : FGeometricInfo ( ) , NewVID ) ; // we want the default of false, and 1.
2021-09-08 11:15:58 -04:00
2022-05-19 10:43:03 -04:00
// (2) update the surface point for the new vertex by tracing one of the new edges.
2021-09-08 11:15:58 -04:00
//
// --- fix the r3 position of the new vertex and compute its SurfacePoint Attributes by doing a trace on the Extrinsic Mesh along one of the edges incident on NewVID
// Use first incident edge and find the distance and direction (angle) to trace
// Q: would picking the shortest of the new edges be better than just the first?
const int32 AtoNewEID = PokeInfo . NewEdges [ 0 ] ; // a-to-new edge
const double AtoNewDist = EdgeLengths [ AtoNewEID ] ;
// trace from A vertex in the direction toward the new vertex the edge length distance. This updates the surface position entry for the new vert.
const int32 AVID = OriginalVIDs [ 0 ] ;
const double AtoNewDir = TriEdgeDir [ 2 ] [ 1 ] ;
const double NewToAAngle = UpdateVertexByEdgeTrace ( NewVID , AVID , AtoNewDir , AtoNewDist ) ;
// update the Newto{A,B,C} directions such that NewToA has the angle LocalEIDAngle.
for ( int32 e = 0 ; e < 3 ; + + e )
{
TriEdgeDir [ e ] [ 2 ] = AsZeroToTwoPi ( TriEdgeDir [ e ] [ 2 ] + NewToAAngle ) ;
}
// record the new edge directions.
2021-11-10 14:22:10 -05:00
SignpostData . IntrinsicEdgeAngles . InsertAt ( TriEdgeDir [ 2 ] , NewTris [ 2 ] ) ;
SignpostData . IntrinsicEdgeAngles . InsertAt ( TriEdgeDir [ 1 ] , NewTris [ 1 ] ) ;
SignpostData . IntrinsicEdgeAngles [ NewTris [ 0 ] ] = TriEdgeDir [ 0 ] ;
2021-09-08 11:15:58 -04:00
// record the coordinate actually used.
PokeInfo . BaryCoords = BaryCoordinates ;
return EMeshResult : : Ok ;
}
UE : : Geometry : : EMeshResult UE : : Geometry : : FIntrinsicTriangulation : : SplitEdge ( int32 EdgeAB , FEdgeSplitInfo & SplitInfo , double SplitParameterT )
{
SplitParameterT = FMath : : Clamp ( SplitParameterT , 0. , 1. ) ;
if ( ! IsEdge ( EdgeAB ) )
{
return EMeshResult : : Failed_NotAnEdge ;
}
const FEdge OriginalEdge = GetEdge ( EdgeAB ) ;
if ( IsBoundaryEdge ( EdgeAB ) )
{
// state before the split
2022-05-19 10:43:03 -04:00
const int32 TID = OriginalEdge . Tri [ 0 ] ;
2021-09-08 11:15:58 -04:00
const int32 IndexOfe = GetTriEdges ( TID ) . IndexOf ( EdgeAB ) ;
// Info about the original T0 tri, reordered to make the split edge the first edge..
2022-05-19 10:43:03 -04:00
const FIndex3i OriginalT0VIDs = Permute ( IndexOfe , GetTriangle ( TID ) ) ; // as (a, b, c)
2021-11-10 14:22:10 -05:00
const FVector3d OriginalT0EdgeDirs = Permute ( IndexOfe , SignpostData . IntrinsicEdgeAngles [ TID ] ) ; // as ( aTob, bToc, cToa)
2022-05-19 10:43:03 -04:00
const FVector3d OriginalT0EdgeLengths = Permute ( IndexOfe , GetTriEdgeLengths ( TID ) ) ; // as (|ab|, |bc|, |ca|)
2021-09-08 11:15:58 -04:00
// Update the connectivity with the edge split
2022-05-19 10:43:03 -04:00
EMeshResult Result = MyBase : : SplitEdge ( EdgeAB , SplitInfo , SplitParameterT ) ;
2021-09-08 11:15:58 -04:00
if ( Result ! = EMeshResult : : Ok )
{
return Result ;
}
const int32 NewVID = SplitInfo . NewVertex ;
const int32 NewTID = SplitInfo . NewTriangles [ 0 ] ; // edges {fb, bc, cf}
const double DistAF = SplitParameterT * OriginalT0EdgeLengths [ 0 ] ;
const FVector3d TIDInternalAngles = Permute ( IndexOfe , InternalAngles [ TID ] ) ;
// update the directions. Say DirFtoB is zero
2021-11-10 14:22:10 -05:00
const double ToRadiansAtC = SignpostData . GeometricVertexInfo [ OriginalT0VIDs [ 2 ] ] . ToRadians ;
SignpostData . GeometricVertexInfo . InsertAt ( FSignpost : : FGeometricInfo ( ) , NewVID ) ; // we want the default of false, and 1.
2021-09-08 11:15:58 -04:00
FVector3d TriEdgeDir [ 2 ] ; // one for each tri in order (TID, NewTID )
// edges around the boundary of the original tri
TriEdgeDir [ 0 ] [ 0 ] = OriginalT0EdgeDirs [ 0 ] ; // AtoF dir
TriEdgeDir [ 1 ] [ 1 ] = OriginalT0EdgeDirs [ 1 ] ; // BtoC dir
TriEdgeDir [ 0 ] [ 2 ] = OriginalT0EdgeDirs [ 2 ] ; // CtoA dir
TriEdgeDir [ 1 ] [ 0 ] = 0. ; // FtoB dir
TriEdgeDir [ 1 ] [ 2 ] = AsZeroToTwoPi ( OriginalT0EdgeDirs [ 2 ] + ToRadiansAtC * TIDInternalAngles [ 2 ] ) ; // CtoF dir
TriEdgeDir [ 0 ] [ 1 ] = AsZeroToTwoPi ( InternalAngles [ NewTID ] [ 0 ] ) ; // FtoC dir relative to FtoB
const double FtoADir = AsZeroToTwoPi ( InternalAngles [ NewTID ] [ 0 ] + TIDInternalAngles [ 1 ] ) ; // FtoA dir relative to FtoB
// update the surface position of the new vertex and the relative directions from it.
// trace from A vertex in the direction toward the new vertex the edge length distance. This updates the surface position entry for the new vert.
const int32 AVID = OriginalT0VIDs [ 0 ] ;
2022-05-19 10:43:03 -04:00
const double AtoFDir = TriEdgeDir [ 0 ] [ 0 ] ;
const double FToAAngle = UpdateVertexByEdgeTrace ( NewVID , AVID , AtoFDir , DistAF ) ;
2021-09-08 11:15:58 -04:00
const double ToLocalDir = ( FToAAngle - FtoADir ) ;
// update the Fto{B,C} directions such that the direction from F to A would agree with FtoAAngle
TriEdgeDir [ 0 ] [ 1 ] = AsZeroToTwoPi ( TriEdgeDir [ 0 ] [ 1 ] + ToLocalDir ) ;
TriEdgeDir [ 1 ] [ 0 ] = AsZeroToTwoPi ( TriEdgeDir [ 1 ] [ 0 ] + ToLocalDir ) ;
// store angle results
2021-11-10 14:22:10 -05:00
SignpostData . IntrinsicEdgeAngles . InsertAt ( TriEdgeDir [ 1 ] , NewTID ) ;
SignpostData . IntrinsicEdgeAngles [ TID ] = Permute ( ( 3 - IndexOfe ) % 3 , TriEdgeDir [ 0 ] ) ;
2021-09-08 11:15:58 -04:00
return Result ;
}
else
{
// state before the split
const int32 TID = OriginalEdge . Tri [ 0 ] ;
const int32 IndexOfe = GetTriEdges ( TID ) . IndexOf ( EdgeAB ) ;
// Info about the original T0 tri, reordered to make the split edge the first edge..
const FIndex3i OriginalT0VIDs = Permute ( IndexOfe , GetTriangle ( TID ) ) ; // as (a, b, c)
2021-11-10 14:22:10 -05:00
const FVector3d OriginalT0EdgeDirs = Permute ( IndexOfe , SignpostData . IntrinsicEdgeAngles [ TID ] ) ; // as ( aTob, bToc, cToa)
2022-05-19 10:43:03 -04:00
const FVector3d OriginalT0EdgeLengths = Permute ( IndexOfe , GetTriEdgeLengths ( TID ) ) ; // as (|ab|, |bc|, |ca|)
2021-09-08 11:15:58 -04:00
// Info about the original T0 tri, reordered to make the split edge the first edge..
const int32 TID1 = OriginalEdge . Tri [ 1 ] ;
const int32 IndexOfT1e = GetTriEdges ( TID1 ) . IndexOf ( EdgeAB ) ;
2022-05-19 10:43:03 -04:00
const FIndex3i OriginalT1VIDs = Permute ( IndexOfT1e , GetTriangle ( TID1 ) ) ; // as (b, a, d)
const FVector3d OriginalT1EdgeDirs = Permute ( IndexOfT1e , SignpostData . IntrinsicEdgeAngles [ TID1 ] ) ; // as (bToa, aTod, dTob)
2021-09-08 11:15:58 -04:00
2022-05-19 10:43:03 -04:00
// Update the connectivity and the intrinsic data with the edge split
EMeshResult Result = MyBase : : SplitEdge ( EdgeAB , SplitInfo , SplitParameterT ) ;
2021-09-08 11:15:58 -04:00
if ( Result ! = EMeshResult : : Ok )
{
return Result ;
}
const int32 NewVID = SplitInfo . NewVertex ;
const int32 NewTID0 = SplitInfo . NewTriangles [ 0 ] ; // edges {fb, bc, cf}
const int32 NewTID1 = SplitInfo . NewTriangles [ 1 ] ; // edges {fd, db, bc}
const double DistAF = SplitParameterT * OriginalT0EdgeLengths [ 0 ] ;
// update the directions. Say DirFtoB is zero
2021-11-10 14:22:10 -05:00
const double ToRadiansAtC = SignpostData . GeometricVertexInfo [ OriginalT0VIDs [ 2 ] ] . ToRadians ;
const double ToRadiansAtD = SignpostData . GeometricVertexInfo [ OriginalT1VIDs [ 2 ] ] . ToRadians ;
SignpostData . GeometricVertexInfo . InsertAt ( FSignpost : : FGeometricInfo ( ) , NewVID ) ; // we want the default of false, and 1.
2021-09-08 11:15:58 -04:00
const FVector3d TIDInternalAngles = Permute ( IndexOfe , InternalAngles [ TID ] ) ;
const FVector3d TID1InternalAngles = Permute ( IndexOfT1e , InternalAngles [ TID1 ] ) ;
FVector3d TriEdgeDir [ 4 ] ; // one for each tri in order (TID, NewTID0, TID1, NewTID1 )
// edges around the boundary of the original tri
TriEdgeDir [ 0 ] [ 0 ] = OriginalT0EdgeDirs [ 0 ] ; // AtoF dir
TriEdgeDir [ 1 ] [ 1 ] = OriginalT0EdgeDirs [ 1 ] ; // BtoC dir
TriEdgeDir [ 0 ] [ 2 ] = OriginalT0EdgeDirs [ 2 ] ; // CtoA dir
TriEdgeDir [ 3 ] [ 2 ] = OriginalT1EdgeDirs [ 0 ] ; // BtoF dir
TriEdgeDir [ 2 ] [ 1 ] = OriginalT1EdgeDirs [ 1 ] ; // AtoD dir
TriEdgeDir [ 3 ] [ 1 ] = OriginalT1EdgeDirs [ 2 ] ; // DtoB dir
TriEdgeDir [ 1 ] [ 2 ] = AsZeroToTwoPi ( OriginalT0EdgeDirs [ 2 ] + ToRadiansAtC * TIDInternalAngles [ 2 ] ) ; // CtoF dir
TriEdgeDir [ 2 ] [ 2 ] = AsZeroToTwoPi ( OriginalT1EdgeDirs [ 2 ] + ToRadiansAtD * InternalAngles [ NewTID1 ] [ 1 ] ) ; // Dtof dir
TriEdgeDir [ 1 ] [ 0 ] = 0. ; // FtoB dir
TriEdgeDir [ 0 ] [ 1 ] = AsZeroToTwoPi ( InternalAngles [ NewTID0 ] [ 0 ] ) ; // FtoC dir relative to FtoB
TriEdgeDir [ 2 ] [ 0 ] = AsZeroToTwoPi ( InternalAngles [ NewTID0 ] [ 0 ] + TIDInternalAngles [ 1 ] ) ; // FtoA dir relative to FtoB
TriEdgeDir [ 3 ] [ 0 ] = AsZeroToTwoPi ( InternalAngles [ NewTID0 ] [ 0 ] + TIDInternalAngles [ 1 ] + TID1InternalAngles [ 0 ] ) ; // FtoD dir relative to FtoB
// trace from A vertex in the direction toward the new vertex the edge length distance. This updates the surface position entry for the new vert.
const int32 AVID = OriginalT0VIDs [ 0 ] ;
const double AtoFDir = TriEdgeDir [ 0 ] [ 0 ] ;
2022-05-19 10:43:03 -04:00
const double FToAAngle = UpdateVertexByEdgeTrace ( NewVID , AVID , AtoFDir , DistAF ) ;
2021-09-08 11:15:58 -04:00
const double ToLocalDir = ( FToAAngle - TriEdgeDir [ 2 ] [ 0 ] ) ;
// fix angles relative to local dir.
TriEdgeDir [ 1 ] [ 0 ] = AsZeroToTwoPi ( ToLocalDir ) ;
TriEdgeDir [ 0 ] [ 1 ] = AsZeroToTwoPi ( TriEdgeDir [ 0 ] [ 1 ] + ToLocalDir ) ;
TriEdgeDir [ 2 ] [ 0 ] = AsZeroToTwoPi ( TriEdgeDir [ 2 ] [ 0 ] + ToLocalDir ) ;
TriEdgeDir [ 3 ] [ 0 ] = AsZeroToTwoPi ( TriEdgeDir [ 3 ] [ 0 ] + ToLocalDir ) ;
// store angle results
2021-11-10 14:22:10 -05:00
SignpostData . IntrinsicEdgeAngles . InsertAt ( TriEdgeDir [ 3 ] , NewTID1 ) ;
SignpostData . IntrinsicEdgeAngles . InsertAt ( TriEdgeDir [ 1 ] , NewTID0 ) ;
SignpostData . IntrinsicEdgeAngles [ TID ] = Permute ( ( 3 - IndexOfe ) % 3 , TriEdgeDir [ 0 ] ) ;
SignpostData . IntrinsicEdgeAngles [ TID1 ] = Permute ( ( 3 - IndexOfT1e ) % 3 , TriEdgeDir [ 2 ] ) ;
2021-09-08 11:15:58 -04:00
return Result ;
}
}
2021-11-10 14:22:10 -05:00
/**------------------------------------------------------------------------------
* FIntrinsicEdgeFlipMesh Methods
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
FIntrinsicEdgeFlipMesh : : FIntrinsicEdgeFlipMesh ( const FDynamicMesh3 & SurfaceMesh )
{
2022-05-24 11:10:33 -04:00
// construct the intrinsic mesh directly from this surface mesh
MyBase : : Reset ( SurfaceMesh ) ;
NormalCoordinates . Reset ( SurfaceMesh ) ;
2021-11-10 14:22:10 -05:00
}
EMeshResult FIntrinsicEdgeFlipMesh : : FlipEdge ( int32 EID , FEdgeFlipInfo & EdgeFlipInfo )
{
if ( ! IsEdge ( EID ) )
{
return EMeshResult : : Failed_NotAnEdge ;
}
if ( IsBoundaryEdge ( EID ) )
{
return EMeshResult : : Failed_IsBoundaryEdge ;
}
// state before flip, needed when updating the normal coords after the flip
const FIndex2i EdgeT = GetEdgeT ( EID ) ;
const FIndex3i TAEIDs = GetTriEdges ( EdgeT . A ) ;
const FIndex3i TBEIDs = GetTriEdges ( EdgeT . B ) ;
const FIndex2i OppVs = GetEdgeOpposingV ( EID ) ;
EMeshResult FlipResult = MyBase : : FlipEdge ( EID , EdgeFlipInfo ) ;
if ( FlipResult = = EMeshResult : : Ok )
{
// update the normal coords
NormalCoordinates . OnFlipEdge ( EdgeT . A , TAEIDs , OppVs . A , EdgeT . B , TBEIDs , OppVs . B , EID ) ;
}
return FlipResult ;
}
2021-11-11 11:47:23 -05:00
TArray < FIntrinsicEdgeFlipMesh : : FSurfacePoint > FIntrinsicEdgeFlipMesh : : TraceEdge ( int32 IntrinsicEID , double CoalesceThreshold , bool bReverse ) const
{
2022-05-19 10:43:03 -04:00
return FNormalCoordIntrinsicTraceImpl : : TraceEdge ( * this , IntrinsicEID , CoalesceThreshold , bReverse ) ;
2021-11-11 11:47:23 -05:00
}
2021-11-10 14:22:10 -05:00
TArray < FIntrinsicEdgeFlipMesh : : FEdgeAndCrossingIdx > FIntrinsicEdgeFlipMesh : : GetImplicitEdgeCrossings ( const int32 SurfaceEID , const bool bReverse ) const
{
return FNormalCoordSurfaceTraceImpl : : TraceSurfaceEdge ( * this , SurfaceEID , bReverse ) ;
}
2022-05-19 10:43:03 -04:00
TArray < FIntrinsicEdgeFlipMesh : : FSurfacePoint >
FIntrinsicEdgeFlipMesh : : TraceSurfaceEdge ( int32 SurfaceEID , double CoalesceThreshold , bool bReverse ) const
2021-11-10 14:22:10 -05:00
{
2022-05-19 10:43:03 -04:00
const FDynamicMesh3 & HostMesh = * this - > GetExtrinsicMesh ( ) ;
const FIntrinsicEdgeFlipMesh & TraceMesh = * this ;
TArray < FEdgeAndCrossingIdx > EdgeAndCrossingIdxs = this - > GetImplicitEdgeCrossings ( SurfaceEID , bReverse ) ;
// NB: for this intrinsic mesh type, we know that the intrinsic verts are the same as the surface verts ( since splits and pokes aren't allowed)
TArray < int32 > HostEdgeCrossings ;
HostEdgeCrossings . Reserve ( EdgeAndCrossingIdxs . Num ( ) - 2 ) ; // EdgeAndCrossingsIdx include the start and end vertex. don't need them.
for ( FEdgeAndCrossingIdx EdgeAndCrossing : EdgeAndCrossingIdxs )
{
if ( EdgeAndCrossing . CIdx ! = 0 ) // skip the start and end vertex
{
HostEdgeCrossings . Add ( EdgeAndCrossing . EID ) ;
}
}
return FNormalCoordSurfaceTraceImpl : : TraceEdgeOverHost ( SurfaceEID , HostEdgeCrossings , HostMesh , TraceMesh , CoalesceThreshold , bReverse ,
[ ] ( int32 VID ) { return FSurfacePoint ( VID ) ; } ) ;
}
/**------------------------------------------------------------------------------
* TEdgeCorrespondence
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
template < typename IntrinsicMeshType >
void UE : : Geometry : : TEdgeCorrespondence < IntrinsicMeshType > : : Setup ( const IntrinsicMeshType & Mesh )
{
SurfaceEdgesCrossed . Reset ( ) ;
IntrinsicMesh = & Mesh ;
SurfaceMesh = Mesh . GetNormalCoordinates ( ) . SurfaceMesh ;
2021-11-10 14:22:10 -05:00
const int32 IntrinsicMaxEID = IntrinsicMesh - > MaxEdgeID ( ) ;
2022-05-19 10:43:03 -04:00
const int32 SurfaceMaxEID = SurfaceMesh - > MaxEdgeID ( ) ;
2021-11-10 14:22:10 -05:00
SurfaceEdgesCrossed . SetNum ( IntrinsicMaxEID ) ;
const IntrinsicCorrespondenceUtils : : FNormalCoordinates & NormalCoords = IntrinsicMesh - > GetNormalCoordinates ( ) ;
// allocate the SurfaceEdgesCrossed. From the normal coordinates we know how many surface edge crossings each intrinsic edge sees
for ( int32 IntrinsicEID = 0 ; IntrinsicEID < IntrinsicMaxEID ; + + IntrinsicEID )
{
if ( ! IntrinsicMesh - > IsEdge ( IntrinsicEID ) )
{
continue ;
}
// number of times a surface edge crosses this intrinsic edge
2022-05-19 10:43:03 -04:00
const int32 NumXings = NormalCoords . NumEdgeCrossing ( IntrinsicEID ) ;
2021-11-10 14:22:10 -05:00
if ( NumXings > 0 )
{
SurfaceEdgesCrossed [ IntrinsicEID ] . SetNum ( NumXings ) ;
} // else don't bother making an entry when NumXings == 0 since that means the edges are the same on both meshes.
}
// trace each surface edge across the intrinsic mesh and construct an ordered list of surface edges crossing each intrinsic edge.
// the order of the crossings should be consistent with the edge direction relative to the first adjacent tri
// (i.e. starting at the corner Mesh.GetTriEdges(GetEdgeT(EID).A).IndexOf(EID) )
for ( int32 SurfaceEID = 0 ; SurfaceEID < SurfaceMaxEID ; + + SurfaceEID )
{
if ( ! SurfaceMesh - > IsEdge ( SurfaceEID ) )
{
continue ;
}
const TArray < FEdgeAndCrossingIdx > IntrinsicEdgeXings = IntrinsicMesh - > GetImplicitEdgeCrossings ( SurfaceEID , false /* = bReverseTrace*/ ) ;
for ( int32 i = 0 ; i < IntrinsicEdgeXings . Num ( ) ; + + i )
{
const FEdgeAndCrossingIdx & EdgeXing = IntrinsicEdgeXings [ i ] ;
2022-05-19 10:43:03 -04:00
bool bIsEndVertex = ( EdgeXing . CIdx = = 0 ) ;
2021-11-10 14:22:10 -05:00
if ( ! bIsEndVertex )
{
2022-05-19 10:43:03 -04:00
const int32 IntrinsicEID = EdgeXing . EID ;
2021-11-10 14:22:10 -05:00
const FIndex2i IntrinsicEdgeT = IntrinsicMesh - > GetEdgeT ( IntrinsicEID ) ;
2021-11-11 11:47:23 -05:00
checkSlow ( IntrinsicEdgeT . A = = EdgeXing . TID | | IntrinsicEdgeT . B = = EdgeXing . TID ) ;
2021-11-10 14:22:10 -05:00
2021-11-11 11:47:23 -05:00
// array of surface edges this intrinsic edge crosses, these should be ordered relative to the direction of TriA.
TArray < int32 > & XingSurfaceEdges = SurfaceEdgesCrossed [ IntrinsicEID ] ;
2021-11-10 14:22:10 -05:00
// crossings count from the bottom of the edge to the top relative to EdgeXing.TID
const int32 XingID = ( IntrinsicEdgeT . A = = EdgeXing . TID ) ? EdgeXing . CIdx - 1 : XingSurfaceEdges . Num ( ) - EdgeXing . CIdx ;
checkSlow ( XingID > - 1 ) ;
XingSurfaceEdges [ XingID ] = SurfaceEID ;
}
}
}
}
2022-05-19 10:43:03 -04:00
template < typename IntrinsicMeshType >
TArray < UE : : Geometry : : IntrinsicCorrespondenceUtils : : FSurfacePoint > TEdgeCorrespondence < IntrinsicMeshType > : : TraceEdge ( int32 IntrinsicEID , double CoalesceThreshold , bool bReverse ) const
2021-11-10 14:22:10 -05:00
{
2022-05-19 10:43:03 -04:00
const FDynamicMesh3 & HostMesh = * SurfaceMesh ;
const IntrinsicMeshType & TraceMesh = * IntrinsicMesh ;
const TArray < int32 > & HostEdgeCrossings = SurfaceEdgesCrossed [ IntrinsicEID ] ;
2021-11-10 14:22:10 -05:00
2022-05-19 10:43:03 -04:00
return FNormalCoordSurfaceTraceImpl : : TraceEdgeOverHost ( IntrinsicEID , HostEdgeCrossings , HostMesh , TraceMesh , CoalesceThreshold , bReverse ,
[ & TraceMesh ] ( int32 VID ) { return TraceMesh . GetVertexSurfacePoint ( VID ) ; } ) ;
}
template struct UE : : Geometry : : TEdgeCorrespondence < UE : : Geometry : : FIntrinsicEdgeFlipMesh > ;
template struct UE : : Geometry : : TEdgeCorrespondence < UE : : Geometry : : FIntrinsicMesh > ;
/**------------------------------------------------------------------------------
* FIntrinsicMesh Helpers
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
namespace FIntrinsicMeshImplUtils
{
using namespace UE : : Geometry ;
/**
* Test if a given SurfacePoint is on the specified surface triangle of the surface mesh , this includes the triangle boundaries :
* i . e . will return true , if the point is on the face of the triangle , one of the edges , or on a vertex of the triangle .
*/
bool IsOnSurfaceTriangle ( const IntrinsicCorrespondenceUtils : : FSurfacePoint & SurfacePoint , const int32 SurfaceTID , const FDynamicMesh3 & SurfaceMesh )
2021-11-10 14:22:10 -05:00
{
2022-05-19 10:43:03 -04:00
bool bFoundTID = false ;
if ( ! SurfaceMesh . IsTriangle ( SurfaceTID ) )
2021-11-10 14:22:10 -05:00
{
2022-05-19 10:43:03 -04:00
return bFoundTID ;
}
if ( IsFacePoint ( SurfacePoint ) )
{
const int32 TID = SurfacePoint . Position . TriPosition . TriID ;
if ( TID = = SurfaceTID )
{
bFoundTID = true ;
}
}
else if ( IsEdgePoint ( SurfacePoint ) )
{
const int32 SurfaceEID = SurfacePoint . Position . EdgePosition . EdgeID ;
const FIndex2i SurfaceEdgeT = SurfaceMesh . GetEdgeT ( SurfaceEID ) ;
if ( SurfaceEdgeT . IndexOf ( SurfaceTID ) ! = - 1 )
{
bFoundTID = true ;
}
}
else
{
checkSlow ( IsVertexPoint ( SurfacePoint ) ) ;
const int32 SurfaceVID = SurfacePoint . Position . VertexPosition . VID ;
for ( int TID : SurfaceMesh . VtxTrianglesItr ( SurfaceVID ) )
{
if ( TID = = SurfaceTID )
{
bFoundTID = true ;
break ;
}
}
}
return bFoundTID ;
}
/**
* Collect the surface triangles that are adjacent to the specified surface point
* this may be one , two , or many - depending on location of the point ( eg a surface point that is on an edge may have two adjacent tris )
*/
TArray < int32 > GetAdjacentTriangles ( const IntrinsicCorrespondenceUtils : : FSurfacePoint & SurfacePoint , const FDynamicMesh3 & SurfaceMesh )
{
TArray < int32 > AdjTIDs ;
if ( IsEdgePoint ( SurfacePoint ) )
{
const int32 SurfaceEID = SurfacePoint . Position . EdgePosition . EdgeID ;
const FIndex2i SurfaceEdgeT = SurfaceMesh . GetEdgeT ( SurfaceEID ) ;
AdjTIDs . Add ( SurfaceEdgeT . A ) ;
if ( SurfaceEdgeT . B ! = - 1 )
{
AdjTIDs . Add ( SurfaceEdgeT . B ) ;
}
}
else if ( IsFacePoint ( SurfacePoint ) )
{
const int32 SurfaceTID = SurfacePoint . Position . TriPosition . TriID ;
AdjTIDs . Add ( SurfaceTID ) ;
}
else
{
checkSlow ( IsVertexPoint ( SurfacePoint ) ) ;
const int32 SurfaceVID = SurfacePoint . Position . VertexPosition . VID ;
for ( int SurfaceTID : SurfaceMesh . VtxTrianglesItr ( SurfaceVID ) )
{
AdjTIDs . Add ( SurfaceTID ) ;
}
}
return MoveTemp ( AdjTIDs ) ;
}
/**
* Convert a surface point to barycentric coordinates relative to the specified surface triangle .
* NB : this assumes ( but does not check ) that the surface point is on or adjacent to the surface triangle .
*/
FVector3d AsBarycenteric ( const IntrinsicCorrespondenceUtils : : FSurfacePoint & SurfacePoint , const int32 SurfaceTID , const FDynamicMesh3 & SurfaceMesh )
{
FVector3d BC ( 0. , 0. , 0. ) ;
if ( IsEdgePoint ( SurfacePoint ) ) // translate an edge point to barycentric coordinates
{
const int32 SurfaceEID = SurfacePoint . Position . EdgePosition . EdgeID ;
const double Alpha = SurfacePoint . Position . EdgePosition . Alpha ; // Pos(Edge.A) (Alpha) + (1-Alpha) Pos(Edge.B)
checkSlow ( SurfaceMesh . GetEdgeT ( SurfaceEID ) . IndexOf ( SurfaceTID ) ! = - 1 ) ;
const FIndex2i SurfaceEdgeV = SurfaceMesh . GetEdgeV ( SurfaceEID ) ;
const FIndex3i SurfaceTri = SurfaceMesh . GetTriangle ( SurfaceTID ) ;
for ( int32 i = 0 ; i < 3 ; + + i )
{
if ( SurfaceTri [ i ] = = SurfaceEdgeV . A )
{
BC [ i ] = Alpha ;
}
else if ( SurfaceTri [ i ] = = SurfaceEdgeV . B )
{
BC [ i ] = ( 1. - Alpha ) ;
}
}
}
else if ( IsFacePoint ( SurfacePoint ) ) // translate a face point to barycentric coordinates
{
checkSlow ( SurfacePoint . Position . TriPosition . TriID = = SurfaceTID ) ;
BC = SurfacePoint . Position . TriPosition . BarycentricCoords ;
}
else // translate a vertex point to barycentric coordinates
{
const int32 SurfaceVID = SurfacePoint . Position . VertexPosition . VID ;
const FIndex3i SurfaceTri = SurfaceMesh . GetTriangle ( SurfaceTID ) ;
bool bValid = false ;
for ( int32 i = 0 ; i < 3 ; + + i )
{
if ( SurfaceTri [ i ] = = SurfaceVID )
{
BC [ i ] = 1. ;
bValid = true ;
}
}
checkSlow ( bValid ) ;
}
return BC ;
}
bool AreOnSameSurfaceEdge ( const IntrinsicCorrespondenceUtils : : FSurfacePoint & PointA , const IntrinsicCorrespondenceUtils : : FSurfacePoint & PointB , const FDynamicMesh3 & Mesh )
{
if ( IsFacePoint ( PointA ) | | IsFacePoint ( PointB ) )
{
return false ;
}
if ( IsVertexPoint ( PointA ) & & IsVertexPoint ( PointB ) )
{
return Mesh . FindEdge ( PointA . Position . VertexPosition . VID , PointB . Position . VertexPosition . VID ) ! = FDynamicMesh3 : : InvalidID ;
}
auto TestVertAndEdge = [ & Mesh ] ( const IntrinsicCorrespondenceUtils : : FSurfacePoint & VertexPoint , const IntrinsicCorrespondenceUtils : : FSurfacePoint & EdgePoint ) - > bool
{
const int32 VID = VertexPoint . Position . VertexPosition . VID ;
const int32 EID = EdgePoint . Position . EdgePosition . EdgeID ;
const FDynamicMesh3 : : FEdge & Edge = Mesh . GetEdgeRef ( EID ) ;
return ( Edge . Vert . IndexOf ( VID ) ! = - 1 ) ;
} ;
if ( IsVertexPoint ( PointA ) )
{
// B must be an edge point.
return TestVertAndEdge ( PointA , PointB ) ;
}
else
{
// B must be vert and A is edge
return TestVertAndEdge ( PointB , PointA ) ;
2021-11-10 14:22:10 -05:00
}
}
2022-05-19 10:43:03 -04:00
/** array given matrix-like access with row-major layout for a non-square matrix */
struct FNonSqrMat
{
FNonSqrMat ( int32 NumCols , int32 NumRows )
: N ( NumCols )
, M ( NumRows )
{
Data . AddZeroed ( N * M ) ;
}
2021-11-10 14:22:10 -05:00
2022-05-19 10:43:03 -04:00
double & operator ( ) ( int32 i , int32 j )
{
return Data [ RowMajorOffset ( i , j ) ] ;
}
const double & operator ( ) ( int32 i , int32 j ) const
{
return Data [ RowMajorOffset ( i , j ) ] ;
}
int32 RowMajorOffset ( int32 i , int32 j ) const
{
return i * N + j ;
}
int32 N ;
int32 M ;
TArray < double > Data ;
} ;
/**
* Given an underspecified matrix equation ( more unknowns than equations ) of the form M * Soln = BVector ,
* where M is a Nx3 matrix , Soln is an N - dimensional array , and BVector is a 3 - dimensional array
*
* this computes the solution that minimizes the quantity | Soln |
*
* The matrix M is specified by providing the three rows of the matrix ( Row0 , Row1 , Row2 ) .
*
* @ return false on failure , otherwise the vector Soln will hold the result .
*/
bool ComputeMinNormSolution ( const TArray < double > & Row0 , const TArray < double > & Row1 , const TArray < double > & Row2 , const FVector3d & BVector , TArray < double > & Soln )
{
checkSlow ( Row0 . Num ( ) = = Row1 . Num ( ) & & Row2 . Num ( ) = = Row1 . Num ( ) ) ;
const int32 NCols = Row0 . Num ( ) ;
checkSlow ( NCols > 2 ) ;
Soln . SetNum ( NCols ) ;
if ( NCols = = 3 )
{
const FMatrix3d Mat3d ( Row0 [ 0 ] , Row0 [ 1 ] , Row0 [ 2 ] ,
Row1 [ 0 ] , Row1 [ 1 ] , Row1 [ 2 ] ,
Row2 [ 0 ] , Row2 [ 1 ] , Row2 [ 2 ] ) ;
const double Det = Mat3d . Determinant ( ) ;
if ( TMathUtil < double > : : Abs ( Det ) < TMathUtil < double > : : Epsilon )
{
return false ;
}
else
{
const FMatrix3d InvMat3d = Mat3d . Inverse ( ) ;
const FVector3d SolVector3d = InvMat3d * BVector ;
Soln [ 0 ] = SolVector3d [ 0 ] ;
Soln [ 1 ] = SolVector3d [ 1 ] ;
Soln [ 2 ] = SolVector3d [ 2 ] ;
}
}
else
{
FNonSqrMat Mat ( NCols , 3 ) ;
for ( int j = 0 ; j < NCols ; + + j )
{
Mat ( 0 , j ) = Row0 [ j ] ;
Mat ( 1 , j ) = Row1 [ j ] ;
Mat ( 2 , j ) = Row2 [ j ] ;
}
// form MMt = Mat * Transpose(Mat)
FMatrix3d MMt ( 0. ) ;
for ( int32 i = 0 ; i < 3 ; + + i )
{
FVector3d & Row = ( i = = 0 ) ? MMt . Row0 : ( i = = 1 ) ? MMt . Row1 : MMt . Row2 ;
for ( int32 j = 0 ; j < 3 ; + + j )
{
for ( int32 k = 0 ; k < NCols ; + + k )
{
Row [ j ] + = Mat ( i , k ) * Mat ( j , k ) ;
}
}
}
// solve MMt y = b, and compute x = Mt * y
const double Det = MMt . Determinant ( ) ;
if ( TMathUtil < double > : : Abs ( Det ) < TMathUtil < double > : : Epsilon )
{
return false ;
}
else
{
const FMatrix3d InvMMt = MMt . Inverse ( ) ;
const FVector3d YVector3d = InvMMt * BVector ;
//Soln = Transpose(Mat) * Yvector;
for ( int32 j = 0 ; j < NCols ; + + j )
{
Soln [ j ] = Mat ( 0 , j ) * YVector3d [ 0 ] + Mat ( 1 , j ) * YVector3d [ 1 ] + Mat ( 2 , j ) * YVector3d [ 2 ] ;
}
}
}
return true ;
}
} // end namespace FIntrinsicMeshImplUtils
/**------------------------------------------------------------------------------
* FIntrinsicMesh Methods
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
UE : : Geometry : : FIntrinsicMesh : : FIntrinsicMesh ( const FDynamicMesh3 & SurfaceMesh )
: MyBase ( SurfaceMesh )
, NormalCoordinates ( SurfaceMesh )
2021-11-10 14:22:10 -05:00
{
2022-05-19 10:43:03 -04:00
const int32 MaxVertexID = SurfaceMesh . MaxVertexID ( ) ;
// add surface position.
IntrinsicVertexPositions . SetNum ( MaxVertexID ) ;
// initialize: identify with vertex in Extrinsic Mesh
for ( int32 VID = 0 ; VID < MaxVertexID ; + + VID )
{
if ( SurfaceMesh . IsVertex ( VID ) )
{
FSurfacePoint VertexSurfacePoint ( VID ) ;
IntrinsicVertexPositions [ VID ] = VertexSurfacePoint ;
}
}
2021-11-10 14:22:10 -05:00
2022-05-19 10:43:03 -04:00
}
TArray < UE : : Geometry : : FIntrinsicMesh : : FSurfacePoint > FIntrinsicMesh : : TraceEdge ( int32 IntrinsicEID , double CoalesceThreshold , bool bReverse ) const
{
return FNormalCoordIntrinsicTraceImpl : : TraceEdge ( * this , IntrinsicEID , CoalesceThreshold , bReverse ) ;
}
TArray < UE : : Geometry : : FIntrinsicMesh : : FEdgeAndCrossingIdx > FIntrinsicMesh : : GetImplicitEdgeCrossings ( const int32 SurfaceEID , const bool bReverse ) const
{
return FNormalCoordSurfaceTraceImpl : : TraceSurfaceEdge ( * this , SurfaceEID , bReverse ) ;
}
TArray < UE : : Geometry : : FIntrinsicMesh : : FSurfacePoint > FIntrinsicMesh : : TraceSurfaceEdge ( int32 SurfaceEID , double CoalesceThreshold , bool bReverse ) const
{
const FDynamicMesh3 & SurfaceMesh = * this - > GetExtrinsicMesh ( ) ;
const FIntrinsicMesh & IntrinsicMesh = * this ;
TArray < FEdgeAndCrossingIdx > EdgeAndCrossingIdxs = this - > GetImplicitEdgeCrossings ( SurfaceEID , bReverse ) ;
const int32 NumEdgeAndXIdx = EdgeAndCrossingIdxs . Num ( ) ;
TArray < UE : : Geometry : : FIntrinsicMesh : : FSurfacePoint > ResultTraceArray ;
if ( NumEdgeAndXIdx = = 0 )
{
return ResultTraceArray ;
}
// find start (.A) and end (.B) trace mesh vids for this edge
const FIndex2i OrderedTraceEdgeV = [ & ]
{
const FIndex2i EdgeV = SurfaceMesh . GetEdgeV ( SurfaceEID ) ;
if ( bReverse )
{
return FIndex2i ( EdgeV . B , EdgeV . A ) ;
}
else
{
return EdgeV ;
}
} ( ) ;
// the start and end are vertices in both SurfaceMesh and IntrinsicMesh, furthermore when vertices exist on both meshes they have the same VID
const FSurfacePoint TraceStartSurfacePoint ( OrderedTraceEdgeV . A ) ;
const FSurfacePoint TraceEndSurfacePoint ( OrderedTraceEdgeV . B ) ;
ResultTraceArray . Add ( TraceStartSurfacePoint ) ;
// due to (possible) intrinsic mesh edge splits, the trace of the surface edge across the intrinsic mesh has to be broken
// into sections that follow split intrinsic mesh edges, and those that cross the faces of intrinsic mesh triangles.
{
FSurfacePoint LocalStartSurfacePoint = TraceStartSurfacePoint ;
TArray < int32 > IntrinsicEdgesCrossed ;
for ( int32 i = 1 ; i < NumEdgeAndXIdx ; + + i )
{
const FEdgeAndCrossingIdx & EdgeIDandXIdx = EdgeAndCrossingIdxs [ i ] ;
if ( EdgeIDandXIdx . CIdx ! = 0 )
{
IntrinsicEdgesCrossed . Add ( EdgeIDandXIdx . EID ) ;
}
else if ( IntrinsicEdgesCrossed . Num ( ) > 0 )
{
// current point is the end point of local face crossing.
// convert to surface point.
const FSurfacePoint LocalEndSurfacePoint ( IntrinsicMesh . GetTriangle ( EdgeIDandXIdx . TID ) [ EdgeIDandXIdx . EID ] ) ;
// compute crossings
FNormalCoordSurfaceTraceImpl : : ConvertEdgesCrossed ( LocalStartSurfacePoint , LocalEndSurfacePoint , IntrinsicEdgesCrossed ,
IntrinsicMesh , SurfaceMesh , CoalesceThreshold ,
[ ] ( int32 vid ) { return FSurfacePoint ( vid ) ; } , ResultTraceArray ) ;
// add surface point
ResultTraceArray . Add ( LocalEndSurfacePoint ) ;
// empty the temp crossings.
IntrinsicEdgesCrossed . Reset ( ) ;
LocalStartSurfacePoint = LocalEndSurfacePoint ;
}
else
{
// convert to surface point.
const FSurfacePoint LocalEndSurfacePoint ( IntrinsicMesh . GetTriangle ( EdgeIDandXIdx . TID ) [ EdgeIDandXIdx . EID ] ) ;
// add surface point
ResultTraceArray . Add ( LocalEndSurfacePoint ) ;
LocalStartSurfacePoint = LocalEndSurfacePoint ;
}
}
// should have processed all edge crossings.
checkSlow ( IntrinsicEdgesCrossed . Num ( ) = = 0 ) ;
// ended on the last vertex.
checkSlow ( ResultTraceArray . Last ( ) . Position . VertexPosition . VID = = OrderedTraceEdgeV . B ) ;
}
return MoveTemp ( ResultTraceArray ) ;
}
UE : : Geometry : : EMeshResult UE : : Geometry : : FIntrinsicMesh : : FlipEdge ( int32 EID , FEdgeFlipInfo & EdgeFlipInfo )
{
using namespace FIntrinsicMeshImplUtils ;
if ( ! IsEdge ( EID ) )
{
return EMeshResult : : Failed_NotAnEdge ;
}
if ( IsBoundaryEdge ( EID ) )
{
return EMeshResult : : Failed_IsBoundaryEdge ;
}
// state before flip, needed when updating the normal coords after the flip
const FIndex2i EdgeT = GetEdgeT ( EID ) ;
const FIndex3i TAEIDs = GetTriEdges ( EdgeT . A ) ;
const FIndex3i TBEIDs = GetTriEdges ( EdgeT . B ) ;
const FIndex2i OppVs = GetEdgeOpposingV ( EID ) ;
EMeshResult FlipResult = MyBase : : FlipEdge ( EID , EdgeFlipInfo ) ;
if ( FlipResult = = EMeshResult : : Ok )
{
// update the normal coords
NormalCoordinates . OnFlipEdge ( EdgeT . A , TAEIDs , OppVs . A , EdgeT . B , TBEIDs , OppVs . B , EID ) ;
// if the flip produced an intrinsic edge that is a segment of a surface edge, update the
// coorindate to -1 to help tracing.
if ( NormalCoordinates . NormalCoord [ EID ] = = 0 & & OppVs . A ! = OppVs . B )
{
FSurfacePoint SurfacePoints [ 2 ] = { GetVertexSurfacePoint ( OppVs . A ) , GetVertexSurfacePoint ( OppVs . B ) } ;
if ( AreOnSameSurfaceEdge ( SurfacePoints [ 0 ] , SurfacePoints [ 1 ] , * NormalCoordinates . SurfaceMesh ) )
{
NormalCoordinates . NormalCoord [ EID ] = - 1 ;
}
}
}
return FlipResult ;
}
UE : : Geometry : : EMeshResult UE : : Geometry : : FIntrinsicMesh : : PokeTriangle ( int32 IntrinsicTID , const FVector3d & BaryCoordinates , FPokeTriangleInfo & PokeInfo )
{
// The intrinsic poke creates 3 new intrinsic edges and "normal coordinates"(the number of crossing surface edges) need to be created for each.
//
// also, "roundabout" data is recored for any new intrinsic edge that is adjacent to an intrinsic vertex that is also a surface vertex.
// this roundabout data indicates the next surface edge when traveling ccw about the vertex from the new edge.
//
// lastly the surface position of the new intrinsic vertex is computed and recored.
//
// note: to compute the surface position of the new intrinsic vertex, the intersection (convex polygon) between the original intrinsic triangle and the surface triangle
// that supports the new vertex is first identified
using namespace FIntrinsicMeshImplUtils ;
if ( ! IsTriangle ( IntrinsicTID ) )
{
return EMeshResult : : Failed_NotATriangle ;
}
// state before the poke
const FIndex3i OriginalVIDs = GetTriangle ( IntrinsicTID ) ;
const FIndex3i OriginalEdges = GetTriEdges ( IntrinsicTID ) ;
const FVector3d OriginalTriEdgeLengths = GetTriEdgeLengths ( IntrinsicTID ) ;
const int32 IndexOf = VectorUtil : : Min3Index ( OriginalTriEdgeLengths ) ;
// fail if the smallest side of the triangle is too small
//[todo] consider permuting the triangle so the zero side is the longest.
if ( OriginalTriEdgeLengths [ IndexOf ] < TMathUtilConstants < double > : : ZeroTolerance )
{
return EMeshResult : : Failed_Unsupported ;
}
// unwrap the intrinsic triangle to a 2d plane.
// edge0 runs along the positive x-axis
const FVector2d IntrinsicTri2D [ 3 ] = { FVector2d ( 0. , 0. ) ,
FVector2d ( OriginalTriEdgeLengths [ 0 ] , 0. ) ,
ComputeOpposingVert2d ( OriginalTriEdgeLengths [ 0 ] , OriginalTriEdgeLengths [ 1 ] , OriginalTriEdgeLengths [ 2 ] ) } ;
auto AsR2Position = [ & IntrinsicTri2D ] ( const FVector3d & BCoords )
{
return BCoords [ 0 ] * IntrinsicTri2D [ 0 ] + BCoords [ 1 ] * IntrinsicTri2D [ 1 ] + BCoords [ 2 ] * IntrinsicTri2D [ 2 ] ;
} ;
// position of the new vertex relative to the flattened intrinsic triangle.
const FVector2d PokedPos = AsR2Position ( BaryCoordinates ) ;
// Utility: converts distance along a directed edge of an the intrinsic triangle to barycentric coords.
auto EdgeDistanceToBaryCoords = [ & OriginalTriEdgeLengths ] ( int32 TriSide , double Distance )
{
const double EdgeLength = OriginalTriEdgeLengths [ TriSide ] ;
const double Alpha = ( EdgeLength > TMathUtilConstants < double > : : ZeroTolerance ) ? Distance / EdgeLength : 0.5 ;
FVector3d Result ( 0. , 0. , 0. ) ;
Result [ TriSide ] = ( 1. - Alpha ) ;
Result [ ( TriSide + 1 ) % 3 ] = Alpha ;
return Result ;
} ;
// -- find the surface edges that cross the intrinsic triangle.
// for each side of the intrinsic triangle, generate a list of places where the surfaces edge cross the intrinsic edge
// storing the location as a FPointCorrespondence
// point described both relative to the intrinsic triangle and to the surface mesh.
struct FPointCorrespondence
{
FVector2d LocalPos ; // position relative to flattened intrinsic triangle
FVector3d IntrinsicBC ; // barycentric coordinates relative to intrinsic triangle ( duplicates info in LocalPos, but easier to work with)
FSurfacePoint SurfacePos ; // position relative to surface mesh
} ;
// for each directed side the intrinsic triangle, recored all the surface edge crossings and their relative locations along the side.
TMap < int32 , FPointCorrespondence > SurfaceEdgeXings [ 3 ] ;
{
auto GetSurfaceEdgeCrossingLocations = [ & ] ( const int32 Side )
{
const int32 IntrinsicEID = OriginalEdges [ Side ] ;
const FDynamicMesh3 & SurfaceMesh = * NormalCoordinates . SurfaceMesh ;
const double CoalesceThreshold = 0. ;
const bool bReverse = ( GetEdgeT ( IntrinsicEID ) . A ! = IntrinsicTID ) ;
// sequence of surface points corresponding to surface edges intersecting this intrinsic edge.
const TArray < FSurfacePoint > EdgeAsSurfacePoints = TraceEdge ( IntrinsicEID , CoalesceThreshold , bReverse ) ;
// record the surface point and the distance along this triangle side to the intersection
// key with the surface edge id.
TMap < int32 , FPointCorrespondence > CrossingLocations ;
const int32 NumSPoints = EdgeAsSurfacePoints . Num ( ) ;
if ( NumSPoints > 0 )
{
bool bTmp ;
FVector3d Positions [ 2 ] ;
Positions [ 0 ] = AsR3Position ( EdgeAsSurfacePoints [ 0 ] , SurfaceMesh , bTmp ) ;
int32 Cur = 1 ;
double AccumulatedDistance = 0. ;
for ( int i = 1 ; i < NumSPoints ; + + i )
{
const FSurfacePoint & SurfacePoint = EdgeAsSurfacePoints [ i ] ;
Positions [ Cur ] = AsR3Position ( SurfacePoint , SurfaceMesh , bTmp ) ;
const double LineElementLength = ( Positions [ Cur ] - Positions [ 1 - Cur ] ) . Length ( ) ;
AccumulatedDistance + = LineElementLength ;
if ( SurfacePoint . PositionType = = IntrinsicCorrespondenceUtils : : FSurfacePoint : : EPositionType : : Edge )
{
const int32 SurfaceEID = SurfacePoint . Position . EdgePosition . EdgeID ;
const FVector3d IntrinsicBaryCoords = EdgeDistanceToBaryCoords ( Side , AccumulatedDistance ) ;
FPointCorrespondence XingCorrespondence = { AsR2Position ( IntrinsicBaryCoords ) , IntrinsicBaryCoords , SurfacePoint } ;
CrossingLocations . Add ( SurfaceEID , XingCorrespondence ) ;
}
Cur = 1 - Cur ;
}
}
return MoveTemp ( CrossingLocations ) ;
} ;
for ( int32 i = 0 ; i < 3 ; + + i )
{
SurfaceEdgeXings [ i ] = GetSurfaceEdgeCrossingLocations ( i ) ;
}
}
// the intersection of a surface edge with the intrinsic triangle.
struct FSurfaceEdgeSegment
{
// end points where this surface edge intersects the boundary of the intrinsic triangle.
FPointCorrespondence P0 ;
FPointCorrespondence P1 ;
// corresponding surface mesh edge id
int32 SurfaceEID = - 1 ;
} ;
// after the poke, new intrinsic edges will connect the new vertex to the corners of the original triangle.
// number these as {0, 1, 2} according to the corners of the IntrinsicTri2D
// compute "normal coordinates", i.e. find the number of times each new intrinsic edge is intersected by surface mesh edge segments
// additionally, (for each new intrinsic edge) identify the intersecting surface edge that was closest to the new vertex.
int32 NewEdgeNormCoords [ 3 ] = { 0 , 0 , 0 } ;
FSurfaceEdgeSegment ClosestSurfaceEdgeSegment [ 3 ] ; // per new intrinsic edge
{
auto UpdateNormCoordsAndClosest = [ & NewEdgeNormCoords , & ClosestSurfaceEdgeSegment ] ( const FPointCorrespondence & P0 , const FPointCorrespondence & P1 , int32 SurfaceEID , int32 ZeroOneOrTwo )
{
NewEdgeNormCoords [ ZeroOneOrTwo ] + = 1 ;
FSurfaceEdgeSegment & ClosestSegment = ClosestSurfaceEdgeSegment [ ZeroOneOrTwo ] ;
const bool bHaveValidClosest = ( ClosestSegment . SurfaceEID ! = - 1 ) ;
if ( bHaveValidClosest )
{
// by construction the surface edge segments can not intersect in the face of an intrinsic triangle
// so we need only check a single point of the old segment against the one defined by P0 to P1
const FVector2d OldSegmentCenter = 0.5 * ( ClosestSegment . P0 . LocalPos + ClosestSegment . P1 . LocalPos ) ;
const double SideTest = UE : : Geometry : : Orient ( P0 . LocalPos , P1 . LocalPos , OldSegmentCenter ) ;
if ( SideTest > 0 ) // old surface segment is to the right of this new one segment
{
ClosestSegment . P0 = P0 ;
ClosestSegment . P1 = P1 ;
ClosestSegment . SurfaceEID = SurfaceEID ;
}
}
else
{
// just update since this is the first intersecting surface edge
ClosestSegment . P0 = P0 ;
ClosestSegment . P1 = P1 ;
ClosestSegment . SurfaceEID = SurfaceEID ;
}
} ;
for ( int32 Side = 0 ; Side < 3 ; + + Side )
{
const int32 NextSide = ( Side + 1 ) % 3 ;
const int32 NextNextSide = ( Side + 2 ) % 3 ;
for ( const TPair < int32 , FPointCorrespondence > & XingPair : SurfaceEdgeXings [ Side ] )
{
const int32 & SurfaceEID = XingPair . Key ;
const FPointCorrespondence & P0 = XingPair . Value ;
if ( FPointCorrespondence * P1Ptr = SurfaceEdgeXings [ NextSide ] . Find ( SurfaceEID ) ) // the surface edge exits the next side
{
const FPointCorrespondence & P1 = * P1Ptr ;
const double SignedArea = UE : : Geometry : : Orient ( P0 . LocalPos , P1 . LocalPos , PokedPos ) ; //same as (P1-P0)X(PokedPos - P0)
if ( SignedArea < = 0 ) // surface segment crosses two new intrinsic edges.
{
// [todo] better treatment for == case?
UpdateNormCoordsAndClosest ( P0 , P1 , SurfaceEID , Side ) ;
UpdateNormCoordsAndClosest ( P0 , P1 , SurfaceEID , NextNextSide ) ;
}
if ( SignedArea > 0 ) // surface segment crosses one new intrinsic edges.
{
UpdateNormCoordsAndClosest ( P1 , P0 , SurfaceEID , NextSide ) ;
}
}
else if ( SurfaceEdgeXings [ NextNextSide ] . Contains ( SurfaceEID ) = = false ) // the surface edge must exit the opp vertex
{
FVector3d P1BC ( 0. , 0. , 0. ) ; P1BC [ NextNextSide ] = 1. ;
const FPointCorrespondence P1 = { IntrinsicTri2D [ NextNextSide ] , P1BC , IntrinsicVertexPositions [ OriginalVIDs [ NextNextSide ] ] } ; ;
const double SignedArea = UE : : Geometry : : Orient ( P0 . LocalPos , P1 . LocalPos , PokedPos ) ; // same as (P1-P0)X(PokedPos - P0)
if ( SignedArea < = 0 ) // surface segment crosses one new intrinsic edges.
{
// [todo] better treatment for == case?
UpdateNormCoordsAndClosest ( P0 , P1 , SurfaceEID , Side ) ;
}
if ( SignedArea > 0 ) // surface segment crosses one new intrinsic edges.
{
UpdateNormCoordsAndClosest ( P1 , P0 , SurfaceEID , NextSide ) ;
}
}
}
}
}
// intersection of the intrinsic mesh triangle (prior to poke) and the surface mesh triangle that ultimately supports the new vertex
// forms a convex polygon. Convert the vertices of this polygon to FPointCorresponce.
const TArray < FPointCorrespondence > BoundingConvexPolyVerts = [ & ]
{
TArray < FPointCorrespondence > TmpBoundaryPoints ;
// method to add Correspondence points. Makes sure we don't add a point twice
// note, only need to check surface verts due to our usage and the fact surface edges only meet at surface verts.
TSet < int32 > VisitedSurfaceVerts ;
auto AddPointCorrespondence = [ & ] ( const FPointCorrespondence & PointCorrespondence )
{
const FSurfacePoint & SP = PointCorrespondence . SurfacePos ;
if ( IsVertexPoint ( SP ) )
{
const int32 SurfaceVID = SP . Position . VertexPosition . VID ;
if ( ! VisitedSurfaceVerts . Contains ( SurfaceVID ) )
{
TmpBoundaryPoints . Add ( PointCorrespondence ) ;
VisitedSurfaceVerts . Add ( SurfaceVID ) ;
}
}
else
{
TmpBoundaryPoints . Add ( PointCorrespondence ) ;
}
} ;
TSet < int32 > VisitedSurfaceEdges ;
for ( int32 i = 0 ; i < 3 ; + + i )
{
// if it exists, this surface edge blocks
// the new intrinsic vertex's view of the i-th original tri corner.
const FSurfaceEdgeSegment & SurfaceEdgeSegment = ClosestSurfaceEdgeSegment [ i ] ;
const int32 SurfaceEdgeID = SurfaceEdgeSegment . SurfaceEID ;
if ( SurfaceEdgeID = = - 1 ) // no edge blocks..
{
const FVector2d & CornerLocalPos = IntrinsicTri2D [ i ] ;
const int32 VID = OriginalVIDs [ i ] ;
const FSurfacePoint & CornerSP = IntrinsicVertexPositions [ VID ] ;
FVector3d IntrinsicBC ( 0. , 0. , 0. ) ;
IntrinsicBC [ i ] = 1. ;
FPointCorrespondence PointCorrespondence = { CornerLocalPos , IntrinsicBC , CornerSP } ;
AddPointCorrespondence ( PointCorrespondence ) ;
}
else
{
if ( ! VisitedSurfaceEdges . Contains ( SurfaceEdgeID ) )
{
VisitedSurfaceEdges . Add ( SurfaceEdgeID ) ;
AddPointCorrespondence ( SurfaceEdgeSegment . P0 ) ;
AddPointCorrespondence ( SurfaceEdgeSegment . P1 ) ;
}
}
}
return MoveTemp ( TmpBoundaryPoints ) ;
} ( ) ;
// Identify the surface triangle that is common to the bounding convex polygon verts.
const int32 SurfaceTID = [ & ]
{
const FDynamicMesh3 & SurfaceMesh = * NormalCoordinates . SurfaceMesh ;
// special case: are the points just the corners of a surface triangle?
if ( BoundingConvexPolyVerts . Num ( ) = = 3 )
{
if ( IsVertexPoint ( BoundingConvexPolyVerts [ 0 ] . SurfacePos )
& & IsVertexPoint ( BoundingConvexPolyVerts [ 1 ] . SurfacePos )
& & IsVertexPoint ( BoundingConvexPolyVerts [ 2 ] . SurfacePos ) )
{
int32 SurfaceVIDs [ 3 ] = { BoundingConvexPolyVerts [ 0 ] . SurfacePos . Position . VertexPosition . VID ,
BoundingConvexPolyVerts [ 1 ] . SurfacePos . Position . VertexPosition . VID ,
BoundingConvexPolyVerts [ 2 ] . SurfacePos . Position . VertexPosition . VID
} ;
return SurfaceMesh . FindTriangle ( SurfaceVIDs [ 0 ] , SurfaceVIDs [ 1 ] , SurfaceVIDs [ 2 ] ) ;
}
}
// find surface triangles corresponding to one of the surface points.
// note: a surface point will correspond to 1, 2, or many surface triangles (face point, edge point, or vertex point).
const TArray < int32 > CandidateTIDs = [ & ]
{
TArray < int32 > TmpTIDs ;
// are any of the surface point already on a surface triangle face?
int32 TID = - 1 ;
for ( const FPointCorrespondence & BoundaryPoint : BoundingConvexPolyVerts )
{
const FSurfacePoint & SurfacePos = BoundaryPoint . SurfacePos ;
if ( IsFacePoint ( SurfacePos ) )
{
TID = SurfacePos . Position . TriPosition . TriID ;
break ;
}
}
if ( TID ! = - 1 )
{
TmpTIDs . Add ( TID ) ;
}
else
{
const FSurfacePoint & SurfacePos = BoundingConvexPolyVerts [ 0 ] . SurfacePos ;
// get all the surface triangles adjacent to this surface point
TmpTIDs = GetAdjacentTriangles ( SurfacePos , SurfaceMesh ) ;
}
return MoveTemp ( TmpTIDs ) ;
} ( ) ;
// determine which candidate triangle is actually adjacent to all the surface points.
const int32 MutuallyAdjacentTID = [ & ]
{
for ( int32 CandidateTID : CandidateTIDs )
{
bool bIsAdjacentToAll = true ;
// check adjacency to the boundary points.
for ( const FPointCorrespondence & BoundaryPoint : BoundingConvexPolyVerts )
{
const FSurfacePoint & SurfacePos = BoundaryPoint . SurfacePos ;
bIsAdjacentToAll = bIsAdjacentToAll & & IsOnSurfaceTriangle ( SurfacePos , CandidateTID , SurfaceMesh ) ;
}
if ( bIsAdjacentToAll )
{
return CandidateTID ;
}
}
return - 1 ;
} ( ) ;
return MutuallyAdjacentTID ;
} ( ) ;
if ( SurfaceTID = = - 1 )
{
return EMeshResult : : Failed_Unsupported ;
}
// construct the surface barycentric coords for the poked position.
const FVector3d PokedSurfaceBaryCoords = [ & ]
{
const int32 NumBoundaryPts = BoundingConvexPolyVerts . Num ( ) ;
// In the barycentric space of the intrinsic triangle:
2023-02-07 17:42:45 -05:00
// find the (min norm) linear combination convex-poly vertices that is equivalent to the new vertex location
2022-05-19 10:43:03 -04:00
TArray < double > MinNormSolution ;
TArray < double > MatRows [ 3 ] ;
for ( int32 i = 0 ; i < 3 ; + + i )
{
MatRows [ i ] . AddUninitialized ( NumBoundaryPts ) ;
for ( int32 j = 0 ; j < NumBoundaryPts ; + + j )
{
const FVector3d & IntrinsicBarycentric = BoundingConvexPolyVerts [ j ] . IntrinsicBC ;
MatRows [ i ] [ j ] = IntrinsicBarycentric [ i ] ;
}
}
const bool bValidInterpolation = ComputeMinNormSolution ( MatRows [ 0 ] , MatRows [ 1 ] , MatRows [ 2 ] , BaryCoordinates , MinNormSolution ) ;
// convert the surface points to barycentric coords relative to the surface triangle.
TArray < FVector3d > SurfaceBaryCoords ;
{
const FDynamicMesh3 & SurfaceMesh = * NormalCoordinates . SurfaceMesh ;
SurfaceBaryCoords . Reserve ( NumBoundaryPts ) ;
for ( const FPointCorrespondence & BoundaryPoint : BoundingConvexPolyVerts )
{
const FSurfacePoint & SurfacePos = BoundaryPoint . SurfacePos ;
SurfaceBaryCoords . Add ( AsBarycenteric ( SurfacePos , SurfaceTID , SurfaceMesh ) ) ;
}
}
// interpolate the surface barycentric coordinates (using the coefficients from the intrinsic barycenterics)
// to construct the surface barycentric coordinates of the new vertex location.
// note: if interpolation failed just use the center of the polygon.
FVector3d SurfaceBarycoods ( 0. , 0. , 0. ) ;
if ( bValidInterpolation )
{
for ( int32 i = 0 ; i < NumBoundaryPts ; + + i )
{
SurfaceBarycoods + = MinNormSolution [ i ] * SurfaceBaryCoords [ i ] ;
}
}
else
{
// just use the "center" of the convex polygon
for ( int32 i = 0 ; i < NumBoundaryPts ; + + i )
{
SurfaceBarycoods + = SurfaceBaryCoords [ i ] ;
}
SurfaceBarycoods * = 1. / double ( NumBoundaryPts ) ;
}
return SurfaceBarycoods ;
} ( ) ;
// surface point for the new (poked) vertex
const FSurfacePoint PokedSurfacePoint ( SurfaceTID , PokedSurfaceBaryCoords ) ;
// update the topology.
// Add a new vertex and faces to the IntrinsicMesh.
// Note: the r3 position will be wrong initially since this poke will just interpolate the corners of the intrinsic tri.
// we fix this position as the last step in this function
EMeshResult PokeResult = MyBase : : PokeTriangle ( IntrinsicTID , BaryCoordinates , PokeInfo ) ;
if ( PokeResult ! = EMeshResult : : Ok )
{
return PokeResult ;
}
FIndex3i NewTris ( IntrinsicTID , PokeInfo . NewTriangles [ 0 ] , PokeInfo . NewTriangles [ 1 ] ) ;
const int32 NewVID = PokeInfo . NewVertex ;
// Need to update intrinsic information for the 3 triangles that resulted from the poke
// 1) a)update the number of surface edge crossings for each new intrinsic edge
// b)update the roundabout data for any new intrinsic edge that is adj to a surface vertex
// 2) update the surface position of the new vertex.
{
// set/update number of surface edge crossings for the new edges.
{
// original tri is and verts (a, b,c) and edges (a2b, b2c, c2a)
// is updated by the poke to have verts (a, b, new) and edges (a2b, b2new, new2a)
const FIndex3i T0EIDs = GetTriEdges ( NewTris [ 0 ] ) ;
const FIndex3i T1EIDs = GetTriEdges ( NewTris [ 1 ] ) ;
// set the correct number of surface edges the cross each new intrinsic edge.
NormalCoordinates . NormalCoord . InsertAt ( NewEdgeNormCoords [ 0 ] , T0EIDs [ 2 ] ) ; // a2new
NormalCoordinates . NormalCoord . InsertAt ( NewEdgeNormCoords [ 2 ] , T1EIDs [ 1 ] ) ; // b2new
NormalCoordinates . NormalCoord . InsertAt ( NewEdgeNormCoords [ 1 ] , T0EIDs [ 1 ] ) ; // c2new
}
// update the roundabout information.
{
// edge order: a2b, b2c, c2a
const FIndex3i OriginalRO = NormalCoordinates . RoundaboutOrder [ IntrinsicTID ] ;
// copy the roundabout information to the new triangles with default -1 for the new directed edges.
FIndex3i NewTrisRO [ 3 ] = { FIndex3i ( OriginalRO [ 0 ] , - 1 , - 1 ) // NewTri[0] edge order: a2b, b2new, new2a
, FIndex3i ( OriginalRO [ 1 ] , - 1 , - 1 ) // NewTri[1] edge order: b2c, c2new, new2b
, FIndex3i ( OriginalRO [ 2 ] , - 1 , - 1 ) // NewTri[2] edge order: c2a, a2new, new2c
} ;
// update roundabout order for any new directed edge that starts at an implicit vertex that is also a surface vertex
// Note: by construction the new intrinsic vertex does not correspond to a vertex on the surface mesh so we need only consider a2new, b2new, c2new.
const FSurfacePoint OriginalTriSPs [ 3 ] = { IntrinsicVertexPositions [ OriginalVIDs [ 0 ] ] ,
IntrinsicVertexPositions [ OriginalVIDs [ 1 ] ] ,
IntrinsicVertexPositions [ OriginalVIDs [ 2 ] ] } ;
for ( int32 i = 0 ; i < 3 ; + + i )
{
if ( IsVertexPoint ( OriginalTriSPs [ i ] ) )
{
const FIndex3i TriEIDs = GetTriEdges ( NewTris [ i ] ) ;
const int32 Radj = NewTrisRO [ i ] [ 0 ] ; // {R_ab, R_bc, R_ca } for i = 0, 1, or 2
const int32 Eopp = NormalCoordinates . NumCornerEmanatingRefEdges ( TriEIDs , 0 ) ; // {Ebn_a, Ecn_b, Ean_c} for i = 0, 1, or 2
const int32 Rnew = ( Radj + Eopp ) % NormalCoordinates . RefVertDegree [ OriginalVIDs [ i ] ] ;
NewTrisRO [ ( i + 2 ) % 3 ] [ 1 ] = Rnew ;
}
}
NormalCoordinates . RoundaboutOrder . InsertAt ( NewTrisRO [ 0 ] , NewTris [ 0 ] ) ;
NormalCoordinates . RoundaboutOrder . InsertAt ( NewTrisRO [ 1 ] , NewTris [ 1 ] ) ;
NormalCoordinates . RoundaboutOrder . InsertAt ( NewTrisRO [ 2 ] , NewTris [ 2 ] ) ;
}
}
// update the position of the new vertex ( R3 position and the surface point )
bool bIsValid ;
const FVector3d R3Pos = IntrinsicCorrespondenceUtils : : AsR3Position ( PokedSurfacePoint , * NormalCoordinates . SurfaceMesh , bIsValid ) ;
Vertices [ NewVID ] = R3Pos ;
IntrinsicVertexPositions . InsertAt ( PokedSurfacePoint , NewVID ) ;
return EMeshResult : : Ok ;
}
UE : : Geometry : : EMeshResult UE : : Geometry : : FIntrinsicMesh : : SplitEdge ( int32 EdgeAB , FEdgeSplitInfo & SplitInfo , double SplitParameterT )
{
using namespace FIntrinsicMeshImplUtils ;
if ( ! IsEdge ( EdgeAB ) )
{
return EMeshResult : : Failed_NotAnEdge ;
}
const FDynamicMesh3 & SurfaceMesh = * NormalCoordinates . SurfaceMesh ;
const FEdge OriginalEdge = GetEdge ( EdgeAB ) ;
// is the target intrinsic edge equivalent to a segment of a surface edge?
const bool bIsOnSurfaceEdge = NormalCoordinates . IsSurfaceEdgeSegment ( EdgeAB ) ;
// say new vertex is f.
const int32 TID0 = OriginalEdge . Tri [ 0 ] ;
const int32 IndexOfe = GetTriEdges ( TID0 ) . IndexOf ( EdgeAB ) ;
const int32 TID1 = OriginalEdge . Tri [ 1 ] ;
const bool bIsBoundary = ( TID1 = = - 1 ) ;
const int32 IndexOf1e = ( ! bIsBoundary ) ? GetTriEdges ( TID1 ) . IndexOf ( EdgeAB ) : - 1 ;
// Info about the original T0 tri, reordered to make the split edge the first edge..
const FIndex3i OriginalT0VIDs = Permute ( IndexOfe , GetTriangle ( TID0 ) ) ;
const FIndex3i OriginalT0Edges = Permute ( IndexOfe , GetTriEdges ( TID0 ) ) ;
const FVector3d OriginalT0EdgeLengths = Permute ( IndexOfe , GetTriEdgeLengths ( TID0 ) ) ; // as (|ab|, |bc|, |ca|)
// which, if any, of the original intrinsic edges are coincident with surface edges
// in the order { edgeAB, edgeBC, edgeCA, edgeDB }
const bool Kroneckers [ 4 ] = { NormalCoordinates . IsSurfaceEdgeSegment ( OriginalT0Edges [ 0 ] ) ,
NormalCoordinates . IsSurfaceEdgeSegment ( OriginalT0Edges [ 1 ] ) ,
NormalCoordinates . IsSurfaceEdgeSegment ( OriginalT0Edges [ 2 ] ) ,
( bIsBoundary ) ? false : NormalCoordinates . IsSurfaceEdgeSegment ( Permute ( IndexOf1e , GetTriEdges ( TID1 ) ) [ 2 ] ) } ;
const double IntrinsicEdgeSplitDistance = SplitParameterT * OriginalT0EdgeLengths [ 0 ] ;
// description of surface edge intersection with intrinsic edge AB
struct FEdgeDistanceCorrespondence
{
int32 SurfaceEID = - 1 ; // surface edge
double DistOnEdge ; // distance measured along the intrinsic edge
FSurfacePoint SurfacePoint ; // location on the surface mesh
} ;
auto AsR3Pos = [ & SurfaceMesh ] ( const FSurfacePoint & SP )
{
bool bTemp ;
return AsR3Position ( SP , SurfaceMesh , bTemp ) ;
} ;
// -- prior to actually doing the edge split, we find the two points (surface edge intersections) that bracket the proposed edge split location
// and pre-compute the normal coordinates for the 4 edges that are changed by the split
// initialize the bounding points with the ends of the edge being split.
FEdgeDistanceCorrespondence BoundingPoints [ 2 ] = { { - 1 , 0. , IntrinsicVertexPositions [ OriginalT0VIDs [ 0 ] ] } ,
{ - 1 , OriginalT0EdgeLengths [ 0 ] , IntrinsicVertexPositions [ OriginalT0VIDs [ 1 ] ] } } ;
int32 UpdatedNormCoord = 0 ; // after split edgeAB becomes edge [a, f]
int32 NewEdgeNormCoords [ 3 ] = { 0 , 0 , 0 } ; //N_fb, N_fc, N_fd
{
if ( NormalCoordinates . NumEdgeCrossing ( EdgeAB ) = = 0 ) // no surface edges cross the intrinsic edge being split
{
// potentially propagating -1 from NormalCoord[EdgeAB] may explicitly mark these as a segment of EdgeAB
UpdatedNormCoord = NormalCoordinates . NormalCoord [ EdgeAB ] ; // becomes edgeAF
NewEdgeNormCoords [ 0 ] = NormalCoordinates . NormalCoord [ EdgeAB ] ; // new edge, edgeFB
NewEdgeNormCoords [ 1 ] = FMath : : Max ( NormalCoordinates . NumEdgeCrossing ( OriginalT0Edges [ 1 ] ) , NormalCoordinates . NumEdgeCrossing ( OriginalT0Edges [ 2 ] ) ) ;
// poke makes t0 = {a, f, c} and t2 = { f, b, c} ( note: t1 = {f, a, d} t3 = f, d, b} don't exist in the boundary edge case )
if ( ! bIsBoundary )
{
// Info about the original T1 tri, reordered to make the split edge the first edge..
const FIndex3i OriginalT1Edges = Permute ( IndexOf1e , GetTriEdges ( TID1 ) ) ;
const FVector3d OriginalT1EdgeLengths = Permute ( IndexOf1e , GetTriEdgeLengths ( TID1 ) ) ; // as (|ba|, |ad|, |db})
NewEdgeNormCoords [ 2 ] = FMath : : Max ( NormalCoordinates . NumEdgeCrossing ( OriginalT1Edges [ 1 ] ) , NormalCoordinates . NumEdgeCrossing ( OriginalT1Edges [ 2 ] ) ) ;
}
}
else
{
const int32 IntrinsicEID = OriginalT0Edges [ 0 ] ;
const FIndex3i OriginalT1Edges = ( TID1 ! = - 1 ) ? Permute ( IndexOf1e , GetTriEdges ( TID1 ) ) : FIndex3i ( - 1 , - 1 , - 1 ) ;
// sequence of surface points corresponding to surface edges intersecting intrinsic edge ab.
// ordered relative to OriginalEdge.Tri[0];
const TArray < FSurfacePoint > ABEdgeAsSurfacePoints = [ & ]
{
const double CoalesceThreshold = 0. ;
const bool bReverse = false ;
return TraceEdge ( IntrinsicEID , CoalesceThreshold , bReverse ) ;
} ( ) ;
auto GetExitEdge = [ & ] ( const int32 TriID , const int32 P )
{
int32 ExitEID = - 1 ;
if ( TriID ! = - 1 )
{
const TTuple < int32 , int32 > ExitEIDandP = FNormalCoordSurfaceTraceImpl : : GetCrossingExit ( * this , NormalCoordinates , TriID , EdgeAB , P ) ;
ExitEID = ( ExitEIDandP . Get < 1 > ( ) ! = 0 ) ? ExitEIDandP . Get < 0 > ( ) : - 1 ;
}
return ExitEID ;
} ;
// account for surface edges that cross the intrinsic edgeAB at the endpoints and the new edges, ie edgeFC (or edgeFD)
{
const int32 Ebc_a = NormalCoordinates . NumCornerEmanatingRefEdges ( OriginalT0Edges , 0 ) ; // surface edges that start at 'a' and exit side bc
const int32 Eca_b = NormalCoordinates . NumCornerEmanatingRefEdges ( OriginalT0Edges , 1 ) ; // surface edges that start at 'b' and exit side ca
NewEdgeNormCoords [ 1 ] + = Eca_b + Eca_b ; // new edge - edgeFC
if ( ! bIsBoundary )
{
const int32 Ead_b = NormalCoordinates . NumCornerEmanatingRefEdges ( OriginalT1Edges , 0 ) ; // surface edges that start at 'b' and exit side ad
const int32 Edb_a = NormalCoordinates . NumCornerEmanatingRefEdges ( OriginalT1Edges , 1 ) ; // surface edges that start at 'a' and exit side db
NewEdgeNormCoords [ 2 ] + = Ead_b + Edb_a ; // new edge - edgeFD
}
}
// count the number of surface edges that cross both edgeAB and the edgeFC, and those that cross edgeAB and edgeFD
// Also identify the two surface edge crossing of edgeAB that bracket the vertex produced by the split.
{
// double buffer.
int32 Cur = 1 ;
FVector3d Positions [ 2 ] ;
Positions [ 0 ] = AsR3Pos ( ABEdgeAsSurfacePoints [ 0 ] ) ;
const int32 NumSPoints = ABEdgeAsSurfacePoints . Num ( ) ;
double AccumulatedDistance = 0 ;
for ( int32 i = 1 ; i < NumSPoints - 1 ; + + i ) // the first and last points are the implicit edge start/end.
{
const FSurfacePoint & SurfacePoint = ABEdgeAsSurfacePoints [ i ] ;
Positions [ Cur ] = AsR3Pos ( SurfacePoint ) ;
const double LineElementLength = ( Positions [ Cur ] - Positions [ 1 - Cur ] ) . Length ( ) ;
AccumulatedDistance + = LineElementLength ;
Cur = 1 - Cur ; // swap double buffer.
// which side does this surface edge exit?
const int32 T0ExitEID = GetExitEdge ( TID0 , i ) ;
const int32 T1ExitEID = GetExitEdge ( TID1 , NumSPoints - i ) ;
if ( AccumulatedDistance < IntrinsicEdgeSplitDistance ) // crossing to left of split
{
UpdatedNormCoord + = 1 ; // edgeAF
checkSlow ( IsEdgePoint ( SurfacePoint ) ) ;
int32 SurfaceEID = SurfacePoint . Position . EdgePosition . EdgeID ;
BoundingPoints [ 0 ] = { SurfaceEID , AccumulatedDistance , SurfacePoint } ;
if ( OriginalT0Edges [ 1 ] = = T0ExitEID )
{
NewEdgeNormCoords [ 1 ] + = 1 ; // new edge - edgeFC
}
if ( T1ExitEID ! = - 1 & & OriginalT1Edges [ 2 ] = = T1ExitEID )
{
NewEdgeNormCoords [ 2 ] + = 1 ; // new edge - edgeFD
}
}
else // crossing to right of split
{
NewEdgeNormCoords [ 0 ] + = 1 ; // edgeFB
if ( AccumulatedDistance < BoundingPoints [ 1 ] . DistOnEdge )
{
checkSlow ( IsEdgePoint ( SurfacePoint ) ) ;
int32 SurfaceEID = SurfacePoint . Position . EdgePosition . EdgeID ;
BoundingPoints [ 1 ] = { SurfaceEID , AccumulatedDistance , SurfacePoint } ;
}
if ( OriginalT0Edges [ 2 ] = = T0ExitEID )
{
NewEdgeNormCoords [ 1 ] + = 1 ; // new edge - edgeFC
}
if ( T1ExitEID ! = - 1 & & OriginalT1Edges [ 1 ] = = T1ExitEID )
{
NewEdgeNormCoords [ 2 ] + = 1 ; // new edge - edgeFD
}
}
}
}
}
}
// do the actual edge split
UE : : Geometry : : EMeshResult SplitResult = MyBase : : SplitEdge ( EdgeAB , SplitInfo , SplitParameterT ) ;
if ( SplitResult ! = EMeshResult : : Ok )
{
return SplitResult ;
}
// update the number of edge crossings for the edge configuration resulting from the split
// original edge[a,b] is now [a,f] new edges are [f,b], [f,c] and [f,d]
// Note: this must be done before updating the roundabout information
NormalCoordinates . NormalCoord [ SplitInfo . OriginalEdge ] = UpdatedNormCoord ;
NormalCoordinates . NormalCoord . InsertAt ( NewEdgeNormCoords [ 0 ] , SplitInfo . NewEdges [ 0 ] ) ;
NormalCoordinates . NormalCoord . InsertAt ( NewEdgeNormCoords [ 1 ] , SplitInfo . NewEdges [ 1 ] ) ;
if ( ! bIsBoundary )
{
NormalCoordinates . NormalCoord . InsertAt ( NewEdgeNormCoords [ 2 ] , SplitInfo . NewEdges [ 2 ] ) ;
}
// fix the roundabout orders
if ( ! bIsBoundary ) // for 4 triangles.
{
const FIndex3i OriginalT0RO = Permute ( IndexOfe , NormalCoordinates . RoundaboutOrder [ TID0 ] ) ; // {ROa2b, ROb2c, ROc2a}
const FIndex3i OriginalT1RO = Permute ( IndexOf1e , NormalCoordinates . RoundaboutOrder [ TID1 ] ) ; // {ROb2a, ROa2d, ROd2b}
const FIndex3i OriginalT1VIDs = Permute ( IndexOf1e , GetTriangle ( TID1 ) ) ; // {b, a, d}
// initialize with the correct RO information for the outer diamond c2a, a2d, b2c, d2b
const int32 Rc2a = OriginalT0RO [ 2 ] ;
const int32 Ra2d = OriginalT1RO [ 1 ] ;
const int32 Rd2b = OriginalT1RO [ 2 ] ;
const int32 Rb2c = OriginalT0RO [ 1 ] ;
const int32 Ra2b = OriginalT0RO [ 0 ] ;
// T0 and T1 after split {a,f, c} and {f, a, d}.. although permuted to the original order.
FIndex3i UpdatedRO [ 2 ] = { FIndex3i ( - 1 , - 1 , Rc2a ) ,
FIndex3i ( - 1 , Ra2d , - 1 ) } ;
// tris {f,b, c} and {f, d, b}
FIndex3i NewTrisRO [ 2 ] = { FIndex3i ( - 1 , Rb2c , - 1 ) ,
FIndex3i ( - 1 , Rd2b , - 1 ) } ;
// correct the roundabout order consistent with the new point f being just inside the original T0
// directed edge a2f RO: UpdatedRO[0][0]
{
const int32 A = OriginalT0VIDs [ 0 ] ;
if ( IsVertexPoint ( IntrinsicVertexPositions [ A ] ) )
{
UpdatedRO [ 0 ] [ 0 ] = Ra2b ;
}
}
// directed edge b2f RO: NewTriRO[1][2]
{
const int32 B = OriginalT0VIDs [ 1 ] ;
if ( IsVertexPoint ( IntrinsicVertexPositions [ B ] ) )
{
const FIndex3i NewTri0Edges = GetTriEdges ( SplitInfo . NewTriangles [ 0 ] ) ;
const int32 Kronecker_bc = ( int32 ) Kroneckers [ 1 ] ;
const int32 Ecf_b = NormalCoordinates . NumCornerEmanatingRefEdges ( NewTri0Edges , 1 ) ; // num surface edges coming from b and crossing edgeCF.
NewTrisRO [ 1 ] [ 2 ] = ( Rb2c + Ecf_b + Kronecker_bc ) % NormalCoordinates . RefVertDegree [ B ] ;
}
}
// directed edges c2f RO: NewTriRO[0][2]
{
const int32 C = OriginalT0VIDs [ 2 ] ;
if ( IsVertexPoint ( IntrinsicVertexPositions [ C ] ) )
{
const FIndex3i T0UpdatedEdges = Permute ( IndexOfe , GetTriEdges ( TID0 ) ) ;
const int32 Kronecker_ca = ( int32 ) Kroneckers [ 2 ] ;
const int32 Eaf_c = NormalCoordinates . NumCornerEmanatingRefEdges ( T0UpdatedEdges , 2 ) ; // num surface edges coming from c and crossing edgeAF.
const int32 Rc2f = ( Rc2a + Eaf_c + Kronecker_ca ) % NormalCoordinates . RefVertDegree [ C ] ;
NewTrisRO [ 0 ] [ 2 ] = Rc2f ;
}
}
// directed edge d2f RO: UpdatedRO[1][2]
{
const int32 D = OriginalT1VIDs [ 2 ] ;
if ( IsVertexPoint ( IntrinsicVertexPositions [ D ] ) )
{
const FIndex3i NewTri1Edges = GetTriEdges ( SplitInfo . NewTriangles [ 1 ] ) ;
const int32 Kronecker_bd = ( int32 ) Kroneckers [ 3 ] ;
const int32 Ebf_d = NormalCoordinates . NumCornerEmanatingRefEdges ( NewTri1Edges , 1 ) ; // num surface edges coming from d and crossing edgeBF
const int32 Rd2f = ( Rd2b + Ebf_d + Kronecker_bd ) % NormalCoordinates . RefVertDegree [ D ] ;
UpdatedRO [ 1 ] [ 2 ] = Rd2f ;
}
}
NormalCoordinates . RoundaboutOrder [ TID0 ] = Permute ( ( 3 - IndexOfe ) % 3 , UpdatedRO [ 0 ] ) ;
NormalCoordinates . RoundaboutOrder [ TID1 ] = Permute ( ( 3 - IndexOf1e ) % 3 , UpdatedRO [ 1 ] ) ;
NormalCoordinates . RoundaboutOrder . InsertAt ( NewTrisRO [ 0 ] , SplitInfo . NewTriangles [ 0 ] ) ;
NormalCoordinates . RoundaboutOrder . InsertAt ( NewTrisRO [ 1 ] , SplitInfo . NewTriangles [ 1 ] ) ;
}
else // only 2 triangles.
{
const FIndex3i OriginalT0RO = Permute ( IndexOfe , NormalCoordinates . RoundaboutOrder [ TID0 ] ) ;
// initialize with the correct RO information for the outer diamond c2a, a2d, b2c, d2b
const int32 Ra2b = OriginalT0RO [ 0 ] ;
const int32 Rb2c = OriginalT0RO [ 1 ] ;
const int32 Rc2a = OriginalT0RO [ 2 ] ;
FIndex3i UpdatedRO = FIndex3i ( - 1 , - 1 , Rc2a ) ; // {ROa2f, ROf2c, ROc2a}
FIndex3i NewTriRO = FIndex3i ( - 1 , Rb2c , - 1 ) ; // {Rof2b, ROb2c, ROc2f} tris {f,b, c}
// directed edge a2f RO: UpdatedRO[0][0]
{
const int32 A = OriginalT0VIDs [ 0 ] ;
if ( IsVertexPoint ( IntrinsicVertexPositions [ A ] ) )
{
UpdatedRO [ 0 ] = Ra2b ;
}
}
// directed edges c2f RO: NewTriRO[2]
{
const int32 C = OriginalT0VIDs [ 2 ] ;
if ( IsVertexPoint ( IntrinsicVertexPositions [ C ] ) )
{
const FIndex3i T0UpdatedEdges = Permute ( IndexOfe , GetTriEdges ( TID0 ) ) ;
const int32 Kronecker_ca = ( int32 ) Kroneckers [ 2 ] ;
const int32 Eaf_c = NormalCoordinates . NumCornerEmanatingRefEdges ( T0UpdatedEdges , 2 ) ; // num surface edges coming from c and crossing edgeAF.
const int32 Rc2f = ( Rc2a + Eaf_c + Kronecker_ca ) % NormalCoordinates . RefVertDegree [ C ] ;
NewTriRO [ 2 ] = Rc2f ;
}
}
NormalCoordinates . RoundaboutOrder [ TID0 ] = Permute ( ( 3 - IndexOfe ) % 3 , UpdatedRO ) ;
NormalCoordinates . RoundaboutOrder . InsertAt ( NewTriRO , SplitInfo . NewTriangles [ 0 ] ) ;
}
// update the location of the new vertex, both as surface position and R3.
{
// the two surface points on the split edge that bracket the new vertex
const FSurfacePoint & LeftSP = BoundingPoints [ 0 ] . SurfacePoint ;
const FSurfacePoint & RightSP = BoundingPoints [ 1 ] . SurfacePoint ;
if ( bIsOnSurfaceEdge )
{
// the bounding surface points are either surface vert points or surface edge points
// find the surface edge
const int32 SurfaceEID = [ & ]
{
int32 SurfaceEID = - 1 ;
if ( IsEdgePoint ( LeftSP ) )
{
SurfaceEID = LeftSP . Position . EdgePosition . EdgeID ;
}
else if ( IsEdgePoint ( RightSP ) )
{
SurfaceEID = RightSP . Position . EdgePosition . EdgeID ;
}
else
{
// neither bounding point was an "edge" point,
// so they must both be "vertex" points
checkSlow ( IsVertexPoint ( LeftSP ) & & IsVertexPoint ( RightSP ) ) ;
const int32 SurfaceVIDs [ 2 ] = { LeftSP . Position . VertexPosition . VID , RightSP . Position . VertexPosition . VID } ;
SurfaceEID = SurfaceMesh . FindEdge ( SurfaceVIDs [ 0 ] , SurfaceVIDs [ 1 ] ) ;
checkSlow ( SurfaceEID ! = - 1 ) ;
}
return SurfaceEID ;
} ( ) ;
// compute alpha location relative to the surface edge.
const FEdge SurfaceEdge = SurfaceMesh . GetEdge ( SurfaceEID ) ;
const FIndex2i SurfaceEdgeV = SurfaceEdge . Vert ;
// orient the surface edge in the same direction as the intrinsic edge ( the intrinsic edge was ordered by its T0)
// note: the intrinsic edge vertices can not be the same because the intrinsic edge is a segment of the surface edge
const int32 StartV = [ & ]
{
if ( IsVertexPoint ( LeftSP ) )
{
return ( int32 ) ( LeftSP . Position . VertexPosition . VID = = SurfaceEdgeV . B ) ;
}
else if ( IsVertexPoint ( RightSP ) )
{
return ( int32 ) ( RightSP . Position . VertexPosition . VID ! = SurfaceEdgeV . B ) ;
}
checkSlow ( IsEdgePoint ( RightSP ) & & IsEdgePoint ( LeftSP ) ) ;
return ( int32 ) ( RightSP . Position . EdgePosition . Alpha > LeftSP . Position . EdgePosition . Alpha ) ;
} ( ) ;
// surface edge end points, ordered correctly.
const FVector3d SurfaceEdgeEndPts [ 2 ] = { SurfaceMesh . GetVertex ( SurfaceEdgeV [ StartV ] ) , SurfaceMesh . GetVertex ( SurfaceEdgeV [ 1 - StartV ] ) } ;
const double SurfaceEdgeLength = ( SurfaceEdgeEndPts [ 1 ] - SurfaceEdgeEndPts [ 0 ] ) . Length ( ) ;
const double SurfaceDistToSplit = ( AsR3Pos ( LeftSP ) - SurfaceEdgeEndPts [ 0 ] ) . Length ( ) + IntrinsicEdgeSplitDistance ;
const double R3Alpha = 1. - SurfaceDistToSplit / SurfaceEdgeLength ;
checkSlow ( SurfaceDistToSplit < = SurfaceEdgeLength ) ;
// compute the R3 position of the new vertex
const FVector3d R3Pos = R3Alpha * SurfaceEdgeEndPts [ 0 ] + ( 1. - R3Alpha ) * SurfaceEdgeEndPts [ 1 ] ;
// record the R3 position and the surface position.
Vertices [ SplitInfo . NewVertex ] = R3Pos ;
const double SurfaceAlpha = ( StartV = = 0 ) ? R3Alpha : 1. - R3Alpha ;
IntrinsicVertexPositions . InsertAt ( FSurfacePoint ( SurfaceEID , SurfaceAlpha ) , SplitInfo . NewVertex ) ;
}
else
{
// compute the r3 positions of the boundary points
const FVector3d LeftR3 = AsR3Pos ( LeftSP ) ;
const FVector3d RightR3 = AsR3Pos ( RightSP ) ;
// compute the local alpha of the new vertex between the boundary points
const double BoundingPtsSeperation = ( LeftR3 - RightR3 ) . Length ( ) ;
const double SplitToRightBoundary = BoundingPoints [ 1 ] . DistOnEdge - IntrinsicEdgeSplitDistance ;
const double Alpha = SplitToRightBoundary / BoundingPtsSeperation ;
checkSlow ( Alpha > = 0. & & Alpha < = 1. ) ;
// find the surface triangle the new vertex lives on
const int32 SurfaceTID = [ & ]
{
int32 TID = - 1 ;
if ( IsFacePoint ( LeftSP ) )
{
TID = LeftSP . Position . TriPosition . TriID ;
}
else if ( IsFacePoint ( RightSP ) )
{
TID = RightSP . Position . TriPosition . TriID ;
}
if ( TID = = - 1 )
{
TArray < int32 > AdjSurfaceTIDs = GetAdjacentTriangles ( LeftSP , SurfaceMesh ) ;
for ( const int32 CandidtateTID : AdjSurfaceTIDs )
{
if ( IsOnSurfaceTriangle ( RightSP , CandidtateTID , SurfaceMesh ) )
{
TID = CandidtateTID ;
break ;
}
}
}
return TID ;
} ( ) ;
checkSlow ( SurfaceTID ! = - 1 ) ;
// compute the barycentric coordinates of the boundary points relative to the surface triangle
const FVector3d LeftBC = AsBarycenteric ( LeftSP , SurfaceTID , SurfaceMesh ) ;
const FVector3d RightBC = AsBarycenteric ( RightSP , SurfaceTID , SurfaceMesh ) ;
// compute the R3 and Barycentric location of the new vertex
const FVector3d R3Pos = Alpha * LeftR3 + ( 1. - Alpha ) * RightR3 ;
const FVector3d SurfaceBCPos = Alpha * LeftBC + ( 1. - Alpha ) * RightBC ;
// record the R3 position and the surface position.
Vertices [ SplitInfo . NewVertex ] = R3Pos ;
IntrinsicVertexPositions . InsertAt ( FSurfacePoint ( SurfaceTID , SurfaceBCPos ) , SplitInfo . NewVertex ) ;
}
}
return EMeshResult : : Ok ;
2021-11-10 14:22:10 -05:00
}