Files
Jimmy Andrews 20f8e9cd7e Make a new triangulation fill mode that supports explicit boundary and hole edges
Update Arrangement2d's triangulation functions to use that fill mode, and also to be explicit about whether the boundary group is expected or not

#rb david.hill
#rb rinat.abdrashitov
#preflight 6269606c8c2782e4f2327a34

[CL 19941732 by Jimmy Andrews in ue5-main branch]
2022-04-27 12:30:18 -04:00

247 lines
7.3 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
// Port of geometry3Sharp Arrangement2d
#include "Arrangement2d.h"
#include "CompGeom/Delaunay2.h"
using namespace UE::Geometry;
namespace
{
//#define DEBUG_FILE_DUMPING 1
#ifndef DEBUG_FILE_DUMPING
void DumpGraphForDebug(const FDynamicGraph2d& Graph, const FString& PathBase)
{
}
void DumpGraphForDebugAsOBJ(const FDynamicGraph2d& Graph, const FString& PathBase)
{
}
void DumpTriangulationForDebug(const FDynamicGraph2d& Graph, const TArray<FIntVector>& Triangles, const FString& PathBase)
{
}
#else
#include <fstream>
static int num = 0;
void DumpGraphForDebug(const FDynamicGraph2d& Graph, const FString& PathBase)
{
num++;
FString Path = PathBase + FString::FromInt(num) + ".txt";
std::ofstream f(*Path);
for (int32 VertexIdx = 0; VertexIdx < Graph.MaxVertexID(); VertexIdx++)
{
const FVector2d& Vertex = Graph.GetVertex(VertexIdx);
f << "v " << Vertex.X << " " << Vertex.Y << " " << std::endl;
}
for (const FDynamicGraph::FEdge& Edge : Graph.Edges())
{
f << "e " << Edge.A << " " << Edge.B << std::endl;
}
f.close();
}
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 < Graph.MaxVertexID(); VertexIdx++)
{
const FVector2d& Vertex = Graph.GetVertex(VertexIdx);
f << "v " << Vertex.X << " " << Vertex.Y << " 0" << std::endl;
}
for (int32 VertexIdx = 0; VertexIdx < Graph.MaxVertexID(); VertexIdx++)
{
const FVector2d& Vertex = Graph.GetVertex(VertexIdx);
f << "v " << Vertex.X << " " << Vertex.Y << " .5" << std::endl;
}
for (const FDynamicGraph::FEdge& Edge : Graph.Edges())
{
f << "f " << Edge.A + 1 << " " << Edge.B + 1 << " " << 1 + Edge.A + Graph.MaxVertexID() << std::endl;
}
f.close();
}
void DumpTriangulationForDebug(const FDynamicGraph2d& Graph, const TArray<FIntVector>& Triangles, const FString& PathBase)
{
num++;
FString Path = PathBase + FString::FromInt(num) + ".obj";
std::ofstream f(*Path);
for (int32 VertexIdx = 0; VertexIdx < Graph.MaxVertexID(); VertexIdx++)
{
const FVector2d& Vertex = Graph.GetVertex(VertexIdx);
f << "v " << Vertex.X << " " << Vertex.Y << " 0" << std::endl;
}
for (const FIntVector& Tri : Triangles)
{
f << "f " << 1 + Tri.X << " " << 1 + Tri.Y << " " << 1 + Tri.Z << std::endl;
}
f.close();
}
#endif
}
bool FArrangement2d::AttemptTriangulate(TArray<FIntVector>& Triangles, TArray<int32>& SkippedEdges, int32 BoundaryEdgeGroupID)
{
// TODO: Currently this just constructs an FIndex3i array and then copies it to an FIntVector array; if we need to construct FIntVector versions faster, we could directly construct them here instead
// However this would require writing FIntVector versions of the GetTriangles functions in FDelaunay2; ideally we would instead mainly call the FIndex3i variant
TArray<FIndex3i> TrianglesInd3i;
bool bResult = TriangulateInternal(TrianglesInd3i, true, BoundaryEdgeGroupID, false, -1, true, &SkippedEdges);
Triangles.SetNumUninitialized(TrianglesInd3i.Num());
for (int32 Idx = 0; Idx < TrianglesInd3i.Num(); Idx++)
{
Triangles[Idx] = (FIntVector)TrianglesInd3i[Idx];
}
return bResult;
}
bool FArrangement2d::TriangulateInternal(TArray<FIndex3i>& Triangles, bool bHasBoundaryGroupID, int32 BoundaryEdgeGroupID, bool bHasHoleGroupID, int32 HoleEdgeGroupID, bool bLegacyKeepTrianglesIfBoundaryNotFound, TArray<int32>* SkippedEdges)
{
Triangles.Empty();
// A flat array of the vertices, copied out of the graph
TArray<FVector2d> InputVertices;
// If there are unused vertices, define a vertex index remapping so the delaunay code won't have to understand that
bool bNeedsRemap = Graph.MaxVertexID() != Graph.VertexCount();
TArray<int> InputIndices, OutputIndices;
if (bNeedsRemap)
{
InputIndices.SetNumZeroed(Graph.MaxVertexID());
OutputIndices.SetNumZeroed(Graph.VertexCount());
int TotalOffset = 0;
for (int i = 0; i < Graph.MaxVertexID(); i++)
{
if (Graph.IsVertex(i))
{
OutputIndices[i - TotalOffset] = i;
InputIndices[i] = i - TotalOffset;
FVector2d Vertex = Graph.GetVertex(i);
InputVertices.Add(Vertex);
}
else
{
InputIndices[i] = -1;
TotalOffset++;
}
}
}
else
{ // no index remapping needed; just copy the vertices
for (int i = 0; i < Graph.MaxVertexID(); i++)
{
FVector2d Vertex = Graph.GetVertex(i);
InputVertices.Add(Vertex);
}
}
FDelaunay2 Delaunay;
Delaunay.bAutomaticallyFixEdgesToDuplicateVertices = false; // Arrangement will remove duplicates already
if (!Delaunay.Triangulate(InputVertices))
{
return false;
}
Delaunay.bValidateEdges = false;
Delaunay.bKeepFastEdgeAdjacencyData = true;
bool bInsertConstraintFailure = false;
bool bBoundaryTrackingFailure = false;
TArray<FIndex2i> AllEdges, BoundaryEdges, HoleEdges;
for (int EdgeIdx : Graph.EdgeIndices())
{
FDynamicGraph::FEdge Edge = Graph.GetEdge(EdgeIdx);
if (bNeedsRemap)
{
Edge.A = InputIndices[Edge.A];
Edge.B = InputIndices[Edge.B];
}
FIndex2i& AddedEdge = AllEdges.Emplace_GetRef(Edge.A, Edge.B);
if (bHasBoundaryGroupID && Edge.Group == BoundaryEdgeGroupID)
{
BoundaryEdges.Add(AddedEdge);
}
if (bHasHoleGroupID && Edge.Group == HoleEdgeGroupID)
{
HoleEdges.Add(AddedEdge);
}
}
Delaunay.ConstrainEdges(InputVertices, AllEdges);
// Verify all edges after all constraints are in -- to ensure that inserted edges were also not removed by subsequent edge insertion
for (int EdgeIdx : Graph.EdgeIndices())
{
FDynamicGraph::FEdge Edge = Graph.GetEdge(EdgeIdx);
if (bNeedsRemap)
{
Edge.A = InputIndices[Edge.A];
Edge.B = InputIndices[Edge.B];
}
if (!Delaunay.HasEdge(FIndex2i(Edge.A, Edge.B), false))
{
bInsertConstraintFailure = true;
if (SkippedEdges)
{
SkippedEdges->Add(EdgeIdx);
}
if (Edge.Group == BoundaryEdgeGroupID)
{
bBoundaryTrackingFailure = true;
}
}
}
if (bHasBoundaryGroupID && (!bLegacyKeepTrianglesIfBoundaryNotFound || (!bBoundaryTrackingFailure && BoundaryEdges.Num() > 0)))
{
if (bHasHoleGroupID)
{
Delaunay.GetFilledTriangles(Triangles, BoundaryEdges, HoleEdges);
}
else
{
Triangles = Delaunay.GetFilledTriangles(BoundaryEdges, FDelaunay2::EFillMode::Solid);
}
}
else
{
Triangles = Delaunay.GetTriangles();
}
if (bNeedsRemap)
{
for (FIndex3i& Face : Triangles)
{
Face.A = OutputIndices[Face.A];
Face.B = OutputIndices[Face.B];
Face.C = OutputIndices[Face.C];
}
}
return !bInsertConstraintFailure;
}
bool FArrangement2d::AttemptTriangulate(TArray<FIndex3i>& Triangles, TArray<int32>& SkippedEdges, int32 BoundaryEdgeGroupID)
{
return TriangulateInternal(Triangles, true, BoundaryEdgeGroupID, false, -1, true, &SkippedEdges);
}
bool FArrangement2d::TriangulateWithBoundaryAndHoles(TArray<FIndex3i>& Triangles, int32 BoundaryEdgeGroupID, int32 HoleEdgeGroupID)
{
return TriangulateInternal(Triangles, true, BoundaryEdgeGroupID, true, HoleEdgeGroupID, false, nullptr);
}
bool FArrangement2d::TriangulateWithBoundary(TArray<FIndex3i>& Triangles, int32 BoundaryEdgeGroupID)
{
return TriangulateInternal(Triangles, true, BoundaryEdgeGroupID, false, -1, false, nullptr);
}
bool FArrangement2d::Triangulate(TArray<FIndex3i>& Triangles)
{
return TriangulateInternal(Triangles, false, -1, false, -1, false, nullptr);
}