2019-12-27 09:26:59 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
2019-09-10 11:35:20 -04:00
# include "ConstrainedDelaunay2.h"
2022-04-05 14:07:13 -04:00
# include "CompGeom/Delaunay2.h"
2020-01-23 16:28:59 -05:00
# include "Async/ParallelFor.h"
2022-04-05 14:07:13 -04:00
2019-09-10 11:35:20 -04:00
2021-03-09 19:33:56 -04:00
using namespace UE : : Geometry ;
2019-09-10 11:35:20 -04:00
//namespace
//{
//#define DEBUG_FILE_DUMPING 1
//#ifndef DEBUG_FILE_DUMPING
// void DumpDelaunayInputForDebugAsOBJ(const FConstrainedDelaunay2d& Delaunay, const FString& PathBase)
// {
// }
// void DumpDelaunayTriangulationForDebug(const FConstrainedDelaunay2d& Delaunay, const FString& PathBase)
// {
// }
//#else
//#include <fstream>
// static int num = 0;
// template <typename RealType>
// void DumpDelaunayInputForDebugAsOBJ(const TConstrainedDelaunay2<RealType>& Delaunay, const FString& PathBase)
// //void DumpGraphForDebugAsOBJ(const FDynamicGraph2d& Graph, const FString& PathBase)
// {
// num++;
// FString Path = PathBase + FString::FromInt(num) + ".obj";
// std::ofstream f(*Path);
//
// for (int32 VertexIdx = 0; VertexIdx < Delaunay.Vertices.Num(); VertexIdx++)
// {
2021-08-30 18:03:07 -04:00
// const TVector2<RealType>& Vertex = Delaunay.Vertices[VertexIdx];
2019-09-10 11:35:20 -04:00
// f << "v " << Vertex.X << " " << Vertex.Y << " 0" << std::endl;
// }
// for (int32 VertexIdx = 0; VertexIdx < Delaunay.Vertices.Num(); VertexIdx++)
// {
2021-08-30 18:03:07 -04:00
// const TVector2<RealType>& Vertex = Delaunay.Vertices[VertexIdx];
2019-09-10 11:35:20 -04:00
// f << "v " << Vertex.X << " " << Vertex.Y << " .5" << std::endl;
// }
// for (const FIndex2i& Edge : Delaunay.Edges)
// {
// f << "f " << Edge.A + 1 << " " << Edge.B + 1 << " " << 1 + Edge.A + Delaunay.Vertices.Num() << std::endl;
// }
// f.close();
// }
// //void DumpTriangulationForDebug(const FDynamicGraph2d& Graph, const TArray<FIntVector>& Triangles, const FString& PathBase)
// template <typename RealType>
// void DumpDelaunayTriangulationForDebug(const TConstrainedDelaunay2<RealType>& Delaunay, const FString& PathBase)
// {
// num++;
// FString Path = PathBase + FString::FromInt(num) + ".obj";
// std::ofstream f(*Path);
// for (int32 VertexIdx = 0; VertexIdx < Delaunay.Vertices.Num(); VertexIdx++)
// {
2021-08-30 18:03:07 -04:00
// const TVector2<RealType>& Vertex = Delaunay.Vertices[VertexIdx];
2019-09-10 11:35:20 -04:00
// f << "v " << Vertex.X << " " << Vertex.Y << " 0" << std::endl;
// }
// for (const FIndex3i& Tri : Delaunay.Triangles)
// {
// f << "f " << 1 + Tri.A << " " << 1 + Tri.B << " " << 1 + Tri.C << std::endl;
// }
// f.close();
// }
//#endif
//}
void AddOrderedEdge ( TMap < TPair < int , int > , bool > & EdgeMap , int VertA , int VertB )
{
bool bReversed = VertA > VertB ;
if ( bReversed )
{
Swap ( VertA , VertB ) ;
}
EdgeMap . Add ( TPair < int , int > ( VertA , VertB ) , bReversed ) ;
}
/**
* Compute the change in winding number from crossing an oriented edge connecting VertA to VertB
*
* @ param EdgeMap Map of known edges & orientations
* @ param VertA First vertex on edge
* @ param VertB Second vertex on edge
* @ return - 1 if reverse edge ( B - A ) found , 1 if forward edge ( A - B ) found , 0 otherwise
*/
int WindingAcross ( const TMap < TPair < int , int > , bool > & EdgeMap , int VertA , int VertB )
{
bool bReversed = VertA > VertB ;
if ( bReversed )
{
Swap ( VertA , VertB ) ;
}
TPair < int , int > EdgeKey ( VertA , VertB ) ;
const bool * bFoundReversed = EdgeMap . Find ( EdgeKey ) ;
if ( ! bFoundReversed )
{
return 0 ;
}
bool bSameDir = bReversed = = * bFoundReversed ;
return bSameDir ? 1 : - 1 ;
}
/**
* Check if any edge in edge map connects VertA to VertB ( or VertB to VertA )
*
* @ param EdgeMap Map of known edges & orientations
* @ param VertA First vertex on edge
* @ param VertB Second vertex on edge
* @ return true if VertA and VertB are connected by an edge ( in either direction )
*/
bool HasUnorderedEdge ( const TMap < TPair < int , int > , bool > & EdgeMap , int VertA , int VertB )
{
return EdgeMap . Contains ( TPair < int , int > ( FMath : : Min ( VertA , VertB ) , FMath : : Max ( VertA , VertB ) ) ) ;
}
2020-01-23 16:28:59 -05:00
namespace ConstrainedDelaunay2Internal
{
template < class RealType >
2022-04-05 14:07:13 -04:00
void SplitBowtiesHelper ( TArray < TPair < int , int > > & NeedUpdates , TArray < TVector2 < RealType > > & Vertices , TArray < int8 > & Keep , const TArray < FIndex3i > & Indices , const TArray < FIndex3i > & Adj )
2020-01-23 16:28:59 -05:00
{
2022-04-05 14:07:13 -04:00
int TriNum = Adj . Num ( ) ;
2020-01-23 16:28:59 -05:00
int32 OrigNumVertices = Vertices . Num ( ) ;
// track all wedge verts that are seen by walking local tris
auto OtherEdgeOnTri = [ & Indices ] ( int VertID , int TriID , int EdgeIdx )
{
int StepNext = 1 ;
2022-04-05 14:07:13 -04:00
if ( Indices [ TriID ] [ EdgeIdx ] = = VertID ) {
2020-01-23 16:28:59 -05:00
StepNext = 2 ;
}
return ( EdgeIdx + StepNext ) % 3 ;
} ;
// helper to find new edge idx of the edge you crossed to go from FromTriID over to ToTriID
auto CrossEdge = [ & Adj ] ( int FromTriID , int ToTriID )
{
for ( int AdjEdgeIdx = 0 ; AdjEdgeIdx < 3 ; AdjEdgeIdx + + )
{
2022-04-05 14:07:13 -04:00
if ( Adj [ ToTriID ] [ AdjEdgeIdx ] = = FromTriID )
2020-01-23 16:28:59 -05:00
{
return AdjEdgeIdx ;
}
}
2022-02-02 05:54:21 -05:00
checkSlow ( false ) ;
2020-01-23 16:28:59 -05:00
return - 1 ;
} ;
auto GetVertSubIdx = [ & Indices ] ( int VertID , int TriID )
{
for ( int VertSubIdx = 0 ; VertSubIdx < 3 ; VertSubIdx + + )
{
2022-04-05 14:07:13 -04:00
if ( Indices [ TriID ] [ VertSubIdx ] = = VertID )
2020-01-23 16:28:59 -05:00
{
return VertSubIdx ;
}
}
2022-02-02 05:54:21 -05:00
checkSlow ( false ) ;
2020-01-23 16:28:59 -05:00
return - 1 ;
} ;
auto Walk = [ & Adj , & OtherEdgeOnTri ] ( int VertID , int TriID , int EdgeSubIdx )
{
int OtherEdgeSubIdx = OtherEdgeOnTri ( VertID , TriID , EdgeSubIdx ) ;
2022-04-05 14:07:13 -04:00
int AdjTri = Adj [ TriID ] [ OtherEdgeSubIdx ] ;
2020-01-23 16:28:59 -05:00
return AdjTri ;
} ;
TArray < bool > Seen , SeenSource ; Seen . SetNumZeroed ( TriNum * 3 ) ; SeenSource . SetNumZeroed ( Vertices . Num ( ) ) ;
for ( int TriID = 0 ; TriID < TriNum ; TriID + + )
{
if ( Keep [ TriID ] ! = 1 )
{
continue ;
}
for ( int SubIdx = 0 , OtherSubIdx = 2 ; SubIdx < 3 ; OtherSubIdx = SubIdx + + )
{
int WedgeIdx = TriID * 3 + SubIdx ;
2022-04-05 14:07:13 -04:00
int VertID = Indices [ TriID ] [ SubIdx ] ;
2020-01-23 16:28:59 -05:00
if ( Seen [ WedgeIdx ] ) // already been walked over & therefore covered by previous pass
{
continue ;
}
// if seen source but haven't seen specific wedge, then we need to duplicate the vertex and re-link
bool bSeenSource = SeenSource [ VertID ] ;
int NewVertID = - 1 ;
if ( bSeenSource )
{
2021-08-30 18:03:07 -04:00
TVector2 < RealType > VertexToCopy = Vertices [ VertID ] ;
2020-01-23 16:28:59 -05:00
NewVertID = Vertices . Add ( VertexToCopy ) ;
}
// process all triangles starting from the given tri ID and edge idx; return true if looped, false otherwise
auto WalkAll = [ & TriNum , & Indices , & Seen , & SeenSource , & bSeenSource , & NeedUpdates , & NewVertID , & Keep ,
& CrossEdge , & GetVertSubIdx , & Walk ] ( int WalkVertID , int WalkTriID , int WalkSubIdx )
{
int StartTriID = WalkTriID ;
int SafetyCounter = 0 ;
while ( true )
{
int VertSubIdx = GetVertSubIdx ( WalkVertID , WalkTriID ) ;
int WalkWedgeIdx = WalkTriID * 3 + VertSubIdx ;
ensure ( ! Seen [ WalkWedgeIdx ] ) ;
2022-04-05 14:07:13 -04:00
checkSlow ( Indices [ WalkTriID ] [ VertSubIdx ] = = WalkVertID ) ;
2020-01-23 16:28:59 -05:00
Seen [ WalkWedgeIdx ] = true ;
if ( bSeenSource )
{
NeedUpdates . Add ( TPair < int , int > ( WalkWedgeIdx , NewVertID ) ) ;
}
int NextTriID = Walk ( WalkVertID , WalkTriID , WalkSubIdx ) ;
if ( NextTriID < 0 | | Keep [ NextTriID ] ! = 1 )
{
return false ;
}
WalkSubIdx = CrossEdge ( WalkTriID , NextTriID ) ;
WalkTriID = NextTriID ;
if ( WalkTriID = = StartTriID )
{
return true ;
}
check ( SafetyCounter + + < TriNum * 2 ) ; // infinite loop catcher
}
} ;
bool bLooped = WalkAll ( VertID , TriID , SubIdx ) ;
if ( ! bLooped )
{
// if it didn't loop around, also walk the other direction
int OtherWayTriID = Walk ( VertID , TriID , OtherSubIdx ) ;
if ( OtherWayTriID > = 0 & & Keep [ OtherWayTriID ] = = 1 )
{
int OtherWayTriSubIdx = CrossEdge ( TriID , OtherWayTriID ) ;
ensure ( ! WalkAll ( VertID , OtherWayTriID , OtherWayTriSubIdx ) ) ;
}
}
SeenSource [ VertID ] = true ;
}
}
}
2022-04-05 14:07:13 -04:00
void BuildFinalTriangles ( TArray < FIndex3i > & Triangles , TArray < TPair < int , int > > & NeedUpdates , int & AddedVerticesStartIndex , TArray < int8 > & Keep , const TArray < FIndex3i > & Indices , int OrigNumVertices , bool bOutputCCW )
2020-01-23 16:28:59 -05:00
{
2022-04-05 14:07:13 -04:00
int TriNum = Indices . Num ( ) ;
2020-01-23 16:28:59 -05:00
// function to build output triangles out of an indices array
// normally called directly on the const indices from the CDT, but will be called on an updated copy if bowtie splits happen
2022-04-05 14:07:13 -04:00
auto BuildTriangles = [ & Triangles , & TriNum , & Keep , & bOutputCCW ] ( const TArray < FIndex3i > & IndicesIn )
2020-01-23 16:28:59 -05:00
{
for ( int i = 0 ; i < TriNum ; i + + )
{
if ( Keep [ i ] > 0 )
{
2022-04-05 14:07:13 -04:00
FIndex3i & Tri = Triangles . Emplace_GetRef ( IndicesIn [ i ] . A , IndicesIn [ i ] . B , IndicesIn [ i ] . C ) ;
2020-01-23 16:28:59 -05:00
if ( ! bOutputCCW )
{
Swap ( Tri . B , Tri . C ) ;
}
}
}
} ;
if ( NeedUpdates . Num ( ) > 0 )
{
2022-04-05 14:07:13 -04:00
TArray < FIndex3i > UpdatedIndices = Indices ;
2020-01-23 16:28:59 -05:00
for ( const TPair < int , int > & Update : NeedUpdates )
{
2022-04-05 14:07:13 -04:00
int TriID = ( int ) Update . Key / 3 ;
int SubIdx = Update . Key % 3 ;
UpdatedIndices [ TriID ] [ SubIdx ] = Update . Value ;
2020-01-23 16:28:59 -05:00
}
AddedVerticesStartIndex = OrigNumVertices ;
BuildTriangles ( UpdatedIndices ) ;
}
else
{
BuildTriangles ( Indices ) ;
}
}
}
template < class RealType >
2021-08-30 18:03:07 -04:00
bool TConstrainedDelaunay2 < RealType > : : Triangulate ( TFunctionRef < bool ( const TArray < TVector2 < RealType > > & , const FIndex3i & ) > KeepTriangle )
2020-01-23 16:28:59 -05:00
{
Triangles . Empty ( ) ;
2022-04-05 14:07:13 -04:00
FDelaunay2 Delaunay ;
Delaunay . bAutomaticallyFixEdgesToDuplicateVertices = true ;
if ( ! Delaunay . Triangulate ( Vertices ) )
2020-01-23 16:28:59 -05:00
{
return false ;
}
2022-04-05 14:07:13 -04:00
Delaunay . bKeepFastEdgeAdjacencyData = true ;
bool bEdgesFailed = ! Delaunay . ConstrainEdges ( Vertices , Edges ) ;
bool bHoleEdgesFailed = ! Delaunay . ConstrainEdges ( Vertices , HoleEdges ) ;
bool bBoundaryTrackingFailure = bEdgesFailed | | bHoleEdgesFailed ;
2020-01-23 16:28:59 -05:00
2022-04-05 14:07:13 -04:00
TArray < FIndex3i > Indices , Adj ;
Delaunay . GetTrianglesAndAdjacency ( Indices , Adj ) ;
2020-01-23 16:28:59 -05:00
2022-04-05 14:07:13 -04:00
int TriNum = Adj . Num ( ) ;
2020-01-23 16:28:59 -05:00
TArray < int8 > Keep ; // values: 0->unprocessed (delete), 1->yes keep, -1->processed, delete
Keep . SetNumZeroed ( TriNum ) ;
ParallelFor ( TriNum , [ this , & KeepTriangle , & Indices , & Keep ] ( int32 Index )
{
2022-04-05 14:07:13 -04:00
bool bKeepTri = KeepTriangle ( Vertices , Indices [ Index ] ) ;
2020-01-23 16:28:59 -05:00
Keep [ Index ] = bKeepTri ? 1 : - 1 ;
} ) ;
TArray < TPair < int , int > > NeedUpdates ; // stores all wedge indices and the corresponding new vertices they require
int32 OrigNumVertices = Vertices . Num ( ) ;
if ( bSplitBowties )
{
ConstrainedDelaunay2Internal : : SplitBowtiesHelper ( NeedUpdates , Vertices , Keep , Indices , Adj ) ;
}
ConstrainedDelaunay2Internal : : BuildFinalTriangles ( Triangles , NeedUpdates , AddedVerticesStartIndex , Keep , Indices , OrigNumVertices , bOutputCCW ) ;
return ! bBoundaryTrackingFailure ;
}
2019-09-10 11:35:20 -04:00
template < class RealType >
bool TConstrainedDelaunay2 < RealType > : : Triangulate ( )
{
Triangles . Empty ( ) ;
check ( FillRule < = EFillRule : : Odd | | bOrientedEdges ) ;
2022-04-05 14:07:13 -04:00
FDelaunay2 Delaunay ;
Delaunay . bAutomaticallyFixEdgesToDuplicateVertices = true ;
if ( ! Delaunay . Triangulate ( Vertices ) )
2019-09-10 11:35:20 -04:00
{
return false ;
}
2022-04-05 14:07:13 -04:00
Delaunay . bKeepFastEdgeAdjacencyData = true ;
Delaunay . bValidateEdges = false ; // edge validation will be done manually later
Delaunay . ConstrainEdges ( Vertices , Edges ) ;
Delaunay . ConstrainEdges ( Vertices , HoleEdges ) ;
2019-12-19 18:07:47 -05:00
2019-09-10 11:35:20 -04:00
TMap < TPair < int , int > , bool > BoundaryMap , HoleMap ; // tracks all the boundary edges as they are added, so we can later flood fill across them for inside/outside decisions
TMap < TPair < int , int > , bool > * EdgeAndHoleMaps [ 2 ] = { & BoundaryMap , & HoleMap } ;
bool bBoundaryTrackingFailure = false ;
TArray < FIndex2i > * InputEdgesAndHoles [ 2 ] = { & Edges , & HoleEdges } ;
for ( int EdgeOrHole = 0 ; EdgeOrHole < 2 ; EdgeOrHole + + )
{
TArray < FIndex2i > & Input = * InputEdgesAndHoles [ EdgeOrHole ] ;
TMap < TPair < int , int > , bool > & InputMap = * EdgeAndHoleMaps [ EdgeOrHole ] ;
2022-04-05 14:07:13 -04:00
for ( FIndex2i Edge : Input )
2019-09-10 11:35:20 -04:00
{
2022-04-05 14:07:13 -04:00
Delaunay . FixDuplicatesOnEdge ( Edge ) ;
if ( ! Delaunay . HasEdge ( Edge , false ) )
2019-09-10 11:35:20 -04:00
{
2022-02-02 05:54:21 -05:00
// Note the failed edge; we will try to proceed anyway, just without this edge. This can happen for example if the edge is exactly on top of another edge
2019-09-10 11:35:20 -04:00
bBoundaryTrackingFailure = true ;
}
else
{
2022-04-05 14:07:13 -04:00
AddOrderedEdge ( InputMap , Edge . A , Edge . B ) ;
2019-09-10 11:35:20 -04:00
}
}
}
2022-04-05 14:07:13 -04:00
TArray < FIndex3i > Indices , Adj ;
Delaunay . GetTrianglesAndAdjacency ( Indices , Adj ) ;
int TriNum = Adj . Num ( ) ;
2019-09-10 11:35:20 -04:00
TArray < int8 > Keep ; // values: 0->unprocessed (delete), 1->yes keep, -1->processed, delete
Keep . SetNumZeroed ( TriNum ) ;
TArray < TPair < int , int > > ToWalkQ ; // Pair of tri index, winding number
// seed the queue with all triangles that are on the boundary of the convex hull
// note: need *all* not just *one* because of the strategy of refusing to cross hole edges; if using pure winding number classification would just need one boundary triangle to start
for ( int TriIdx = 0 ; TriIdx < TriNum ; TriIdx + + )
{
for ( int SubIdx = 0 , NextIdx = 2 ; SubIdx < 3 ; NextIdx = SubIdx + + )
{
2022-04-05 14:07:13 -04:00
if ( Adj [ TriIdx ] [ NextIdx ] < 0 ) // on hull
2019-09-10 11:35:20 -04:00
{
2022-04-05 14:07:13 -04:00
int VertA = Indices [ TriIdx ] [ SubIdx ] , VertB = Indices [ TriIdx ] [ NextIdx ] ;
2019-09-10 11:35:20 -04:00
if ( HasUnorderedEdge ( HoleMap , VertA , VertB ) )
{
continue ; // cannot cross hole edges
}
// note we negate the winding across for these hull triangles because we're not actually crossing the edge; we're already on the 'inside' of the hull edge
int Winding = - WindingAcross ( BoundaryMap , VertA , VertB ) ;
ToWalkQ . Add ( TPair < int , int > ( TriIdx , Winding ) ) ;
Keep [ TriIdx ] = ClassifyFromRule ( Winding ) ? 1 : - 1 ;
break ; // don't check any more edges once in queue
}
}
}
int SelIdx = 0 ; // Index of item to Pop next; used to make the traversal less depth-first in shape, so a little more robust to bad data
while ( ToWalkQ . Num ( ) )
{
SelIdx = ( SelIdx + 1 ) % ToWalkQ . Num ( ) ;
TPair < int , int > TriWithWinding = ToWalkQ [ SelIdx ] ;
ToWalkQ . RemoveAtSwap ( SelIdx ) ;
2022-04-05 14:07:13 -04:00
int TriIdx = TriWithWinding . Key ;
2019-09-10 11:35:20 -04:00
int LastWinding = TriWithWinding . Value ;
for ( int SubIdx = 0 , NextIdx = 2 ; SubIdx < 3 ; NextIdx = SubIdx + + )
{
2022-04-05 14:07:13 -04:00
int VertA = Indices [ TriIdx ] [ SubIdx ] , VertB = Indices [ TriIdx ] [ NextIdx ] ;
2019-09-10 11:35:20 -04:00
if ( HasUnorderedEdge ( HoleMap , VertA , VertB ) )
{
continue ; // cannot cross hole edges
}
2022-04-05 14:07:13 -04:00
int AdjTri = Adj [ TriIdx ] [ NextIdx ] ;
2019-10-01 20:41:42 -04:00
if ( AdjTri > = 0 & & Keep [ AdjTri ] = = 0 )
2019-09-10 11:35:20 -04:00
{
int WindingChange = WindingAcross ( BoundaryMap , VertA , VertB ) ;
int Winding = LastWinding + WindingChange ;
ToWalkQ . Add ( TPair < int , int > ( AdjTri , Winding ) ) ;
Keep [ AdjTri ] = ClassifyFromRule ( Winding ) ? 1 : - 1 ;
}
}
}
2020-01-23 16:28:59 -05:00
2019-11-06 14:33:56 -05:00
TArray < TPair < int , int > > NeedUpdates ; // stores all wedge indices and the corresponding new vertices they require
int32 OrigNumVertices = Vertices . Num ( ) ;
if ( bSplitBowties )
{
2020-01-23 16:28:59 -05:00
ConstrainedDelaunay2Internal : : SplitBowtiesHelper ( NeedUpdates , Vertices , Keep , Indices , Adj ) ;
2019-09-10 11:35:20 -04:00
}
2019-11-06 14:33:56 -05:00
2020-01-23 16:28:59 -05:00
ConstrainedDelaunay2Internal : : BuildFinalTriangles ( Triangles , NeedUpdates , AddedVerticesStartIndex , Keep , Indices , OrigNumVertices , bOutputCCW ) ;
2019-11-06 14:33:56 -05:00
2019-09-10 11:35:20 -04:00
return ! bBoundaryTrackingFailure ;
}
2021-11-08 17:50:00 -05:00
2019-09-10 11:35:20 -04:00
template < typename RealType >
2021-03-09 19:33:56 -04:00
TArray < FIndex3i > GEOMETRYALGORITHMS_API UE : : Geometry : : ConstrainedDelaunayTriangulate ( const TGeneralPolygon2 < RealType > & GeneralPolygon )
2019-09-10 11:35:20 -04:00
{
TConstrainedDelaunay2 < RealType > Triangulation ;
Triangulation . FillRule = TConstrainedDelaunay2 < RealType > : : EFillRule : : Positive ;
Triangulation . Add ( GeneralPolygon ) ;
Triangulation . Triangulate ( ) ;
return Triangulation . Triangles ;
}
2021-11-08 17:50:00 -05:00
template < typename RealType >
TArray < FIndex3i > GEOMETRYALGORITHMS_API UE : : Geometry : : ConstrainedDelaunayTriangulateWithVertices ( const TGeneralPolygon2 < RealType > & GeneralPolygon , TArray < TVector2 < RealType > > & OutVertices )
{
TConstrainedDelaunay2 < RealType > Triangulation ;
Triangulation . FillRule = TConstrainedDelaunay2 < RealType > : : EFillRule : : Positive ;
Triangulation . Add ( GeneralPolygon ) ;
Triangulation . Triangulate ( ) ;
OutVertices = MoveTemp ( Triangulation . Vertices ) ;
return Triangulation . Triangles ;
}
2019-09-10 11:35:20 -04:00
2021-03-09 19:33:56 -04:00
namespace UE
{
namespace Geometry
{
2019-09-10 11:35:20 -04:00
2021-03-09 19:33:56 -04:00
template TArray < FIndex3i > GEOMETRYALGORITHMS_API UE : : Geometry : : ConstrainedDelaunayTriangulate ( const TGeneralPolygon2 < double > & GeneralPolygon ) ;
template TArray < FIndex3i > GEOMETRYALGORITHMS_API UE : : Geometry : : ConstrainedDelaunayTriangulate ( const TGeneralPolygon2 < float > & GeneralPolygon ) ;
2021-11-08 17:50:00 -05:00
template TArray < FIndex3i > GEOMETRYALGORITHMS_API UE : : Geometry : : ConstrainedDelaunayTriangulateWithVertices ( const TGeneralPolygon2 < double > & GeneralPolygon , TArray < TVector2 < double > > & Vertices ) ;
template TArray < FIndex3i > GEOMETRYALGORITHMS_API UE : : Geometry : : ConstrainedDelaunayTriangulateWithVertices ( const TGeneralPolygon2 < float > & GeneralPolygon , TArray < TVector2 < float > > & Vertices ) ;
2019-09-10 11:35:20 -04:00
template struct TConstrainedDelaunay2 < float > ;
template struct TConstrainedDelaunay2 < double > ;
2021-03-09 19:33:56 -04:00
} // end namespace UE::Geometry
} // end namespace UE