Files
UnrealEngine/Engine/Plugins/Runtime/GeometryProcessing/Source/DynamicMesh/Private/Operations/SelectiveTessellate.cpp
2025-05-18 13:04:45 +08:00

2240 lines
73 KiB
C++

// 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<typename RealType, int ElementSize>
class FOverlayTessellationData;
bool ConstructTessellatedMesh(const FDynamicMesh3* Mesh,
FProgressCancel* Progress,
FTessellationData& TessData,
FCompactMaps& CompactInfo,
FDynamicMesh3* ResultMesh,
FSelectiveTessellate::FTessellationInformation& TessInfo);
template<typename RealType, int ElementSize>
bool ConstructTessellatedOverlay(const FDynamicMesh3* Mesh,
const TDynamicMeshOverlay<RealType, ElementSize>* Overlay,
FTessellationData& MeshTessData,
FOverlayTessellationData<RealType, ElementSize>& TessData,
const FCompactMaps& CompactInfo,
FProgressCancel* Progress,
TDynamicMeshOverlay<RealType, ElementSize>* ResultOverlay);
// Utility functions
template<typename RealType, int ElementSize>
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<typename RealType, int ElementSize>
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<int> EdgesToTessellate;
TSet<int> 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<int> EdgeIDs; // IDs of the vertices/elements added inside the tessellated edges
TMap<FIndex2i, FIndex2i> 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<double> EdgeCoord; // Lerp coefficients of the vertices/elements added along the tessellated edges
TMap<int, FIndex2i> EdgeCoordOffsets; // Maps Edge ID to tuple of (offset, length) numbers used to access a data
// block in the EdgeCoord arrays.
TArray<int> InnerIDs; // IDs of the vertices/elements added inside the tessellated triangles
TArray<FVector3d> InnerCoord; // Barycentric coordinates of the vertices added inside the tessellated triangles
TMap<int, FIndex2i> InnerOffsets; // Maps Triangle ID to tuple of (offset, length) numbers used to access
// a data block in the InnerIDs and InnerCoord arrays.
TArray<int> TriangleIDs; // IDs of the new triangles that the original triangle is split into
TArray<FIndex3i> Triangles; // Triangle indicies (i.e. vertex id, element id, etc)
TMap<int, FIndex2i> 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<double> MapEdgeCoordBufferBlock(const int EdgeID)
{
TArrayView<double> 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<int> MapEdgeIDBufferBlock(const int EdgeID)
{
TArrayView<int> 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<int> MapEdgeIDBufferBlock(const int EdgeID, const int TriangleID)
{
TArrayView<int> 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<FVector3d> MapInnerCoordBufferBlock(const int TriangleID)
{
TArrayView<FVector3d> 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<int> MapInnerIDBufferBlock(const int TriangleID)
{
TArrayView<int> 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<int> MapTriangleIDBufferBlock(const int TriangleID)
{
TArrayView<int> 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<FIndex3i> MapTrianglesBufferBlock(const int TriangleID)
{
TArrayView<FIndex3i> 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<typename RealType, int ElementSize>
class FOverlayTessellationData : public FTessellationData
{
protected:
const TDynamicMeshOverlay<RealType, ElementSize>* Overlay = nullptr;
public:
FOverlayTessellationData(const FDynamicMesh3* InMesh, const TDynamicMeshOverlay<RealType, ElementSize>* 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<int> 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<int> 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<int>& InEdgeTessLevels, const TArray<int>& 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<FRing> 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<FIndex3i>& 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<int> InnerTessLevels;
/** An array of per edge tessellation levels. The size must match the maximum edge ID of the mesh. */
TArray<int> 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<int> 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<int> 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<typename RealType, int ElementSize>
bool TessellateOverlay(const FDynamicMesh3* Mesh,
const TDynamicMeshOverlay<RealType, ElementSize>* Overlay,
const FTessellationPattern* Pattern,
FTessellationData& MeshTessData,
const bool bUseParallel,
FProgressCancel* Progress,
const FCompactMaps& CompactInfo,
TDynamicMeshOverlay<RealType, ElementSize>* 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<int> 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<int> 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<typename RealType, int ElementSize>
void ConstructTriangleAttribute(const FDynamicMesh3* Mesh,
const TDynamicMeshTriangleAttribute<RealType, ElementSize>* Attribute,
FTessellationData& TessData,
const bool bUseParallel,
const FCompactMaps& CompactInfo,
TDynamicMeshTriangleAttribute<RealType, ElementSize>* OutAttribute)
{
RealType Value[ElementSize];
for (const int TID : Mesh->TriangleIndicesItr())
{
Attribute->GetValue(TID, Value);
if (TessData.TrianglesToTessellate.Contains(TID))
{
TArrayView<int> 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<double> CoordBlock = TessData.MapEdgeCoordBufferBlock(EdgeID);
TArrayView<int> 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<FVector3d> CoordBlock = TessData.MapInnerCoordBufferBlock(TriangleID);
TArrayView<int> 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<FIndex3i> TrianglesBlock = TessData.MapTrianglesBufferBlock(TriangleID);
TArrayView<int> 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<int> 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<typename RealType, int ElementSize>
bool ConstructTessellatedOverlay(const FDynamicMesh3* Mesh,
const TDynamicMeshOverlay<RealType, ElementSize>* Overlay,
FTessellationData& MeshTessData,
FOverlayTessellationData<RealType, ElementSize>& OverlayTessData,
const FCompactMaps& CompactInfo,
FProgressCancel* Progress,
TDynamicMeshOverlay<RealType, ElementSize>* ResultOverlay)
{
ResultOverlay->ClearElements();
// Need to track the ID of the appended elements to handle non-compact meshes
TMap<int, int> 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<double> CoordBlock = OverlayTessData.MapEdgeCoordBufferBlock(EdgeID);
if (Overlay->IsSetTriangle(EdgeTri.A))
{
TArrayView<int> 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<RealType, ElementSize>(Element1, Element2, Out, Alpha);
MapE.Add(ElementID, ResultOverlay->AppendElement(Out));
}
}
if (EdgeTri.B != FDynamicMesh3::InvalidID && Overlay->IsSetTriangle(EdgeTri.B) && Overlay->IsSeamEdge(EdgeID))
{
TArrayView<int> 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<RealType, ElementSize>(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<FVector3d> CoordBlock = OverlayTessData.MapInnerCoordBufferBlock(TriangleID);
TArrayView<int> 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<RealType, ElementSize>(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<FIndex3i> TrianglesBlock = OverlayTessData.MapTrianglesBufferBlock(TriangleID);
TArrayView<int> 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<void(int V1, int V2, int NewV, double U)> InterpolateEdgeFunc,
const TFunctionRef<void(int V1, int V2, int V3, int NewV, double U, double V, double W)> 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<double> CoordBlock = TessData.MapEdgeCoordBufferBlock(EdgeID);
TArrayView<int> 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<FVector3d> CoordBlock = TessData.MapInnerCoordBufferBlock(TriangleID);
TArrayView<int> 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<typename RealType, int ElementSize>
void ConstructDynamicMeshVertexAttribute(const FDynamicMesh3* Mesh,
const TDynamicMeshVertexAttribute<RealType, ElementSize>* Attribute,
FTessellationData& TessData,
const bool bUseParallel,
const FCompactMaps& CompactInfo,
TDynamicMeshVertexAttribute<RealType, ElementSize>* 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<RealType, ElementSize>(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<RealType, ElementSize>(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<FName, TUniquePtr<FDynamicMeshVertexSkinWeightsAttribute>>& 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<float, 1>(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<FTessellationPattern> FSelectiveTessellate::CreateConcentricRingsTessellationPattern(const FDynamicMesh3* InMesh,
const TArray<int>& InEdgeTessLevels,
const TArray<int>& InInnerTessLevels)
{
TUniquePtr<SelectiveTessellateLocals::FConcentricRingsTessellationPattern> Pattern = MakeUnique<SelectiveTessellateLocals::FConcentricRingsTessellationPattern>(InMesh);
Pattern->Init(InEdgeTessLevels, InInnerTessLevels);
return Pattern;
}
TUniquePtr<FTessellationPattern> FSelectiveTessellate::CreateConcentricRingsTessellationPattern(const FDynamicMesh3* InMesh,
const int InTessellationLevel)
{
TArray<int> InEdgeTessLevels;
InEdgeTessLevels.Init(InTessellationLevel, InMesh->MaxEdgeID());
TArray<int> InInnerTessLevels;
InInnerTessLevels.Init(InTessellationLevel, InMesh->MaxTriangleID());
return FSelectiveTessellate::CreateConcentricRingsTessellationPattern(InMesh, InEdgeTessLevels, InInnerTessLevels);
}
TUniquePtr<FTessellationPattern> FSelectiveTessellate::CreateConcentricRingsTessellationPattern(const FDynamicMesh3* InMesh,
const int InTessellationLevel,
const TArray<int>& InTriangleList)
{
if (InTriangleList.IsEmpty())
{
return nullptr;
}
TArray<int> 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<int> TriangleTessLevels;
TriangleTessLevels.Init(0, InMesh->MaxTriangleID());
for (const int TriangleID : InTriangleList)
{
TriangleTessLevels[TriangleID] = InTessellationLevel;
}
return FSelectiveTessellate::CreateConcentricRingsTessellationPattern(InMesh, EdgeTessLevels, TriangleTessLevels);
}
TUniquePtr<FTessellationPattern> FSelectiveTessellate::CreateConcentricRingsTessellationPattern(const TFunctionRef<int(const int EdgeID)> InEdgeFunc,
const TFunctionRef<int(const int TriangleID)> InTriFunc)
{
// TODO: not implemented yet
checkNoEntry();
return nullptr;
}
TUniquePtr<FTessellationPattern> FSelectiveTessellate::CreateConcentricRingsPatternFromTriangleGroup(const FDynamicMesh3* InMesh,
const int InTessellationLevel,
const int InPolygroupID)
{
if (InMesh->HasTriangleGroups() == false)
{
return nullptr;
}
TArray<int> 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<FTessellationPattern> 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<int> 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<FTessellationPattern> 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<int> 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);
}
TUniquePtr<FTessellationPattern> FSelectiveTessellate::CreateConcentricRingsPatternFromSelectionAndMaterial(const FDynamicMesh3* InMesh,
const int InTessellationLevel,
const int MaterialID,
const TArray<int>& SelectedTriangles)
{
if (InMesh->HasAttributes() == false || InMesh->Attributes()->HasMaterialID() == false || SelectedTriangles.IsEmpty())
{
return nullptr;
}
TArray<int> SelectedTrianglesWithMaterial;
for (const int TID : SelectedTriangles)
{
const int TriangleMaterialID = InMesh->Attributes()->GetMaterialID()->GetValue(TID);
if (TriangleMaterialID == MaterialID)
{
SelectedTrianglesWithMaterial.Add(TID);
}
}
return FSelectiveTessellate::CreateConcentricRingsTessellationPattern(InMesh, InTessellationLevel, SelectedTrianglesWithMaterial);
}
//
// Inner uniform pattern
//
TUniquePtr<FTessellationPattern> FSelectiveTessellate::CreateInnerUnifromTessellationPattern(const FDynamicMesh3* InMesh,
const TArray<int>& InEdgeTessLevels,
const TArray<int>& InInnerTessLevels)
{
// TODO: not implemented yet
checkNoEntry();
return nullptr;
}
//
// Uniform pattern
//
TUniquePtr<FTessellationPattern> CreateUniformTessellationPattern(const FDynamicMesh3* InMesh,
const int InTessellationLevel,
const TArray<int>& 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<FDynamicMesh3> ResultMeshCopy = nullptr;
if (bInPlace)
{
ResultMeshCopy = MakeUnique<FDynamicMesh3>();
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;
}