// Copyright Epic Games, Inc. All Rights Reserved. #include "Operations/RedGreenTessellation.h" #include "DynamicMesh/DynamicMesh3.h" #include "CompGeom/PolygonTriangulation.h" namespace UE { namespace Geometry { namespace RedGreenTessellationPatternHelpers { /* * Given a triangle and additional vertices for each triangle edge, produce a valid triangulation */ static void TriangulatePoly(const FIndex3i& Triangle, const TStaticArray, 3>& EdgePoints, TArray& Tris) { int32 NumSplitEdges = 0; int32 MaxEdgeTessellationLevel = 0; for (int32 EdgeIndex = 0; EdgeIndex < 3; ++EdgeIndex) { if (EdgePoints[EdgeIndex].Num() > 0) { ++NumSplitEdges; MaxEdgeTessellationLevel = FMath::Max(MaxEdgeTessellationLevel, EdgePoints[EdgeIndex].Num()); } } if (NumSplitEdges == 0) { Tris.Add({ Triangle[0], Triangle[1], Triangle[2] }); } else if (NumSplitEdges == 1 && MaxEdgeTessellationLevel == 1) { // Green triangle for (int32 TriVertIndex = 0; TriVertIndex < 3; ++TriVertIndex) { if (EdgePoints[TriVertIndex].Num() > 0) { ensure(EdgePoints[TriVertIndex].Num() == 1); const int32 A = Triangle[TriVertIndex]; const int32 B = Triangle[(TriVertIndex + 1) % 3]; const int32 C = Triangle[(TriVertIndex + 2) % 3]; const int32 M = EdgePoints[TriVertIndex][0]; Tris.Add({ A, M, C }); Tris.Add({ M, B, C }); break; } } } else if (NumSplitEdges == 2 && MaxEdgeTessellationLevel == 1) { // "Double green" (?) triangle for (int32 TriVertIndex = 0; TriVertIndex < 3; ++TriVertIndex) { const int32 Next = (TriVertIndex + 1) % 3; if (EdgePoints[TriVertIndex].Num() == 1 && EdgePoints[Next].Num() == 1) { const int32 A = Triangle[TriVertIndex]; const int32 B = Triangle[Next]; const int32 C = Triangle[(TriVertIndex + 2) % 3]; const int32 AB = EdgePoints[TriVertIndex][0]; const int32 BC = EdgePoints[Next][0]; Tris.Add({ A, AB, C }); Tris.Add({ AB, BC, C }); Tris.Add({ AB, B, BC }); break; } } } else if (NumSplitEdges == 3 && MaxEdgeTessellationLevel == 1) { // Red triangle ensure(EdgePoints[0].Num() == 1 && EdgePoints[1].Num() == 1 && EdgePoints[2].Num() == 1); const int32 A = Triangle[0]; const int32 B = Triangle[1]; const int32 C = Triangle[2]; const int32 AB = EdgePoints[0][0]; const int32 BC = EdgePoints[1][0]; const int32 CA = EdgePoints[2][0]; Tris.Add({ A, AB, CA }); Tris.Add({ AB, B, BC }); Tris.Add({ CA, BC, C }); Tris.Add({ AB, BC, CA }); } else { // Too complicated -- go full poly triangulation mode TArray Polygon; TArray PolygonVertices; const TStaticArray Corners = { FVector2d{0,0}, FVector2d{1,0}, FVector2d{0,1} }; for (int32 TriEdgeIndex = 0; TriEdgeIndex < 3; ++TriEdgeIndex) { PolygonVertices.Add(Corners[TriEdgeIndex]); Polygon.Add(Triangle[TriEdgeIndex]); const TArray& Edge = EdgePoints[TriEdgeIndex]; if (Edge.Num() > 0) { const double Dx = 1.0 / (Edge.Num() + 1); const FVector2d& ThisCorner = Corners[TriEdgeIndex]; const FVector2d& NextCorner = Corners[(TriEdgeIndex + 1) % 3]; for (int32 EdgeVertexIndex = 0; EdgeVertexIndex < Edge.Num(); ++EdgeVertexIndex) { Polygon.Add(Edge[EdgeVertexIndex]); const double Alpha = static_cast(EdgeVertexIndex + 1) * Dx; PolygonVertices.Add(ThisCorner + Alpha * (NextCorner - ThisCorner)); } } } const int32 ExpectedNumberOfTriangles = PolygonVertices.Num() - 2; // The input "polygon" is geometrically triangular by construction, so the resulting triangulation will have no foldovers or topological issues in 3D TArray LocalTris; constexpr bool bOrientAsHoleFill = false; PolygonTriangulation::TriangulateSimplePolygon(PolygonVertices, LocalTris, bOrientAsHoleFill); ensure(LocalTris.Num() == ExpectedNumberOfTriangles); for (const FIndex3i& LocalTri : LocalTris) { Tris.Add(FIndex3i{ Polygon[LocalTri[0]], Polygon[LocalTri[1]], Polygon[LocalTri[2]] }); } } } } // namespace RedGreenTessellationPatternHelpers // // FRedGreenTessellationPattern // /** A single iteration of Red-Green subdivision is shown here, where the center triangle is "red" subdivided (1 to 4), and the neighboring triangles are "green" subdivided (1 to 2) to maintain a conforming mesh (i.e. avoid T-junctions or cracks in the mesh.) O O / \ /|\ / \ / | \ / \ / | \ / \ / | \ / \ / | \ / \ / | \ / \ / | \ / \ / | \ / \ / | \ O\-----------------/O O\--------O--------/O / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / O/_______O \ / \ / \ / / \ /\ \ / \ / \ / / \ / \ \ / \ / \ / / \ / \ \ / \ / \ / / \ / \ \ / \ \ // \ \ \ O-------------------O-------------------O => O-------------------O--------------------O To achieve deeper levels of tessellation, we could repeatedly apply red-green over the entire mesh, however it can be more efficient to tessellate each triangle to its specified level in one shot. Here is an example of our triangle patch tessellation. The triangle has a specified tessellation level 2, but the neighbouring triangle below it has level 3, which necessitates additional triangles along the bottom edge. O O |\ | \ │ \ │ \ │ \ │ \ │ \ │ \ O O O---------O │ \ │\ │ \ │ \ │ \ │ \ │ \ │ \ │ \ │ \ │ \│ \ O O O---------O---------O │ \ │\ │\ │ \ │ \ │ \ │ \ │ \ │ \ │ \ │ \ │ \ │ \ │ \│ \│ \ O O O---------O---------O---------O │ \ │\\ │\\ │\\ │\\ │ \ │ \ \ │ \ \ │ \ \ │ \ \ │ \ │ \ \ │ \ \ │ \ \ │ \ \ │ \ │ \ \ │ \ \ │ \ \ │ \ \ O----*----O----*----O----*----O----*----O => O----*----O----*----O----*----O----*----O */ FRedGreenTessellationPattern::FRedGreenTessellationPattern(const FDynamicMesh3* InMesh, const TArray& InTriangleTessLevels) : FTessellationPattern(InMesh), TriangleLevels(InTriangleTessLevels) { } EOperationValidationResult FRedGreenTessellationPattern::Validate() const { if (!Mesh) { return EOperationValidationResult::Failed_UnknownReason; } if (TriangleLevels.Num() < Mesh->MaxTriangleID()) { // Too few triangle levels specified return EOperationValidationResult::Failed_UnknownReason; } return EOperationValidationResult::Ok; } int FRedGreenTessellationPattern::GetNumberOfNewVerticesForEdgePatch(const int InEdgeID) const { if (!Mesh->IsEdge(InEdgeID)) { return FTessellationPattern::InvalidIndex; } const FIndex2i EdgeTris = Mesh->GetEdgeT(InEdgeID); int32 MaxLevel = 0; if (EdgeTris[0] != FDynamicMesh3::InvalidID) { MaxLevel = FMath::Max(MaxLevel, TriangleLevels[EdgeTris[0]]); } if (EdgeTris[1] != FDynamicMesh3::InvalidID) { MaxLevel = FMath::Max(MaxLevel, TriangleLevels[EdgeTris[1]]); } return (1 << MaxLevel) - 1; } int FRedGreenTessellationPattern::GetNumberOfNewVerticesForTrianglePatch(const int InTriangleID) const { if (!Mesh->IsTriangle(InTriangleID)) { return FTessellationPattern::InvalidIndex; } const int32 Level = TriangleLevels[InTriangleID]; return ((1 << Level) - 1) * ((1 << Level) - 2) / 2; } int FRedGreenTessellationPattern::GetNumberOfPatchTriangles(const int InTriangleID) const { if (!Mesh->IsTriangle(InTriangleID)) { return FTessellationPattern::InvalidIndex; } const int32 Level = TriangleLevels[InTriangleID]; int32 NewTriangles = 1 << (2 * Level); const FIndex3i NeighborTris = Mesh->GetTriNeighbourTris(InTriangleID); for (int32 NIndex = 0; NIndex < 3; ++NIndex) { const int NeighborTri = NeighborTris[NIndex]; if (Mesh->IsTriangle(NeighborTri) && TriangleLevels[NeighborTri] > Level) { // We need to subdivide new segments along this edge with green triangles NewTriangles += (1 << TriangleLevels[NeighborTri]) - (1 << Level); } } return NewTriangles; } void FRedGreenTessellationPattern::TessellateEdgePatch(EdgePatch& EdgePatch) const { const int32 NumNewVertices = GetNumberOfNewVerticesForEdgePatch(EdgePatch.EdgeID); const float Param = 1.0f / (static_cast(NumNewVertices) + 1.0f); for (int NewVertex = 0; NewVertex < NumNewVertices; ++NewVertex) { EdgePatch.LinearCoord[NewVertex] = static_cast(NewVertex + 1) * Param; } } void FRedGreenTessellationPattern::TessellateTriPatch(TrianglePatch& TriPatch) const { if (TriPatch.Triangles.Num() == 1) { TriPatch.Triangles[0] = TriPatch.UVWCorners; return; } const int32 Level = TriangleLevels[TriPatch.TriangleID]; // Create a grid of vertices which will then be triangulated. The grid structure stores vertex indices // (Note the grid itself is triangular, only storing values at nodes where i + j < N) const int32 GridN = (1 << Level) + 1; // TODO: If using a TMap is slow, investigate creating a triangular grid structure which would allow more direct access. Maybe similar to TDenseGrid2 but only storing the lower half? TMap, int32> Grid; Grid.Reserve(GridN * GridN / 2); for (int32 I = 0; I < GridN; ++I) { for (int32 J = 0; J < GridN - I; ++J) { Grid.Add({I, J}) = -1; } } // generate interior (non-edge) vertices const float GridStep = 1.0f / static_cast(GridN - 1); int32 InteriorVertexIndex = 0; for (int32 I = 1; I < GridN - 1; ++I) { for (int32 J = 1; J < GridN - I - 1; ++J) { const float Bary1 = static_cast(I) * GridStep; const float Bary2 = static_cast(J) * GridStep; const float Bary0 = 1.0f - Bary1 - Bary2; TriPatch.BaryCoord[InteriorVertexIndex] = { Bary0, Bary1, Bary2 }; Grid[{I, J}] = TriPatch.VIDs[InteriorVertexIndex]; ++InteriorVertexIndex; } } // corners const int32 U = TriPatch.UVWCorners[0]; const int32 V = TriPatch.UVWCorners[1]; const int32 W = TriPatch.UVWCorners[2]; Grid[{0, 0}] = U; Grid[{GridN - 1, 0}] = V; Grid[{0, GridN - 1}] = W; // edges auto Reverse = [](const TArrayView& Edge) -> TArray { TArray RevEdge; for (int EV : Edge) { RevEdge.Add(EV); } Algo::Reverse(RevEdge); return RevEdge; }; const TArray EdgeUVSortedVIDs = TriPatch.UVEdge.bIsReversed ? Reverse(TriPatch.UVEdge.VIDs) : TArray(TriPatch.UVEdge.VIDs); const TArray EdgeVWSortedVIDs = TriPatch.VWEdge.bIsReversed ? Reverse(TriPatch.VWEdge.VIDs) : TArray(TriPatch.VWEdge.VIDs); const TArray EdgeUWSortedVIDs = TriPatch.UWEdge.bIsReversed ? Reverse(TriPatch.UWEdge.VIDs) : TArray(TriPatch.UWEdge.VIDs); const FIndex3i NeighborTris = Mesh->GetTriNeighbourTris(TriPatch.TriangleID); // Edge UV const int32 NumUVVertices = TriPatch.UVEdge.VIDs.Num(); // Whether the edge has extra vertices because the neighboring patch is higher level. If so, then we need to only grab every Nth vertex from the TriPatch Edge const bool bUVEdgeHasAdditionalVertices = (NumUVVertices > (1 << Level) - 1); const int32 NbrUVLevel = Mesh->IsTriangle(NeighborTris[0]) ? TriangleLevels[NeighborTris[0]] : Level; for (int32 I = 0; I < GridN - 2; ++I) { if (bUVEdgeHasAdditionalVertices) { checkf(NbrUVLevel >= Level, TEXT("bUVEdgeHasAdditionalVertices is true but Level > NbrUVLevel")); const int32 StepSize = (1 << (NbrUVLevel - Level)); Grid[{I + 1, 0}] = EdgeUVSortedVIDs[StepSize * (I + 1) - 1]; } else { Grid[{I + 1, 0}] = EdgeUVSortedVIDs[I]; } } // Edge UW const int32 NumUWVertices = TriPatch.UWEdge.VIDs.Num(); const bool bUWEdgeHasAdditionalVertices = (NumUWVertices > (1 << Level) - 1); const int32 NbrUWLevel = Mesh->IsTriangle(NeighborTris[2]) ? TriangleLevels[NeighborTris[2]] : Level; // Vertices stored in UWEdge initially run along the edge from vertex W to vertex U. But our grid places W at the (i=0, j=N-1) grid point. // So to fill in the i = 0 column from j = 0 to N-1 we need to traverse UWEdge in reverse for (int32 J = 0; J < GridN - 2; ++J) { if (bUWEdgeHasAdditionalVertices) { checkf(NbrUWLevel >= Level, TEXT("bUWEdgeHasAdditionalVertices is true but Level > NbrUWLevel")); const int32 StepSize = (1 << (NbrUWLevel - Level)); Grid[{0, J + 1}] = EdgeUWSortedVIDs[NumUWVertices - StepSize * (J + 1)]; } else { Grid[{0, J + 1}] = EdgeUWSortedVIDs[GridN - 3 - J]; } } // Edge VW const int32 NumVWVertices = TriPatch.VWEdge.VIDs.Num(); const bool bVWEdgeHasAdditionalVertices = (NumVWVertices > (1 << Level) - 1); const int32 NbrVWLevel = Mesh->IsTriangle(NeighborTris[1]) ? TriangleLevels[NeighborTris[1]] : Level; for (int32 J = 0; J < GridN - 2; ++J) { const int32 I = GridN - 1 - J; const TPair MapKey{ I - 1, J + 1 }; ensure(Grid[MapKey] == -1); if (bVWEdgeHasAdditionalVertices) { checkf(NbrVWLevel >= Level, TEXT("bVWEdgeHasAdditionalVertices is true but Level > NbrVWLevel")); const int32 StepSize = (1 << (NbrVWLevel - Level)); Grid[MapKey] = EdgeVWSortedVIDs[StepSize * (J + 1) - 1]; } else { Grid[MapKey] = EdgeVWSortedVIDs[J]; } } // triangulate each grid cell ensure(TriPatch.Triangles.Num() > 1); int32 TriangleIndex = 0; for (int I = 0; I < GridN - 1; ++I) { for (int J = 0; J < GridN - 1 - I; ++J) { FIndex3i GridTriangle = { Grid[{I, J}], Grid[{I + 1, J}], Grid[{I, J + 1}] }; ensure(GridTriangle[0] >= 0); ensure(GridTriangle[1] >= 0); ensure(GridTriangle[2] >= 0); auto FillEdgePoints = [&](const int32 NbrLevel, const int32 I, const TArray& InFullEdgePoints, TArray& EdgePoints, bool bGridCountReversed) { check(NbrLevel >= Level); // This lambda should only be called when we are adding additional points due to the neighbor having a higher subdivision level const int32 StepSize = (1 << (NbrLevel - Level)); int32 Start, End; if (!bGridCountReversed) { Start = I * StepSize; End = (I + 1) * StepSize - 1; for (int32 V = Start; V < End; ++V) { EdgePoints.Add(InFullEdgePoints[V]); } } else { const int J = GridN - I - 1; const int JNext = J - 1; Start = JNext * StepSize; End = J * StepSize - 1; for (int32 V = Start; V < End; ++V) { EdgePoints.Add(InFullEdgePoints[V]); } } ensure(EdgePoints.Num() == StepSize - 1); }; // If we are on an edge where the neighboring triangle has a higher tessellation level, we'll have more edge vertices we need to think about TStaticArray, 3> EdgePoints; // EdgePoints are new vertices in the UV, WU, and VW edges respectively. if (J == 0 && bUVEdgeHasAdditionalVertices) { FillEdgePoints(NbrUVLevel, I, EdgeUVSortedVIDs, EdgePoints[0], false); } if (I + J == GridN - 2 && bVWEdgeHasAdditionalVertices) { FillEdgePoints(NbrVWLevel, J, EdgeVWSortedVIDs, EdgePoints[1], false); } if (I == 0 && bUWEdgeHasAdditionalVertices) { FillEdgePoints(NbrUWLevel, J, EdgeUWSortedVIDs, EdgePoints[2], true); } // lower triangle(s) for the grid cell if ((EdgePoints[0].Num() == 0) && (EdgePoints[1].Num() == 0) && (EdgePoints[2].Num() == 0)) { // simple case, no extra triangles TriPatch.Triangles[TriangleIndex++] = GridTriangle; } else { // adjacent to a patch with higher tessellation level, need extra triangles TArray Tris; RedGreenTessellationPatternHelpers::TriangulatePoly(GridTriangle, EdgePoints, Tris); for (const FIndex3i& Tri : Tris) { ensure(Tri[0] >= 0); ensure(Tri[1] >= 0); ensure(Tri[2] >= 0); TriPatch.Triangles[TriangleIndex++] = Tri; } } // upper triangle for the grid cell if (I + J < GridN - 2) { const FIndex3i UpperTri = { Grid[{I + 1, J}], Grid[{I + 1, J + 1}], Grid[{I, J + 1}] }; ensure(UpperTri[0] >= 0); ensure(UpperTri[1] >= 0); ensure(UpperTri[2] >= 0); TriPatch.Triangles[TriangleIndex++] = UpperTri; } } } ensure(TriangleIndex == TriPatch.Triangles.Num()); } } // end namespace UE::Geometry } // end namespace UE #undef UE_API