// Copyright Epic Games, Inc. All Rights Reserved. #include "Operations/SelectiveTessellate.h" #include "VectorTypes.h" #include "DynamicMesh/DynamicMesh3.h" #include "Async/ParallelFor.h" #include "Util/ProgressCancel.h" #include "Distance/DistLine3Line3.h" #include "Misc/AssertionMacros.h" #include "Util/CompactMaps.h" #include "DynamicMesh/DynamicMeshAttributeSet.h" #include "DynamicMesh/DynamicVertexSkinWeightsAttribute.h" using namespace UE::Geometry; namespace SelectiveTessellateLocals { // Forward declare class FTessellationData; template class FOverlayTessellationData; bool ConstructTessellatedMesh(const FDynamicMesh3* Mesh, FProgressCancel* Progress, FTessellationData& TessData, FCompactMaps& CompactInfo, FDynamicMesh3* ResultMesh, FSelectiveTessellate::FTessellationInformation& TessInfo); template bool ConstructTessellatedOverlay(const FDynamicMesh3* Mesh, const TDynamicMeshOverlay* Overlay, FTessellationData& MeshTessData, FOverlayTessellationData& TessData, const FCompactMaps& CompactInfo, FProgressCancel* Progress, TDynamicMeshOverlay* ResultOverlay); // Utility functions template void LerpElements(const RealType* Element1, const RealType* Element2, RealType* OutElement, double Alpha) { Alpha = FMath::Clamp(Alpha, 0.0, 1.0); double OneMinusAlpha = 1.0 - Alpha; for (int Idx = 0; Idx < ElementSize; ++Idx) { OutElement[Idx] = RealType(OneMinusAlpha * double(Element1[Idx]) + Alpha * double(Element2[Idx])); } } template void BaryElements(const RealType* Element1, const RealType* Element2, const RealType* Element3, RealType* OutElement, double Alpha, double Beta, double Theta) { checkSlow(FMath::Abs(Alpha + Beta + Theta - 1.0) < KINDA_SMALL_NUMBER); for (int Idx = 0; Idx < ElementSize; ++Idx) { OutElement[Idx] = RealType(Alpha * double(Element1[Idx]) + Beta * double(Element2[Idx]) + Theta * double(Element3[Idx])); } }; /** * Manage data containing tessellation information. This includes barycentric coordinates of the new vertices, * new vertex/elemnent IDs and triangle indices. This class handles cases where 2 elements are stored per vertex. * By default we assume we are interpolating geometry data. Can subclass and implement the virtual methods * to handle other sources of data (see FOverlayTessellationData). * * Given an Edge or Triangle ID, allows to create a TArrayView for reading and writing to the data block * containing barycentric coordinates, vertex/element IDs or triangle data. */ class FTessellationData { public: // Track all of the edges and triangles that we need to tessellate. Allows for fast Contains() queries. TSet EdgesToTessellate; TSet TrianglesToTessellate; // Array of the new IDs for the vertices/elements added along the tessellated edges. Different edges can have a // different number of vertices/elements added. Each vertex can have 1 or 2 IDs added to handle the overlays. // Use the EdgeIDOffsets to figure out the starting point and the length of the data block containing all of // the IDs for the edge/triangle pair. Similar logic applies to EdgeCoord, InnerIDs, InnerCoord, TriangleIDs, // Triangles. // // Edge1 (seam edge) Edge2 Edge50 Edge51 // -------------------------------------------------------------------------- // EdgeIDs | ID1 ID2 : ID3 ID4 | ID5 ID6 | ... | ID100 | ID101 ID102 |.... // -------------------------------------------------------------------------- // / / // / EdgeIDOffsets[(Edge1 , Triangle2)][0] // EdgeIDOffsets[(Edge1 , Triangle1)][0] TArray EdgeIDs; // IDs of the vertices/elements added inside the tessellated edges TMap EdgeIDOffsets; // Maps a tuple (Edge ID, Triangle ID) to tuple of (offset, length) // numbers used to access a data block in the EdgeIDs array. TArray EdgeCoord; // Lerp coefficients of the vertices/elements added along the tessellated edges TMap EdgeCoordOffsets; // Maps Edge ID to tuple of (offset, length) numbers used to access a data // block in the EdgeCoord arrays. TArray InnerIDs; // IDs of the vertices/elements added inside the tessellated triangles TArray InnerCoord; // Barycentric coordinates of the vertices added inside the tessellated triangles TMap InnerOffsets; // Maps Triangle ID to tuple of (offset, length) numbers used to access // a data block in the InnerIDs and InnerCoord arrays. TArray TriangleIDs; // IDs of the new triangles that the original triangle is split into TArray Triangles; // Triangle indicies (i.e. vertex id, element id, etc) TMap TrianglesOffsets; // Maps Triangle ID to tuple of (offset, length) numbers used to access // a data block in the TriangleIDs and Triangles array. protected: const FDynamicMesh3* Mesh = nullptr; // If we are not working with overlays then we don't care which side of the edge we are working with constexpr static int AnyTriangleID = -1; // The starting ID for new vertices/elements to be added int MaxID = -1; public: FTessellationData(const FDynamicMesh3* InMesh) : Mesh(InMesh) { MaxID = Mesh->MaxVertexID(); } virtual ~FTessellationData() { } void Init(const FTessellationPattern* Pattern) { this->InitEdgeVertexBuffers(Pattern); this->InitTriVertexBuffers(Pattern); this->InitTrianglesBuffers(Pattern); } /** * @return Array view of the data block containing the barycentric coordinates for the new vertices added along * the edge. */ TArrayView MapEdgeCoordBufferBlock(const int EdgeID) { TArrayView ArrayView(EdgeCoord); const FIndex2i OffsetLength = EdgeCoordOffsets[EdgeID]; return ArrayView.Slice(OffsetLength[0], OffsetLength[1]); } /** @return Array view of the data block containing the ids for the new vertices/elements added along the edge. */ TArrayView MapEdgeIDBufferBlock(const int EdgeID) { TArrayView ArrayView(EdgeIDs); const FIndex2i OffsetLength = EdgeIDOffsets[FIndex2i(EdgeID, AnyTriangleID)]; return ArrayView.Slice(OffsetLength[0], OffsetLength[1]); } /** @return Array view of the data block containing the ids for the new elements assosiated with an edge and a triangle. */ TArrayView MapEdgeIDBufferBlock(const int EdgeID, const int TriangleID) { TArrayView ArrayView(EdgeIDs); const FIndex2i OffsetLength = EdgeIDOffsets[FIndex2i(EdgeID, TriangleID)]; return ArrayView.Slice(OffsetLength[0], OffsetLength[1]); } /** @return Array view of the data block containing the barycentric coordinates for new vertices added inside the triangle. */ TArrayView MapInnerCoordBufferBlock(const int TriangleID) { TArrayView ArrayView(InnerCoord); const FIndex2i OffsetLength = InnerOffsets[TriangleID]; return ArrayView.Slice(OffsetLength[0], OffsetLength[1]); } /** @return Array view of the data block containing the ids for new vertices added inside the triangle. */ TArrayView MapInnerIDBufferBlock(const int TriangleID) { TArrayView ArrayView(InnerIDs); const FIndex2i OffsetLength = InnerOffsets[TriangleID]; return ArrayView.Slice(OffsetLength[0], OffsetLength[1]); } /** @return Array view of a data block containing the triangle ids for new triangles. */ TArrayView MapTriangleIDBufferBlock(const int TriangleID) { TArrayView ArrayView(TriangleIDs); checkSlow(TrianglesOffsets.Contains(TriangleID)); FIndex2i OffsetLengthPair = TrianglesOffsets[TriangleID]; return ArrayView.Slice(OffsetLengthPair[0], OffsetLengthPair[1]); } /** @return Array view of a data block containing the triangle indices for new triangles. */ TArrayView MapTrianglesBufferBlock(const int TriangleID) { TArrayView ArrayView(Triangles); FIndex2i OffsetLengthPair = TrianglesOffsets[TriangleID]; return ArrayView.Slice(OffsetLengthPair[0], OffsetLengthPair[1]); } //TODO: Add const versions of the Map* methods protected: virtual void InitEdgeVertexBuffers(const FTessellationPattern* Pattern) { int BufferNumVertices = 0; for (const int EdgeID : Mesh->EdgeIndicesItr()) { const int NumNewVertices = Pattern->GetNumberOfNewVerticesForEdgePatch(EdgeID); if (NumNewVertices > 0) { EdgesToTessellate.Add(EdgeID); EdgeCoordOffsets.Add(EdgeID, FIndex2i(BufferNumVertices, NumNewVertices)); EdgeIDOffsets.Add(FIndex2i(EdgeID, AnyTriangleID), FIndex2i(BufferNumVertices, NumNewVertices)); BufferNumVertices += NumNewVertices; } } EdgeIDs.SetNum(BufferNumVertices); for (int Index = 0; Index < EdgeIDs.Num(); ++Index) { EdgeIDs[Index] = Index + MaxID; } // Pre-allocate memory for linear coordinates we will be computing EdgeCoord.SetNum(BufferNumVertices); } virtual void InitTriVertexBuffers(const FTessellationPattern* Pattern) { int BufferNumVertices = 0; for (int TriangleID = 0; TriangleID < Mesh->MaxTriangleID(); ++TriangleID) { if (this->IsValidTriangle(TriangleID)) { const int NumNewVertices = Pattern->GetNumberOfNewVerticesForTrianglePatch(TriangleID); if (NumNewVertices > 0) { InnerOffsets.Add(TriangleID, FIndex2i(BufferNumVertices, NumNewVertices)); BufferNumVertices += NumNewVertices; } } } InnerIDs.SetNum(BufferNumVertices); // IDs of the new vertices/elements added inside triangles start with the last id added along edges. It is // possible that none of the edges were tessellated in which case we start with the max vertex/element id. const int LastEdgeVIDs = EdgeIDs.Num() > 0 ? EdgeIDs.Last() + 1 : MaxID; for (int Index = 0; Index < InnerIDs.Num(); ++Index) { InnerIDs[Index] = Index + LastEdgeVIDs; } // Pre-allocate memory for barycentric coordinates we will be computing InnerCoord.SetNum(BufferNumVertices); } virtual void InitTrianglesBuffers(const FTessellationPattern* Pattern) { int BufferNumTriangles = 0; for (int TriangleID = 0; TriangleID < Mesh->MaxTriangleID(); ++TriangleID) { if (this->IsValidTriangle(TriangleID)) { const int NumNewTriangles = Pattern->GetNumberOfPatchTriangles(TriangleID); if (NumNewTriangles > 0) { TrianglesToTessellate.Add(TriangleID); TrianglesOffsets.Add(TriangleID, FIndex2i(BufferNumTriangles, NumNewTriangles)); BufferNumTriangles += NumNewTriangles; } } } TriangleIDs.SetNum(BufferNumTriangles); for (int Index = 0; Index < TriangleIDs.Num(); ++Index) { TriangleIDs[Index] = Mesh->MaxTriangleID() + Index; } // Pre-allocate memory for triangles we will be computing Triangles.SetNum(BufferNumTriangles); } virtual bool IsValidTriangle(int TriangleID) const { return Mesh->IsTriangle(TriangleID); } }; /** * Handle the data for tessellating overlays. The main difference between this class and its parent is that we * handle 2 elements per vertex along edges. We also need to consider the fact that a triangle could be marked * for tessellation but not be set in the overlay, in which case we can skip any element computation for it. */ template class FOverlayTessellationData : public FTessellationData { protected: const TDynamicMeshOverlay* Overlay = nullptr; public: FOverlayTessellationData(const FDynamicMesh3* InMesh, const TDynamicMeshOverlay* InOverlay) : FTessellationData(InMesh), Overlay(InOverlay) { MaxID = Overlay->MaxElementID(); } protected: virtual void InitEdgeVertexBuffers(const FTessellationPattern* Pattern) override { int CoordBufferSize = 0; int VIDBufferSize = 0; for (const int EdgeID : Mesh->EdgeIndicesItr()) { const int NumNewVertices = Pattern->GetNumberOfNewVerticesForEdgePatch(EdgeID); const FIndex2i EdgeTri = Mesh->GetEdgeT(EdgeID); // Edge is invalid in the overlay if both triangles that share it are not set in the overlay const bool bIsNotBndry = Mesh->GetEdgeT(EdgeID).B != FDynamicMesh3::InvalidID; const bool bIsValidEdge = Overlay->IsSetTriangle(EdgeTri.A) || (bIsNotBndry && Overlay->IsSetTriangle(EdgeTri.B)); if (NumNewVertices > 0 && bIsValidEdge) { EdgesToTessellate.Add(EdgeID); EdgeCoordOffsets.Add(EdgeID, FIndex2i(CoordBufferSize, NumNewVertices)); CoordBufferSize += NumNewVertices; if (Overlay->IsSetTriangle(EdgeTri.A)) { EdgeIDOffsets.Add(FIndex2i(EdgeID, EdgeTri.A), FIndex2i(VIDBufferSize, NumNewVertices)); if (bIsNotBndry && Overlay->IsSetTriangle(EdgeTri.B)) { if (Overlay->IsSeamEdge(EdgeID) == true) { EdgeIDOffsets.Add(FIndex2i(EdgeID, EdgeTri.B), FIndex2i(VIDBufferSize + NumNewVertices, NumNewVertices)); VIDBufferSize += 2*NumNewVertices; } else { // Not a seam edge so simply point to the same data block as FIndex2i(EdgeID, EdgeTri.A) case EdgeIDOffsets.Add(FIndex2i(EdgeID, EdgeTri.B), FIndex2i(VIDBufferSize, NumNewVertices)); VIDBufferSize += NumNewVertices; } } else { VIDBufferSize += NumNewVertices; } } else { checkSlow(bIsNotBndry && Overlay->IsSetTriangle(EdgeTri.B)); EdgeIDOffsets.Add(FIndex2i(EdgeID, EdgeTri.B), FIndex2i(VIDBufferSize, NumNewVertices)); VIDBufferSize += NumNewVertices; } } } EdgeIDs.SetNum(VIDBufferSize); for (int Index = 0; Index < EdgeIDs.Num(); ++Index) { EdgeIDs[Index] = Index + MaxID; } // Pre-allocate memory for linear coordinates we will be computing EdgeCoord.SetNum(CoordBufferSize); } virtual bool IsValidTriangle(int TriangleID) const override { return Mesh->IsTriangle(TriangleID) && Overlay->IsSetTriangle(TriangleID); } }; /** * Pattern where the inner area is tessellated using the style of the OpenGL tessellation shader. The inner area * consists of multiple inner rings. The original triangle we are tessellating is called the outer ring. Areas * between rings are tessellated with the new triangles. * * O * / \ * O. .O * / O \ * O. / \ .O * / O. .O \ * / / O \ \ * / / / \ \ \ * O. / / \ \ .O * / O. / \ .O \ * / / O-------O \ \ * O. / inner ring 2 \ .O * / O----O-------O----O \ * / . inner ring 1 . \ * O----O----O-------O----O----O * outer ring * */ class FConcentricRingsTessellationPattern : public FTessellationPattern { public: /** * FRing edge is a collection of vertices that includes two corner vertices (marked + below) plus all the * vertices in between (marked O below). * * + * / \ * O \ * / \ * ring edge 3 / O ring edge 1 * / \ * O \ * / \ * / \ * +---O----O----O---+ * ring edge 2 */ class FRingEdge { public: FRingEdge() { } /** FRing edge can be a single point. */ FRingEdge(int V1) : V1(V1), VertNum(1) { } FRingEdge(int V1, int V2, TArrayView InInner, bool bReverse = false) : V1(V1), V2(V2), Inner(InInner), VertNum(InInner.Num() + 2), bReverse(bReverse) { } FRingEdge(int V1, int V2, EdgePatch Patch) : FRingEdge(V1, V2, Patch.VIDs, Patch.bIsReversed) { } inline int Num() const { return VertNum; } int operator[](int Index) const { check(Index >= 0 && Index < VertNum); if (Index == 0) { return V1; } else if (Index == VertNum - 1) { return V2; } else { int InnerIndex = Index - 1; // index into the Inner array int ActualIndex = bReverse ? Inner.Num() - InnerIndex - 1: InnerIndex; // potentially reverse index check(ActualIndex >= 0 && ActualIndex < VertNum); return Inner[ActualIndex]; } } private: int V1 = IndexConstants::InvalidID; int V2 = IndexConstants::InvalidID; TArrayView Inner; int VertNum = 0; // Number of elements bool bReverse = false; }; /* A ring consists of 3 ring edges */ struct FRing { FRingEdge UV; FRingEdge VW; FRingEdge UW; }; FConcentricRingsTessellationPattern(const FDynamicMesh3* InMesh) : FTessellationPattern(InMesh) { } virtual ~FConcentricRingsTessellationPattern() { } void Init(const TArray& InEdgeTessLevels, const TArray& InInnerTessLevels) { EdgeTessLevels = InEdgeTessLevels; InnerTessLevels = InInnerTessLevels; // If the inner area of the triangle patch is not being tessellated but at least one of its edges is, then // we insert a single vertex in the middle and connect it to all of the edge vertices. Therefore, we find // those triangles and set their inner tessellation level to 1. This would tell the TessellateTriPatch function // to insert a vertex and generate the triangle fan. for (const int TriangleID : Mesh->TriangleIndicesItr()) { if (InnerTessLevels[TriangleID] <= 0) { const FIndex3i TriEdges = Mesh->GetTriEdges(TriangleID); if (EdgeTessLevels[TriEdges[0]] || EdgeTessLevels[TriEdges[1]] || EdgeTessLevels[TriEdges[2]]) { InnerTessLevels[TriangleID] = 1; } } } } /** * Convenience method to convert an inner tessellation level from the number of vertices to the number of segments. * * O---O---O 3 vertices => 2 segments */ inline int GetInnerLevelAsSegments(const int InTriangleID) const { return InnerTessLevels[InTriangleID] - 1; } virtual int GetNumberOfNewVerticesForEdgePatch(const int InEdgeID) const override { if (Mesh->IsEdge(InEdgeID) == false) { checkNoEntry(); return InvalidIndex; } return EdgeTessLevels[InEdgeID]; } virtual int GetNumberOfNewVerticesForTrianglePatch(const int InTriangleID) const override { if (Mesh->IsTriangle(InTriangleID) == false) { checkNoEntry(); return InvalidIndex; } const int TessLevel = GetInnerLevelAsSegments(InTriangleID); if (TessLevel < 0) { return 0; } else if (TessLevel == 0) { return 1; // one vertex in the middle } // We iterate through rings and count how many vertices we are introducing per ring int NumNewTriangleVertices = 0; for (int RingLevel = TessLevel; RingLevel > 0; RingLevel -= 2) // next inner ring has 2 less segments { // 3 corners plus TessLevel - 1 vertcies along each of the 3 edges. NumNewTriangleVertices += 3 + 3 * (RingLevel - 1); // If the current ring only has 2 segments then we will be inserting one extra vertex in the middle NumNewTriangleVertices += RingLevel == 2 ? 1 : 0; } return NumNewTriangleVertices; } virtual int GetNumberOfPatchTriangles(const int InTriangleID) const override { if (Mesh->IsTriangle(InTriangleID) == false) { checkNoEntry(); return InvalidIndex; } const int TessLevel = GetInnerLevelAsSegments(InTriangleID); // Count how many triangles are we generating when connecting outer ring with the first inner ring int NumNewTrianglesPerFace = 0; for (int EdgeIdx = 0; EdgeIdx < 3; ++EdgeIdx) { const int EdgeID = Mesh->GetTriEdge(InTriangleID, EdgeIdx); const int OuterEdgeTriangles = this->GetNumberOfNewVerticesForEdgePatch(EdgeID) + 1; const int InnerEdgeTriangles = TessLevel; NumNewTrianglesPerFace += OuterEdgeTriangles + InnerEdgeTriangles; } // Iterate over every pair of inner rings and count how many triangles we are generating between them int RingLevel = TessLevel; while (RingLevel > 0) { const int CurRingLevel = RingLevel; const int NextRingLevel = RingLevel - 2; if (NextRingLevel < 0) { // no more rings left, so the current ring is a single triangle at the center checkSlow(CurRingLevel == 1); NumNewTrianglesPerFace += 1; } else { NumNewTrianglesPerFace += 3 * (CurRingLevel + NextRingLevel); } RingLevel -= 2; } return NumNewTrianglesPerFace; } virtual void TessellateEdgePatch(EdgePatch& EdgePatch) const override { const int EdgeTessLevel = EdgeTessLevels[EdgePatch.EdgeID]; checkSlow(EdgeTessLevel == EdgePatch.LinearCoord.Num()); const int NumSegments = EdgeTessLevel + 1; const double Step = 1.0 / NumSegments; for (int Idx = 0; Idx < EdgeTessLevel; ++Idx) { double Value = (Idx + 1)*Step; EdgePatch.LinearCoord[Idx] = Value; } } virtual void TessellateTriPatch(TrianglePatch& TriPatch) const override { FRing OuterRing; OuterRing.UV = FRingEdge(TriPatch.UVWCorners[0], TriPatch.UVWCorners[1], TriPatch.UVEdge); OuterRing.VW = FRingEdge(TriPatch.UVWCorners[1], TriPatch.UVWCorners[2], TriPatch.VWEdge); OuterRing.UW = FRingEdge(TriPatch.UVWCorners[2], TriPatch.UVWCorners[0], TriPatch.UWEdge); TArray RingArray; RingArray.Add(OuterRing); // Track which vertex index we are currently working with int InnerIdx = 0; // We are working with an abstract patch whose vertex coordinates are the same as their barycentric coordinates FVector3d OuterU = FVector3d(1.0, 0.0, 0.0); FVector3d OuterV = FVector3d(0.0, 1.0, 0.0); FVector3d OuterW = FVector3d(0.0, 0.0, 1.0); // Generate inner rings. Each subsequent inner ring will have its level reduced by 2. // The last inner ring can either be a single triangle or a single point. FVector3d InnerU, InnerV, InnerW; for (int TesLevel = GetInnerLevelAsSegments(TriPatch.TriangleID); TesLevel >= 0; TesLevel -= 2) { // Given the 3 corner vertices of the previous ring, compute the 3 corner vertices of this inner ring this->ComputeInnerConcentricTriangle(OuterU, OuterV, OuterW, TesLevel + 1, InnerU, InnerV, InnerW); FRing InnerRing; int StartIdx = InnerIdx; // save the index of the U corner since we will wrap back to it const double Step = TesLevel == 0 ? 0.0 : 1.0 / TesLevel; // Compute the barycentric coordinates for all vertices in the UV ring edge { TriPatch.BaryCoord[InnerIdx] = InnerU; for (int Idx = 1; Idx < TesLevel; ++Idx) { TriPatch.BaryCoord[InnerIdx + Idx] = InnerU + Idx*Step*(InnerV - InnerU); } TriPatch.BaryCoord[InnerIdx + TesLevel] = InnerV; if (TesLevel == 0) { InnerRing.UV = FRingEdge(TriPatch.VIDs[InnerIdx]); // just a single vertex in the middle } else { InnerRing.UV = FRingEdge(TriPatch.VIDs[InnerIdx], TriPatch.VIDs[InnerIdx + TesLevel], TriPatch.VIDs.Slice(InnerIdx + 1, TesLevel - 1)); } InnerIdx += TesLevel; } // Compute the barycentric coordinates for all vertices in the VW ring edge { TriPatch.BaryCoord[InnerIdx] = InnerV; for (int Idx = 1; Idx < TesLevel; ++Idx) { TriPatch.BaryCoord[InnerIdx + Idx] = InnerV + Idx*Step*(InnerW - InnerV); } TriPatch.BaryCoord[InnerIdx + TesLevel] = InnerW; if (TesLevel == 0) { InnerRing.VW = FRingEdge(TriPatch.VIDs[InnerIdx]); } else { InnerRing.VW = FRingEdge(TriPatch.VIDs[InnerIdx], TriPatch.VIDs[InnerIdx + TesLevel], TriPatch.VIDs.Slice(InnerIdx + 1, TesLevel - 1)); } InnerIdx += TesLevel; } // Compute the barycentric coordinates for all vertices in the UW ring edge { TriPatch.BaryCoord[InnerIdx] = InnerW; for (int Idx = 1; Idx < TesLevel; ++Idx) { TriPatch.BaryCoord[InnerIdx + Idx] = InnerW + Idx*Step*(InnerU - InnerW); } if (TesLevel == 0) { InnerRing.UW = FRingEdge(TriPatch.VIDs[InnerIdx]); } else { InnerRing.UW = FRingEdge(TriPatch.VIDs[InnerIdx], TriPatch.VIDs[StartIdx], TriPatch.VIDs.Slice(InnerIdx + 1, TesLevel - 1)); } InnerIdx += TesLevel; } OuterU = InnerU; OuterV = InnerV; OuterW = InnerW; RingArray.Add(InnerRing); } // Iterate over pair of rings and tessellate the area between them int TriangleIdx = 0; // Offset into TriPatch.Triangles where we will be adding new triangles for (int RingIdx = 0; RingIdx < RingArray.Num() - 1; ++RingIdx) { TriangleIdx += this->StitchRingEdges(RingArray[RingIdx].UV, RingArray[RingIdx + 1].UV, TriangleIdx, TriPatch.Triangles); TriangleIdx += this->StitchRingEdges(RingArray[RingIdx].VW, RingArray[RingIdx + 1].VW, TriangleIdx, TriPatch.Triangles); TriangleIdx += this->StitchRingEdges(RingArray[RingIdx].UW, RingArray[RingIdx + 1].UW, TriangleIdx, TriPatch.Triangles); } // If the tessellation level is odd, then we need to create one more triangle with the last inner ring vertices if (GetInnerLevelAsSegments(TriPatch.TriangleID)% 2 != 0) { const FRing& LastRing = RingArray.Last(); TriPatch.Triangles[TriangleIdx] = FIndex3i(LastRing.UV[0], LastRing.UV[1], LastRing.VW[1]); } } /** * Given two ring edges, "stitch" them together by generating a sequence of triangles connecting all vertices. * * Outer ring edge O O O--O * ==> /\ /\ * / \/ \ * Inner ring edge 1 O O O O----O---O * * @return the number of triangles generated */ int StitchRingEdges(const FRingEdge& InOuter, const FRingEdge& InInner, int Offset, TArrayView& OutTriangles) const { const int OuterNum = InOuter.Num(); const int InnerNum = InInner.Num(); int TessDiff = InnerNum - OuterNum; const int NumTriangles = OuterNum - 1 + InnerNum - 1; // we know how many triangles to expect // Track which vertex are we currently at in the outer and inner vertex arrays int OuterIdx = 0; int InnerIdx = 0; // We move along the outer and inner vertices and generate triangles. For each triangle, we need to choose which // ring edge do we pick the third vertex from. Is it the OuterIdx + 1 or the InnerIdx + 1 vertex? We use the // updated TessDiff variable to make the choice. If it's negative we choose the outer vertex, otherwise the inner vertex. // For more information see the Sec. 5.1 and Figure 8 in "Watertight Tessellation using Forward Differencing, H. Moreton, 2001." // // OuterIdx // \ // O O // // // O O O // / // InnerIdx for (int TriIndex = 0; TriIndex < NumTriangles; ++TriIndex) { // If TessDiff is negative and the next vertex along outer side is available or we simply run out of inner vertices, // then use outer vertex as the third vertex to create a triangle if ((TessDiff < 0 && OuterIdx + 1 < OuterNum) || InnerIdx == InnerNum - 1) { OutTriangles[Offset + TriIndex] = FIndex3i(InOuter[OuterIdx], InOuter[OuterIdx + 1], InInner[InnerIdx]); TessDiff += 2*InnerNum; OuterIdx += 1; } else { ensure((TessDiff >= 0 && InnerIdx + 1 < InnerNum) || OuterIdx == OuterNum - 1); OutTriangles[Offset + TriIndex] = FIndex3i(InInner[InnerIdx], InOuter[OuterIdx], InInner[InnerIdx + 1]); TessDiff -= 2*OuterNum; InnerIdx += 1; } } return NumTriangles; } /** An array of per triangle (inner) tessellation levels. The size must match the maximum triangle ID of the mesh. */ TArray InnerTessLevels; /** An array of per edge tessellation levels. The size must match the maximum edge ID of the mesh. */ TArray EdgeTessLevels; }; /** * Use the tessellation pattern to tessellate the mesh and generate the tessellation data. * * @return false if the tessellation failed or was cancelled by the user */ bool TessellateGeometry(const FDynamicMesh3* Mesh, const FTessellationPattern* Pattern, const bool bUseParallel, FProgressCancel* Progress, FCompactMaps& OutCompactInfo, FTessellationData& TessData, FDynamicMesh3* OutMesh, FSelectiveTessellate::FTessellationInformation& TessInfo) { TessData.Init(Pattern); // Tessellate edge patches TArray EdgesToTessellate = TessData.EdgesToTessellate.Array(); ParallelFor(EdgesToTessellate.Num(), [&](int32 Index) { if (Progress && Progress->Cancelled()) { return; } FTessellationPattern::EdgePatch Patch; const int EdgeID = EdgesToTessellate[Index]; Patch.EdgeID = EdgeID; Patch.LinearCoord = TessData.MapEdgeCoordBufferBlock(EdgeID); Patch.VIDs = TessData.MapEdgeIDBufferBlock(EdgeID); Pattern->TessellateEdgePatch(Patch); }, bUseParallel ? EParallelForFlags::None : EParallelForFlags::ForceSingleThread); if (Progress && Progress->Cancelled()) { return false; } // Tessellate triangle patches TArray TrianglesToTessellate = TessData.TrianglesToTessellate.Array(); ParallelFor(TrianglesToTessellate.Num(), [&](int32 Index) { if (Progress && Progress->Cancelled()) { return; } FTessellationPattern::TrianglePatch Patch; const int TriangleID = TrianglesToTessellate[Index]; Patch.TriangleID = TriangleID; FIndex3i TriVertices = Mesh->GetTriangle(TriangleID); Patch.UVWCorners = TriVertices; const FIndex3i& TriEdges = Mesh->GetTriEdgesRef(TriangleID); // UV Edge if (TessData.EdgesToTessellate.Contains(TriEdges[0])) { Patch.UVEdge.EdgeID = TriEdges[0]; Patch.UVEdge.LinearCoord = TessData.MapEdgeCoordBufferBlock(TriEdges[0]); Patch.UVEdge.VIDs = TessData.MapEdgeIDBufferBlock(TriEdges[0]); Patch.UVEdge.bIsReversed = Mesh->GetEdgeV(TriEdges[0])[0] != TriVertices[0]; } // VW Edge if (TessData.EdgesToTessellate.Contains(TriEdges[1])) { Patch.VWEdge.EdgeID = TriEdges[1]; Patch.VWEdge.LinearCoord = TessData.MapEdgeCoordBufferBlock(TriEdges[1]); Patch.VWEdge.VIDs = TessData.MapEdgeIDBufferBlock(TriEdges[1]); Patch.VWEdge.bIsReversed = Mesh->GetEdgeV(TriEdges[1])[0] != TriVertices[1]; } // UW Edge if (TessData.EdgesToTessellate.Contains(TriEdges[2])) { Patch.UWEdge.EdgeID = TriEdges[2]; Patch.UWEdge.LinearCoord = TessData.MapEdgeCoordBufferBlock(TriEdges[2]); Patch.UWEdge.VIDs = TessData.MapEdgeIDBufferBlock(TriEdges[2]); Patch.UWEdge.bIsReversed = Mesh->GetEdgeV(TriEdges[2])[0] != TriVertices[2]; } // Inner Triangle Vertices Patch.BaryCoord = TessData.MapInnerCoordBufferBlock(TriangleID); Patch.VIDs = TessData.MapInnerIDBufferBlock(TriangleID); // Inner Triangles Patch.Triangles = TessData.MapTrianglesBufferBlock(TriangleID); // Tessellate the patch, i.e. generate inner vertices and triangles Pattern->TessellateTriPatch(Patch); }, bUseParallel ? EParallelForFlags::None : EParallelForFlags::ForceSingleThread); if (Progress && Progress->Cancelled()) { return false; } if (SelectiveTessellateLocals::ConstructTessellatedMesh(Mesh, Progress, TessData, OutCompactInfo, OutMesh, TessInfo) == false) { return false; } return true; } /** * Use the tessellation pattern to tessellate an overlay. * * @return false if the tessellation failed or was cancelled by the user */ template bool TessellateOverlay(const FDynamicMesh3* Mesh, const TDynamicMeshOverlay* Overlay, const FTessellationPattern* Pattern, FTessellationData& MeshTessData, const bool bUseParallel, FProgressCancel* Progress, const FCompactMaps& CompactInfo, TDynamicMeshOverlay* OutOverlay) { //TODO: The overlay tessellation data should only compute and contain the connectivity information since we // can simply reuse coordinates from the geometry tessellation (i.e. overlay elements live on the vertices). SelectiveTessellateLocals::FOverlayTessellationData OverlayTessData(Mesh, Overlay); OverlayTessData.Init(Pattern); // Tessellate edge patches TArray EdgesToTessellate = OverlayTessData.EdgesToTessellate.Array(); ParallelFor(EdgesToTessellate.Num(), [&](int32 Index) { if (Progress && Progress->Cancelled()) { return; } const int EdgeID = EdgesToTessellate[Index]; const FIndex2i EdgeTri = Mesh->GetEdgeT(EdgeID); if (Overlay->IsSetTriangle(EdgeTri.A)) { FTessellationPattern::EdgePatch PatchA; PatchA.EdgeID = EdgeID; PatchA.LinearCoord = OverlayTessData.MapEdgeCoordBufferBlock(EdgeID); PatchA.VIDs = OverlayTessData.MapEdgeIDBufferBlock(EdgeID, EdgeTri.A); Pattern->TessellateEdgePatch(PatchA); } if (EdgeTri.B != FDynamicMesh3::InvalidID && Overlay->IsSetTriangle(EdgeTri.B)) { FTessellationPattern::EdgePatch PatchB; PatchB.EdgeID = EdgeID; PatchB.LinearCoord = OverlayTessData.MapEdgeCoordBufferBlock(EdgeID); PatchB.VIDs = OverlayTessData.MapEdgeIDBufferBlock(EdgeID, EdgeTri.B); Pattern->TessellateEdgePatch(PatchB); } }, bUseParallel ? EParallelForFlags::None : EParallelForFlags::ForceSingleThread); if (Progress && Progress->Cancelled()) { return false; } // Tessellate triangle patches TArray TrianglesToTessellate = OverlayTessData.TrianglesToTessellate.Array(); ParallelFor(TrianglesToTessellate.Num(), [&](int32 Index) { if (Progress && Progress->Cancelled()) { return; } FTessellationPattern::TrianglePatch Patch; const int TriangleID = TrianglesToTessellate[Index]; Patch.TriangleID = TriangleID; FIndex3i TriVertices = Mesh->GetTriangle(TriangleID); Patch.UVWCorners = Overlay->GetTriangle(TriangleID); const FIndex3i& TriEdges = Mesh->GetTriEdgesRef(TriangleID); // UV Edge if (OverlayTessData.EdgesToTessellate.Contains(TriEdges[0])) { Patch.UVEdge.EdgeID = TriEdges[0]; Patch.UVEdge.LinearCoord = OverlayTessData.MapEdgeCoordBufferBlock(TriEdges[0]); Patch.UVEdge.VIDs = OverlayTessData.MapEdgeIDBufferBlock(TriEdges[0], TriangleID); Patch.UVEdge.bIsReversed = Mesh->GetEdgeV(TriEdges[0])[0] != TriVertices[0]; } // VW Edge if (OverlayTessData.EdgesToTessellate.Contains(TriEdges[1])) { Patch.VWEdge.EdgeID = TriEdges[1]; Patch.VWEdge.LinearCoord = OverlayTessData.MapEdgeCoordBufferBlock(TriEdges[1]); Patch.VWEdge.VIDs = OverlayTessData.MapEdgeIDBufferBlock(TriEdges[1], TriangleID); Patch.VWEdge.bIsReversed = Mesh->GetEdgeV(TriEdges[1])[0] != TriVertices[1]; } // UW Edge if (OverlayTessData.EdgesToTessellate.Contains(TriEdges[2])) { Patch.UWEdge.EdgeID = TriEdges[2]; Patch.UWEdge.LinearCoord = OverlayTessData.MapEdgeCoordBufferBlock(TriEdges[2]); Patch.UWEdge.VIDs = OverlayTessData.MapEdgeIDBufferBlock(TriEdges[2], TriangleID); Patch.UWEdge.bIsReversed = Mesh->GetEdgeV(TriEdges[2])[0] != TriVertices[2]; } // Inner Triangle Vertices Patch.BaryCoord = OverlayTessData.MapInnerCoordBufferBlock(TriangleID); Patch.VIDs = OverlayTessData.MapInnerIDBufferBlock(TriangleID); // Inner Triangles Patch.Triangles = OverlayTessData.MapTrianglesBufferBlock(TriangleID); // Tessellate the patch, i.e. generate inner vertices and triangles Pattern->TessellateTriPatch(Patch); }, bUseParallel ? EParallelForFlags::None : EParallelForFlags::ForceSingleThread); if (Progress && Progress->Cancelled()) { return false; } if (SelectiveTessellateLocals::ConstructTessellatedOverlay(Mesh, Overlay, MeshTessData, OverlayTessData, CompactInfo, Progress, OutOverlay) == false) { return false; } return true; } /** * Construct a new triangle attribute from the tessellation data and compact information from the geometry tessellation. */ template void ConstructTriangleAttribute(const FDynamicMesh3* Mesh, const TDynamicMeshTriangleAttribute* Attribute, FTessellationData& TessData, const bool bUseParallel, const FCompactMaps& CompactInfo, TDynamicMeshTriangleAttribute* OutAttribute) { RealType Value[ElementSize]; for (const int TID : Mesh->TriangleIndicesItr()) { Attribute->GetValue(TID, Value); if (TessData.TrianglesToTessellate.Contains(TID)) { TArrayView TriangleIDBlock = TessData.MapTriangleIDBufferBlock(TID); for (int Index = 0; Index < TriangleIDBlock.Num(); ++Index) { const int NewTID = TriangleIDBlock[Index]; const int ToTID = CompactInfo.GetTriangleMapping(NewTID); OutAttribute->SetValue(ToTID, Value); } } else { const int ToTID = CompactInfo.GetTriangleMapping(TID); OutAttribute->SetValue(ToTID, Value); } } } /** * Given the original FDynamicMesh3 and the tessellation data, construct new tessellated FDynamicMesh3 geometry. * Output mesh will be compact. * * @param CompactInfo Stores vertex and triangle mappings. * @return false if the user cancelled the operation. */ bool ConstructTessellatedMesh(const FDynamicMesh3* Mesh, FProgressCancel* Progress, FTessellationData& TessData, FCompactMaps& CompactInfo, FDynamicMesh3* ResultMesh, FSelectiveTessellate::FTessellationInformation& TessInfo) { ResultMesh->Clear(); const int ResultNumVertexIDs = Mesh->MaxVertexID() + TessData.EdgeIDs.Num() + TessData.InnerIDs.Num(); CompactInfo.ResetVertexMap(ResultNumVertexIDs, false); const int ResultNumTriangleIDs = Mesh->MaxTriangleID() + TessData.Triangles.Num(); CompactInfo.ResetTriangleMap(ResultNumTriangleIDs, false); // Append all of the vertices from the input mesh for (int VertexID = 0; VertexID < Mesh->MaxVertexID(); ++VertexID) { if (Mesh->IsVertex(VertexID)) { CompactInfo.SetVertexMapping(VertexID, ResultMesh->AppendVertex(Mesh->GetVertex(VertexID))); } else { CompactInfo.SetVertexMapping(VertexID, FCompactMaps::InvalidID); } } if (Progress && Progress->Cancelled()) { return false; } // Append all of the vertices along tessellated edges for (const int EdgeID : TessData.EdgesToTessellate) { TArrayView CoordBlock = TessData.MapEdgeCoordBufferBlock(EdgeID); TArrayView IDBlock = TessData.MapEdgeIDBufferBlock(EdgeID); const FIndex2i EdgeV = Mesh->GetEdgeV(EdgeID); for (int Index = 0; Index < CoordBlock.Num(); ++Index) { double Alpha = CoordBlock[Index]; Alpha = FMath::Clamp(Alpha, 0.0, 1.0); const int VertexID = IDBlock[Index]; const FVector3d Vertex = (1.0 - Alpha)*Mesh->GetVertex(EdgeV[0]) + Alpha*Mesh->GetVertex(EdgeV[1]); const int NewVertexID = ResultMesh->AppendVertex(Vertex); CompactInfo.SetVertexMapping(VertexID, NewVertexID); if (TessInfo.SelectedVertices) { TessInfo.SelectedVertices->Add(NewVertexID); } } } if (Progress && Progress->Cancelled()) { return false; } // Append all of the vertices inside tessellated triangles for (const int TriangleID : TessData.TrianglesToTessellate) { TArrayView CoordBlock = TessData.MapInnerCoordBufferBlock(TriangleID); TArrayView IDBlock = TessData.MapInnerIDBufferBlock(TriangleID); checkSlow(CoordBlock.Num() == IDBlock.Num()); const FIndex3i TriangleV = Mesh->GetTriangle(TriangleID); for (int Index = 0; Index < CoordBlock.Num(); ++Index) { const FVector3d Bary = CoordBlock[Index]; const int VertexID = IDBlock[Index]; const FVector3d Vertex = Bary[0] * Mesh->GetVertex(TriangleV[0]) + Bary[1] * Mesh->GetVertex(TriangleV[1]) + Bary[2] * Mesh->GetVertex(TriangleV[2]); const int NewVertexID = ResultMesh->AppendVertex(Vertex); CompactInfo.SetVertexMapping(VertexID, NewVertexID); if (TessInfo.SelectedVertices) { TessInfo.SelectedVertices->Add(NewVertexID); } } } if (Progress && Progress->Cancelled()) { return false; } if (Mesh->HasTriangleGroups()) { ResultMesh->EnableTriangleGroups(); } // Append all the triangles from the input mesh that we are not tessellating for (const int TriangleID : Mesh->TriangleIndicesItr()) { if (TessData.TrianglesToTessellate.Contains(TriangleID) == false) { const FIndex3i Tri = Mesh->GetTriangle(TriangleID); const int TriGrp = Mesh->GetTriangleGroup(TriangleID); const FIndex3i MappedTri = CompactInfo.GetVertexMapping(Tri); CompactInfo.SetTriangleMapping(TriangleID, ResultMesh->AppendTriangle(MappedTri, TriGrp)); } } // Append all the new triangles generated during the tessellation for (const int TriangleID : TessData.TrianglesToTessellate) { TArrayView TrianglesBlock = TessData.MapTrianglesBufferBlock(TriangleID); TArrayView TriangleIDBlock = TessData.MapTriangleIDBufferBlock(TriangleID); checkSlow(TrianglesBlock.Num() == TriangleIDBlock.Num()); const int TriGrp = Mesh->GetTriangleGroup(TriangleID); for (int Index = 0; Index < TriangleIDBlock.Num(); ++Index) { const int NewTriangleID = TriangleIDBlock[Index]; const FIndex3i Tri = TrianglesBlock[Index]; const FIndex3i MappedTri = CompactInfo.GetVertexMapping(Tri); CompactInfo.SetTriangleMapping(NewTriangleID, ResultMesh->AppendTriangle(MappedTri, TriGrp)); } } if (TessInfo.SelectedVertices) { // Add all of the original vertices of the triangles we are tessellating TSet TempSetSelectedVertices; // first add them to tset to avoid duplicates for (const int TriangleID : TessData.TrianglesToTessellate) { const FIndex3i Tri = Mesh->GetTriangle(TriangleID); const FIndex3i MappedTri = CompactInfo.GetVertexMapping(Tri); TempSetSelectedVertices.Add(MappedTri[0]); TempSetSelectedVertices.Add(MappedTri[1]); TempSetSelectedVertices.Add(MappedTri[2]); } for (int VID : TempSetSelectedVertices) { TessInfo.SelectedVertices->Add(VID); } } //TODO: instead of generating the mesh from scratch, we could remove triangles we are tessellating from the // input mesh and append the new ones. This can be an option when the number of triangles tessellated is small, // compared to the total number of triangles. if (Progress && Progress->Cancelled()) { return false; } return true; } /** * Construct a new overlay from the tessellation data and compact information from the geometry tessellation. * * @return false if fails or the user cancells the operation */ template bool ConstructTessellatedOverlay(const FDynamicMesh3* Mesh, const TDynamicMeshOverlay* Overlay, FTessellationData& MeshTessData, FOverlayTessellationData& OverlayTessData, const FCompactMaps& CompactInfo, FProgressCancel* Progress, TDynamicMeshOverlay* ResultOverlay) { ResultOverlay->ClearElements(); // Need to track the ID of the appended elements to handle non-compact meshes TMap MapE; MapE.Reserve(Overlay->ElementCount() + OverlayTessData.EdgeIDOffsets.Num()); // Buffers to be reused RealType Element1[ElementSize]; RealType Element2[ElementSize]; RealType Element3[ElementSize]; RealType Out[ElementSize]; // First add all the existing elements for (const int ElementID : Overlay->ElementIndicesItr()) { Overlay->GetElement(ElementID, Out); MapE.Add(ElementID, ResultOverlay->AppendElement(Out)); } if (Progress && Progress->Cancelled()) { return false; } // Add all the new elements inserted along the edges by the tessellator for (const int EdgeID : OverlayTessData.EdgesToTessellate) { const FIndex2i EdgeTri = Mesh->GetEdgeT(EdgeID); const FIndex2i EdgeV = Mesh->GetEdgeV(EdgeID); TArrayView CoordBlock = OverlayTessData.MapEdgeCoordBufferBlock(EdgeID); if (Overlay->IsSetTriangle(EdgeTri.A)) { TArrayView IDBlock = OverlayTessData.MapEdgeIDBufferBlock(EdgeID, EdgeTri.A); checkSlow(IDBlock.Num() == CoordBlock.Num()); Overlay->GetElementAtVertex(EdgeTri.A, EdgeV[0], Element1); Overlay->GetElementAtVertex(EdgeTri.A, EdgeV[1], Element2); for (int Index = 0; Index < IDBlock.Num(); ++Index) { const RealType Alpha = (RealType)CoordBlock[Index]; const int ElementID = IDBlock[Index]; LerpElements(Element1, Element2, Out, Alpha); MapE.Add(ElementID, ResultOverlay->AppendElement(Out)); } } if (EdgeTri.B != FDynamicMesh3::InvalidID && Overlay->IsSetTriangle(EdgeTri.B) && Overlay->IsSeamEdge(EdgeID)) { TArrayView IDBlock = OverlayTessData.MapEdgeIDBufferBlock(EdgeID, EdgeTri.B); checkSlow(IDBlock.Num() == CoordBlock.Num()); Overlay->GetElementAtVertex(EdgeTri.B, EdgeV[0], Element1); Overlay->GetElementAtVertex(EdgeTri.B, EdgeV[1], Element2); for (int Index = 0; Index < IDBlock.Num(); ++Index) { const RealType Alpha = (RealType)CoordBlock[Index]; const int ElementID = IDBlock[Index]; LerpElements(Element1, Element2, Out, Alpha); MapE.Add(ElementID, ResultOverlay->AppendElement(Out)); } } } if (Progress && Progress->Cancelled()) { return false; } // Add all the elements added inside of the triangles by the tessellator for (const int TriangleID : OverlayTessData.TrianglesToTessellate) { const FIndex3i TriangleV = Mesh->GetTriangle(TriangleID); Overlay->GetElementAtVertex(TriangleID, TriangleV[0], Element1); Overlay->GetElementAtVertex(TriangleID, TriangleV[1], Element2); Overlay->GetElementAtVertex(TriangleID, TriangleV[2], Element3); TArrayView CoordBlock = OverlayTessData.MapInnerCoordBufferBlock(TriangleID); TArrayView IDBlock = OverlayTessData.MapInnerIDBufferBlock(TriangleID); checkSlow(CoordBlock.Num() == IDBlock.Num()); for (int Index = 0; Index < CoordBlock.Num(); ++Index) { const FVector3d Bary = CoordBlock[Index]; const int ElementID = IDBlock[Index]; BaryElements(Element1, Element2, Element3, Out, Bary[0], Bary[1], Bary[2]); MapE.Add(ElementID, ResultOverlay->AppendElement(Out)); } } if (Progress && Progress->Cancelled()) { return false; } // Set the overlay triangles and point them to the correct element ids for (const int TriangleID : Mesh->TriangleIndicesItr()) { if (Overlay->IsSetTriangle(TriangleID) && OverlayTessData.TrianglesToTessellate.Contains(TriangleID) == false) { const FIndex3i ElementTri = Overlay->GetTriangle(TriangleID); const int ToTID = CompactInfo.GetTriangleMapping(TriangleID); ResultOverlay->SetTriangle(ToTID, FIndex3i(MapE[ElementTri.A], MapE[ElementTri.B], MapE[ElementTri.C])); } } for (const int TriangleID : OverlayTessData.TrianglesToTessellate) { if (ensure(Overlay->IsSetTriangle(TriangleID))) // TrianglesToTessellate set should already contain only triangle IDs set in the overlay { TArrayView TrianglesBlock = OverlayTessData.MapTrianglesBufferBlock(TriangleID); TArrayView TriangleIDBlock = MeshTessData.MapTriangleIDBufferBlock(TriangleID); checkSlow(TrianglesBlock.Num() == TriangleIDBlock.Num()); for (int Index = 0; Index < TrianglesBlock.Num(); ++Index) { const FIndex3i Tri = TrianglesBlock[Index]; const int NewTriangleID = TriangleIDBlock[Index]; const int ToTID = CompactInfo.GetTriangleMapping(NewTriangleID); ResultOverlay->SetTriangle(ToTID, FIndex3i(MapE[Tri.A], MapE[Tri.B], MapE[Tri.C])); } } } if (Progress && Progress->Cancelled()) { return false; } return true; } /** * General purpose function to interpolate any per-vertex data. The function will iterate over all vertices * inserted along the edges and call InterpolateEdgeFunc. Then it will iterate over all of the vertices inserted * inside of the triangles and call InterpolateInnerFunc. * * @param InterpolateEdgeFunc A callback function with the signature void(int V1, int V2, int NewV, double U). * V1,V2 are the edge vertex indices into the original input mesh. NewV is the mapped * vertex id into the tessellated mesh for which we are asking the function to set the * value for. U is the linear interpolation coefficient of NewV with respect to V1,V2. * * @param InterpolateInnerFunc A callback function with the signature void(int V1, int V2, int V3, int NewV, double U, double V, double W). * V1,V2,V3 are the vertex indices into the original input mesh. NewV is the mapped * vertex id into the tessellated mesh for which we are asking the function to set the * value for. U,V,W are the barycentric coordinates of NewV with respect to V1,V2,V3. */ void InterpolateVertexData(const FDynamicMesh3* Mesh, const TFunctionRef InterpolateEdgeFunc, const TFunctionRef InterpolateInnerFunc, FTessellationData& TessData, const bool bUseParallel, const FCompactMaps& CompactInfo) { // Set value for the vertices inserted along edges via interpolation ParallelFor(TessData.EdgesToTessellate.Num(), [&](int32 EIndex) { const int EdgeID = TessData.EdgesToTessellate[FSetElementId::FromInteger(EIndex)]; TArrayView CoordBlock = TessData.MapEdgeCoordBufferBlock(EdgeID); TArrayView IDBlock = TessData.MapEdgeIDBufferBlock(EdgeID); checkSlow(CoordBlock.Num() == IDBlock.Num()); const FIndex2i EdgeV = Mesh->GetEdgeV(EdgeID); for (int Index = 0; Index < CoordBlock.Num(); ++Index) { const double Alpha = CoordBlock[Index]; const int VID = IDBlock[Index]; const int ToVID = CompactInfo.GetVertexMapping(VID); InterpolateEdgeFunc(EdgeV.A, EdgeV.B, ToVID, Alpha); } }, bUseParallel ? EParallelForFlags::None : EParallelForFlags::ForceSingleThread); // Set value for the vertices inserted inside of the triangles via interpolation ParallelFor(TessData.TrianglesToTessellate.Num(), [&](int32 TIndex) { const int TriangleID = TessData.TrianglesToTessellate[FSetElementId::FromInteger(TIndex)]; TArrayView CoordBlock = TessData.MapInnerCoordBufferBlock(TriangleID); TArrayView IDBlock = TessData.MapInnerIDBufferBlock(TriangleID); checkSlow(CoordBlock.Num() == IDBlock.Num()); const FIndex3i TriangleV = Mesh->GetTriangle(TriangleID); for (int Index = 0; Index < CoordBlock.Num(); ++Index) { const FVector3d BaryCoords = CoordBlock[Index]; const int VID = IDBlock[Index]; const int ToVID = CompactInfo.GetVertexMapping(VID); InterpolateInnerFunc(TriangleV.A, TriangleV.B, TriangleV.C, ToVID, BaryCoords[0], BaryCoords[1], BaryCoords[2]); } }, bUseParallel ? EParallelForFlags::None : EParallelForFlags::ForceSingleThread); } /** Interpolate any data stored in the TDynamicMeshVertexAttribute */ template void ConstructDynamicMeshVertexAttribute(const FDynamicMesh3* Mesh, const TDynamicMeshVertexAttribute* Attribute, FTessellationData& TessData, const bool bUseParallel, const FCompactMaps& CompactInfo, TDynamicMeshVertexAttribute* OutAttribute) { auto InterpolateEdgeFunc = [Attribute, OutAttribute] (int V1, int V2, int NewV, double U) { RealType Value1[ElementSize]; RealType Value2[ElementSize]; RealType OutValue[ElementSize]; Attribute->GetValue(V1, Value1); Attribute->GetValue(V2, Value2); LerpElements(Value1, Value2, OutValue, U); OutAttribute->SetValue(NewV, OutValue); }; auto InterpolateInnerFunc = [Attribute, OutAttribute](int V1, int V2, int V3, int NewV, double U, double V, double W) { RealType Value1[ElementSize]; RealType Value2[ElementSize]; RealType Value3[ElementSize]; RealType OutValue[ElementSize]; Attribute->GetValue(V1, Value1); Attribute->GetValue(V2, Value2); Attribute->GetValue(V3, Value3); BaryElements(Value1, Value2, Value3, OutValue, U, V, W); OutAttribute->SetValue(NewV, OutValue); }; RealType Value[ElementSize]; for (const int VID : Mesh-> VertexIndicesItr()) { Attribute->GetValue(VID, Value); const int ToVID = CompactInfo.GetVertexMapping(VID); OutAttribute->SetValue(ToVID, Value); } InterpolateVertexData(Mesh, InterpolateEdgeFunc, InterpolateInnerFunc, TessData, bUseParallel, CompactInfo); } /** Interpolate skin weights */ void ConstructVertexSkinWeightsAttribute(const FDynamicMesh3* Mesh, const FDynamicMeshVertexSkinWeightsAttribute* Attribute, FTessellationData& TessData, const bool bUseParallel, const FCompactMaps& CompactInfo, FDynamicMeshVertexSkinWeightsAttribute* OutAttribute) { using FBoneWeights = UE::AnimationCore::FBoneWeights; auto InterpolateEdgeFunc = [Attribute, OutAttribute] (int V1, int V2, int NewV, double U) { FBoneWeights Value1; FBoneWeights Value2; FBoneWeights OutValue; Attribute->GetValue(V1, Value1); Attribute->GetValue(V2, Value2); U = FMath::Clamp(U, 0.0, 1.0); OutValue = FBoneWeights::Blend(Value1, Value2, (float)U); OutAttribute->SetValue(NewV, OutValue); }; auto InterpolateInnerFunc = [Attribute, OutAttribute] (int V1, int V2, int V3, int NewV, double U, double V, double W) { FBoneWeights Value1, Value2, Value3, OutValue; Attribute->GetValue(V1, Value1); Attribute->GetValue(V2, Value2); Attribute->GetValue(V3, Value3); OutValue = FBoneWeights::Blend(Value1, Value2, Value3, (float)U, (float)V, (float)W); OutAttribute->SetValue(NewV, OutValue); }; FBoneWeights Value; for (const int VID : Mesh-> VertexIndicesItr()) { Attribute->GetValue(VID, Value); const int ToVID = CompactInfo.GetVertexMapping(VID); OutAttribute->SetValue(ToVID, Value); } InterpolateVertexData(Mesh, InterpolateEdgeFunc, InterpolateInnerFunc, TessData, bUseParallel, CompactInfo); } /** Interpolate per-vertex normals */ void ConstructPerVertexNormals(const FDynamicMesh3* Mesh, FTessellationData& TessData, const bool bUseParallel, const FCompactMaps& CompactInfo, FDynamicMesh3* OutMesh) { auto InterpolateEdgeFunc = [Mesh, OutMesh] (int V1, int V2, int NewV, double U) { FVector3f OutValue; U = FMath::Clamp(U, 0.0, 1.0); OutValue = (1.0 - U) * Mesh->GetVertexNormal(V1) + U * Mesh->GetVertexNormal(V2); OutMesh->SetVertexNormal(NewV, OutValue); }; auto InterpolateInnerFunc = [Mesh, OutMesh] (int V1, int V2, int V3, int NewV, double U, double V, double W) { FVector3f OutValue; checkSlow(FMath::Abs(U + V + W - 1.0) < KINDA_SMALL_NUMBER); OutValue = U * Mesh->GetVertexNormal(V1) + V * Mesh->GetVertexNormal(V2) + W * Mesh->GetVertexNormal(V3); OutMesh->SetVertexNormal(NewV, OutValue); }; for (const int VID : Mesh->VertexIndicesItr()) { const int ToVID = CompactInfo.GetVertexMapping(VID); OutMesh->SetVertexNormal(ToVID, Mesh->GetVertexNormal(VID)); } InterpolateVertexData(Mesh, InterpolateEdgeFunc, InterpolateInnerFunc, TessData, bUseParallel, CompactInfo); } /** Interpolate per-vertex UVs */ void ConstructPerVertexUVs(const FDynamicMesh3* Mesh, FTessellationData& TessData, const bool bUseParallel, const FCompactMaps& CompactInfo, FDynamicMesh3* OutMesh) { auto InterpolateEdgeFunc = [Mesh, OutMesh] (int V1, int V2, int NewV, double U) { FVector2f OutValue; U = FMath::Clamp(U, 0.0, 1.0); OutValue = float(1.0 - U) * Mesh->GetVertexUV(V1) + float(U) * Mesh->GetVertexUV(V2); OutMesh->SetVertexUV(NewV, OutValue); }; auto InterpolateInnerFunc = [Mesh, OutMesh] (int V1, int V2, int V3, int NewV, double U, double V, double W) { FVector2f OutValue; checkSlow(FMath::Abs(U + V + W - 1.0) < KINDA_SMALL_NUMBER); OutValue = float(U) * Mesh->GetVertexUV(V1) + float(V) * Mesh->GetVertexUV(V2) + float(W) * Mesh->GetVertexUV(V3); OutMesh->SetVertexUV(NewV, OutValue); }; for (const int VID : Mesh-> VertexIndicesItr()) { const int ToVID = CompactInfo.GetVertexMapping(VID); OutMesh->SetVertexUV(ToVID, Mesh->GetVertexUV(VID)); } InterpolateVertexData(Mesh, InterpolateEdgeFunc, InterpolateInnerFunc, TessData, bUseParallel, CompactInfo); } /** Interpolate per-vertex colors */ void ConstructPerVertexColors(const FDynamicMesh3* Mesh, FTessellationData& TessData, const bool bUseParallel, const FCompactMaps& CompactInfo, FDynamicMesh3* OutMesh) { auto InterpolateEdgeFunc = [Mesh, OutMesh] (int V1, int V2, int NewV, double U) { FVector4f OutValue; U = FMath::Clamp(U, 0.0, 1.0); OutValue = float(1.0 - U) * Mesh->GetVertexColor(V1) + float(U) * Mesh->GetVertexColor(V2); OutMesh->SetVertexColor(NewV, OutValue); }; auto InterpolateInnerFunc = [Mesh, OutMesh] (int V1, int V2, int V3, int NewV, double U, double V, double W) { FVector4f OutValue; checkSlow(FMath::Abs(U + V + W - 1.0) < KINDA_SMALL_NUMBER); OutValue = float(U) * Mesh->GetVertexColor(V1) + float(V) * Mesh->GetVertexColor(V2) + float(W) * Mesh->GetVertexColor(V3); OutMesh->SetVertexColor(NewV, OutValue); }; for (const int VID : Mesh-> VertexIndicesItr()) { const int ToVID = CompactInfo.GetVertexMapping(VID); OutMesh->SetVertexColor(ToVID, Mesh->GetVertexColor(VID)); } InterpolateVertexData(Mesh, InterpolateEdgeFunc, InterpolateInnerFunc, TessData, bUseParallel, CompactInfo); } /** * Run the main tessellation logic for the geometry, overlays and attributes. * * @param OutMesh The resulting tessellated mesh. * @return false if the tessellation failed or the user cancelled it */ bool Tessellate(const FDynamicMesh3* InMesh, const FTessellationPattern* Pattern, const bool bUseParallel, FProgressCancel* Progress, FDynamicMesh3* OutMesh, FSelectiveTessellate::FTessellationInformation& TessInfo) { OutMesh->Clear(); FCompactMaps CompactInfo; SelectiveTessellateLocals::FTessellationData TessData(InMesh); if (TessellateGeometry(InMesh, Pattern, bUseParallel, Progress, CompactInfo, TessData, OutMesh, TessInfo) == false) { return false; } if (InMesh->HasAttributes()) { OutMesh->EnableAttributes(); const FDynamicMeshAttributeSet* InAttributes = InMesh->Attributes(); FDynamicMeshAttributeSet* OutAttributes = OutMesh->Attributes(); if (InAttributes->NumNormalLayers()) { OutAttributes->SetNumNormalLayers(InAttributes->NumNormalLayers()); for (int Idx = 0; Idx < InAttributes->NumNormalLayers(); ++Idx) { if (TessellateOverlay(InMesh, InAttributes->GetNormalLayer(Idx), Pattern, TessData, bUseParallel, Progress, CompactInfo, OutAttributes->GetNormalLayer(Idx)) == false) { return false; } } } if (InAttributes->NumUVLayers()) { OutAttributes->SetNumUVLayers(InAttributes->NumUVLayers()); for (int Idx = 0; Idx < InAttributes->NumUVLayers(); ++Idx) { if (TessellateOverlay(InMesh, InAttributes->GetUVLayer(Idx), Pattern, TessData, bUseParallel, Progress, CompactInfo, OutAttributes->GetUVLayer(Idx)) == false) { return false; } } } if (InAttributes->HasPrimaryColors()) { OutAttributes->EnablePrimaryColors(); if (TessellateOverlay(InMesh, InAttributes->PrimaryColors(), Pattern, TessData, bUseParallel, Progress, CompactInfo, OutAttributes->PrimaryColors()) == false) { return false; } } if (InAttributes->HasMaterialID()) { OutAttributes->EnableMaterialID(); OutAttributes->GetMaterialID()->SetName(InAttributes->GetMaterialID()->GetName()); ConstructTriangleAttribute(InMesh, InAttributes->GetMaterialID(), TessData, bUseParallel, CompactInfo, OutAttributes->GetMaterialID()); if (Progress && Progress->Cancelled()) { return false; } } if (InAttributes->NumPolygroupLayers()) { OutAttributes->SetNumPolygroupLayers(InAttributes->NumPolygroupLayers()); for (int Idx = 0; Idx < InAttributes->NumPolygroupLayers(); ++Idx) { OutAttributes->GetPolygroupLayer(Idx)->SetName(InAttributes->GetPolygroupLayer(Idx)->GetName()); ConstructTriangleAttribute(InMesh, InAttributes->GetPolygroupLayer(Idx), TessData, bUseParallel, CompactInfo, OutAttributes->GetPolygroupLayer(Idx)); } if (Progress && Progress->Cancelled()) { return false; } } for (const TTuple>& AttributeInfo : InAttributes->GetSkinWeightsAttributes()) { FDynamicMeshVertexSkinWeightsAttribute* SkinAttribute = new FDynamicMeshVertexSkinWeightsAttribute(OutMesh); ConstructVertexSkinWeightsAttribute(InMesh, AttributeInfo.Value.Get(), TessData, bUseParallel, CompactInfo, SkinAttribute); SkinAttribute->SetName(AttributeInfo.Value.Get()->GetName()); OutAttributes->AttachSkinWeightsAttribute(AttributeInfo.Key, SkinAttribute); } if (Progress && Progress->Cancelled()) { return false; } if (InAttributes->NumWeightLayers() > 0) { OutAttributes->SetNumWeightLayers(InAttributes->NumWeightLayers()); for (int Idx = 0; Idx < InAttributes->NumWeightLayers(); ++Idx) { ConstructDynamicMeshVertexAttribute(InMesh, InAttributes->GetWeightLayer(Idx), TessData, bUseParallel, CompactInfo, OutAttributes->GetWeightLayer(Idx)); OutAttributes->GetWeightLayer(Idx)->SetName(InAttributes->GetWeightLayer(Idx)->GetName()); } } if (Progress && Progress->Cancelled()) { return false; } } if (InMesh->HasVertexNormals()) { OutMesh->EnableVertexNormals(FVector3f::Zero()); ConstructPerVertexNormals(InMesh, TessData, bUseParallel, CompactInfo, OutMesh); } if (Progress && Progress->Cancelled()) { return false; } if (InMesh->HasVertexUVs()) { OutMesh->EnableVertexUVs(FVector2f::Zero()); ConstructPerVertexUVs(InMesh, TessData, bUseParallel, CompactInfo, OutMesh); } if (Progress && Progress->Cancelled()) { return false; } if (InMesh->HasVertexColors()) { OutMesh->EnableVertexColors(FVector4f::Zero()); ConstructPerVertexColors(InMesh, TessData, bUseParallel, CompactInfo, OutMesh); } if (Progress && Progress->Cancelled()) { return false; } return true; } } // // GLSL pattern // TUniquePtr FSelectiveTessellate::CreateConcentricRingsTessellationPattern(const FDynamicMesh3* InMesh, const TArray& InEdgeTessLevels, const TArray& InInnerTessLevels) { TUniquePtr Pattern = MakeUnique(InMesh); Pattern->Init(InEdgeTessLevels, InInnerTessLevels); return Pattern; } TUniquePtr FSelectiveTessellate::CreateConcentricRingsTessellationPattern(const FDynamicMesh3* InMesh, const int InTessellationLevel) { TArray InEdgeTessLevels; InEdgeTessLevels.Init(InTessellationLevel, InMesh->MaxEdgeID()); TArray InInnerTessLevels; InInnerTessLevels.Init(InTessellationLevel, InMesh->MaxTriangleID()); return FSelectiveTessellate::CreateConcentricRingsTessellationPattern(InMesh, InEdgeTessLevels, InInnerTessLevels); } TUniquePtr FSelectiveTessellate::CreateConcentricRingsTessellationPattern(const FDynamicMesh3* InMesh, const int InTessellationLevel, const TArray& InTriangleList) { if (InTriangleList.IsEmpty()) { return nullptr; } TArray EdgeTessLevels; EdgeTessLevels.Init(0, InMesh->MaxEdgeID()); for (const int TriangleID : InTriangleList) { const FIndex3i EdgesIdx = InMesh->GetTriEdges(TriangleID); EdgeTessLevels[EdgesIdx[0]] = InTessellationLevel; EdgeTessLevels[EdgesIdx[1]] = InTessellationLevel; EdgeTessLevels[EdgesIdx[2]] = InTessellationLevel; } TArray TriangleTessLevels; TriangleTessLevels.Init(0, InMesh->MaxTriangleID()); for (const int TriangleID : InTriangleList) { TriangleTessLevels[TriangleID] = InTessellationLevel; } return FSelectiveTessellate::CreateConcentricRingsTessellationPattern(InMesh, EdgeTessLevels, TriangleTessLevels); } TUniquePtr FSelectiveTessellate::CreateConcentricRingsTessellationPattern(const TFunctionRef InEdgeFunc, const TFunctionRef InTriFunc) { // TODO: not implemented yet checkNoEntry(); return nullptr; } TUniquePtr FSelectiveTessellate::CreateConcentricRingsPatternFromTriangleGroup(const FDynamicMesh3* InMesh, const int InTessellationLevel, const int InPolygroupID) { if (InMesh->HasTriangleGroups() == false) { return nullptr; } TArray TriangleList; for (const int TID : InMesh->TriangleIndicesItr()) { const int PolygroupID = InMesh->GetTriangleGroup(TID); if (PolygroupID == InPolygroupID) { TriangleList.Add(TID); } } return FSelectiveTessellate::CreateConcentricRingsTessellationPattern(InMesh, InTessellationLevel, TriangleList); } TUniquePtr FSelectiveTessellate::CreateConcentricRingsPatternFromPolyGroup(const FDynamicMesh3* InMesh, const int InTessellationLevel, const FString& InLayerName, const int InPolygroupID) { if (InMesh->HasAttributes() == false) { return nullptr; } const FDynamicMeshPolygroupAttribute* PolygroupAttribute = nullptr; for (int Idx = 0; Idx < InMesh->Attributes()->NumPolygroupLayers(); ++Idx) { if (InMesh->Attributes()->GetPolygroupLayer(Idx)->GetName().ToString() == InLayerName) { PolygroupAttribute = InMesh->Attributes()->GetPolygroupLayer(Idx); break; } } if (PolygroupAttribute == nullptr) { return nullptr; } TArray TriangleList; for (const int TID : InMesh->TriangleIndicesItr()) { const int TrianglePolygroupID = PolygroupAttribute->GetValue(TID); if (TrianglePolygroupID == InPolygroupID) { TriangleList.Add(TID); } } return FSelectiveTessellate::CreateConcentricRingsTessellationPattern(InMesh, InTessellationLevel, TriangleList); } TUniquePtr FSelectiveTessellate::CreateConcentricRingsPatternFromMaterial(const FDynamicMesh3* InMesh, const int InTessellationLevel, const int MaterialID) { if (InMesh->HasAttributes() == false) { return nullptr; } if (InMesh->Attributes()->HasMaterialID() == false) { return nullptr; } TArray TriangleList; for (const int TID : InMesh->TriangleIndicesItr()) { const int TriangleMaterialID = InMesh->Attributes()->GetMaterialID()->GetValue(TID); if (TriangleMaterialID == MaterialID) { TriangleList.Add(TID); } } return FSelectiveTessellate::CreateConcentricRingsTessellationPattern(InMesh, InTessellationLevel, TriangleList); } // // Inner uniform pattern // TUniquePtr FSelectiveTessellate::CreateInnerUnifromTessellationPattern(const FDynamicMesh3* InMesh, const TArray& InEdgeTessLevels, const TArray& InInnerTessLevels) { // TODO: not implemented yet checkNoEntry(); return nullptr; } // // Uniform pattern // TUniquePtr CreateUniformTessellationPattern(const FDynamicMesh3* InMesh, const int InTessellationLevel, const TArray& InTriangleList) { // TODO: not implemented yet checkNoEntry(); return nullptr; } void FTessellationPattern::ComputeInnerConcentricTriangle(const FVector3d& V1, const FVector3d& V2, const FVector3d& V3, const int EdgeTessLevel, FVector3d& InnerU, FVector3d& InnerV, FVector3d& InnerW) const { //TODO: Handle the case where V1, V2, V3 form a degenerate triangle // Given two lines defined by a point and a normal find their intersection. It is guaranteed // that both lines are on the same plane and that they are not parallel. auto IntersectLines = [] (const FVector3d& V1, const FVector3d& N1, const FVector3d& V2, const FVector3d& N2) { FLine3d Line1(V1, N1); FLine3d Line2(V2, N2); FDistLine3Line3d LineDist(Line1, Line2); LineDist.ComputeResult(); FVector3d OffsetPoint = 0.5 * (LineDist.Line1ClosestPoint + LineDist.Line2ClosestPoint); return OffsetPoint; }; if (EdgeTessLevel < 0) { checkSlow(false); return; } if (EdgeTessLevel == 0) // edges are not tessellated { InnerU = V1; InnerV = V2; InnerW = V3; return; } else if (EdgeTessLevel == 1) // just a vertex in the middle { FVector3d Center = (V1 + V2 + V3)/3.0; InnerU = Center; InnerV = Center; InnerW = Center; return; } // Inserted vertices on each subdivided edge closest to the edge corners FVector3d EdgeDir1 = (V2 - V1) / (EdgeTessLevel + 1); FVector3d UV1 = V1 + EdgeDir1; FVector3d UV2 = V1 + EdgeTessLevel*EdgeDir1; FVector3d EdgeDir2 = (V3 - V1) / (EdgeTessLevel + 1); FVector3d UW1 = V1 + EdgeDir2; FVector3d UW2 = V1 + EdgeTessLevel*EdgeDir2; FVector3d EdgeDir3 = (V3 - V2) / (EdgeTessLevel + 1); FVector3d VW1 = V2 + EdgeDir3; FVector3d VW2 = V2 + EdgeTessLevel*EdgeDir3; // Normal vector of the plane formed by the triangle FVector3d PlaneNormal = Normalized(EdgeDir1.Cross(EdgeDir2)); // Edge normal vectors FVector3d OuterUVNormal = Normalized(PlaneNormal.Cross(EdgeDir1)); FVector3d OuterUWNormal = Normalized(EdgeDir2.Cross(PlaneNormal)); FVector3d OuterVWNormal = Normalized(PlaneNormal.Cross(EdgeDir3)); // Vertices of the inner triangle InnerU = IntersectLines(UV1, OuterUVNormal, UW1, OuterUWNormal); InnerV = IntersectLines(UV2, OuterUVNormal, VW1, OuterVWNormal); InnerW = IntersectLines(UW2, OuterUWNormal, VW2, OuterVWNormal); } bool FSelectiveTessellate::Cancelled() { return (Progress == nullptr) ? false : Progress->Cancelled(); } bool FSelectiveTessellate::Compute() { if (Validate() != EOperationValidationResult::Ok) { return false; } // Check for the empty meshes if (bInPlace && ResultMesh->TriangleCount() == 0) { return true; } if (bInPlace == false && Mesh->TriangleCount() == 0) { return true; } // Make a copy of the mesh since we are tessellating in place. The copy will be used to restore the mesh to its // original state in case the user cancelled the operation. TUniquePtr ResultMeshCopy = nullptr; if (bInPlace) { ResultMeshCopy = MakeUnique(); ResultMeshCopy->Copy(*ResultMesh); Mesh = ResultMeshCopy.Get(); } bool bTessResult = SelectiveTessellateLocals::Tessellate(Mesh, Pattern, bUseParallel, Progress, ResultMesh, TessInfo); if (Cancelled() || bTessResult == false) { if (bInPlace) { // Restore the input mesh checkSlow(ResultMeshCopy != nullptr); *ResultMesh = MoveTemp(*ResultMeshCopy); } else { ResultMesh->Clear(); } return false; } return true; }