// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "GeometryTypes.h" #include "IndexTypes.h" #include "Containers/Array.h" #include "Containers/ArrayView.h" #include "Math/MathFwd.h" #define UE_API DYNAMICMESH_API class FProgressCancel; namespace UE { namespace Geometry { class FDynamicMesh3; /** * An abstract class for writing tessellation patterns. You can subclass it and implement its virtual methods to specify * a custom tessellation behavior. Original mesh edges and triangles are referred to as edge patches and triangle patches. * The edge patch handles all the vertices we are adding along the edge. The triangle patch handles all the vertices we * are adding inside of the patch (excluding the edges) and the new triangles we are splitting the patch into. * * Patterns describe how individual patches are tessellated. Although you can access mesh data during the tessellation, * the patches do not generate actual vertex coordinates or other attribute data. Instead, new vertices are generated in * terms of linear or barycentric coefficients with respect to the edge or triangle vertices. The coefficient data can * later be used to interpolate the actual mesh data (vertex positions, colors, normals, etc). * * Patterns do not need to worry about generating the vertex IDs as those will be provided for each patch during the * tessellation and need to simply be referred to when generating triangles. * * The job of the tessellation pattern is: * * - Provide the number of new vertices introduced for an edge or a triangle patch. * * - Provide the number of new triangles introduced for a triangle patch. * * - Given a single edge patch with vertices [v1,v2], provide coordinates of the new vertices * [a_i, a_(i+1), ...] inserted along the edge via a linear coeffcient [t_i, t_(i+1), ...] such that * a_i = v1 + t_i*(v2-v1). * * - Given a triangle patch with vertices [v1,v2,v3], provide coordinates of the new vertices [b_i, b_(i+1), ...] * inserted inside the triangle patch via barycentric coordinates [(u_i, v_i, w_i), (u_(i+1), v_(i+1), w_(i+1)), ...] * such that b_i = u_i*v1 + v_i*v2 + w_i*v3. * * - Using the new vertices (along edges and inside the triangle), provide connectivity for a triangle patch by * stitching those vertices together and creating triangles. */ class FTessellationPattern { public: constexpr static int InvalidIndex = -1; protected: const FDynamicMesh3* Mesh; public: /** * Represents an abstract edge as a parameterized line segment with two endpoints [v1,v2]. Contains information * about the linear interpolation parameter of the new vertices added along the edge and their vertex IDs. * * O----x----x----x----O x - New vertices added. We only store their lerp coefficients (t1,t2,t3) * v1 a1 a2 a3 v2 with respect to the end points, s.t. a_i = vi + t_i*(v2-v1). */ struct EdgePatch { int EdgeID = IndexConstants::InvalidID; TArrayView VIDs; // Vertex IDs of the new vertices added. TArrayView LinearCoord; // Lerp cooeficients of the new vertices added. bool bIsReversed = false; // If true, we need to traverse BaryCoord and VIDs in reverse order. // This happens when the directed edge of the triangle is reversed // with the respect to the edge direction stored in the FDynamicMesh3. }; /** * Represents an abstract triangle patch with corners [u,v,w], s.t. u = (1,0,0), v = (0,1,0), w = (0,0,1). * Contains information about the barycentric coordinates and IDs of the new vertices and the new triangles * generated during the tessellation. */ struct TrianglePatch { // ID of the triangle in the original mesh this patch represents int TriangleID = IndexConstants::InvalidID; // Vertex IDs of the 3 corners of the patch. These are the triangle vertex IDs in the original mesh. FIndex3i UVWCorners = FIndex3i::Invalid(); // Edge patches EdgePatch UVEdge; EdgePatch VWEdge; EdgePatch UWEdge; // Vertex IDs of the inner vertices added inside the triangle. TArrayView VIDs; // Barycentric coordinates of the inner vertices. TArrayView BaryCoord; // Triangles the patch is split into TArrayView Triangles; }; FTessellationPattern(const FDynamicMesh3* InMesh) : Mesh(InMesh) { } virtual ~FTessellationPattern() { } /** * @return the number of new vertices added along the edge patch. If InEdgeID is not valid then return * FTessellationPattern::InvalidIndex. */ virtual int GetNumberOfNewVerticesForEdgePatch(const int InEdgeID) const = 0; /** * @return the number of new vertices added inside the triangle patch (i.e. exclude vertices added * along edges). If InTriangleID is not valid then return FTessellationPattern::InvalidIndex. */ virtual int GetNumberOfNewVerticesForTrianglePatch(const int InTriangleID) const = 0; /** * @return the number of triangles that will be generated after tessellating the patch. If InTriangleID is not * valid then return FTessellationPattern::InvalidIndex. */ virtual int GetNumberOfPatchTriangles(const int InTriangleID) const = 0; /** * Tessellate the edge patch by generating linear interpolation coefficients for each new vertex and storing the * result in the EdgePatch.LinearCoord. * * @param EdgePatch Edge patch has EdgePatch.EdgeID and EdgePatch.VIDs fields set. The order of IDs is along the * edge from one corner to another. The edge direction is same as the direction stored by the FDynamicMesh3 class. */ virtual void TessellateEdgePatch(EdgePatch& EdgePatch) const = 0; /** * Tessellate the triangle patch by generating new vertices inside the triangle. * Compute the barycentric coordinates of the new vertices and store them in the TriPatch.BaryCoord. * Using the vertices generated in the edge patches and inside the triangle patch, generate new triangles and * store the result in the TriPatch.Triangles. * * @param TriPatch Has all the fields set except the values in the TriPatch.BaryCoord and TriPatch.Triangles which * should be set by this method. Both arrays are already sized correctly to be able to hold all the * data needed as specified by the GetNumberOfNewVerticesForTrianglePatch() and GetNumberOfPatchTriangles(). */ virtual void TessellateTriPatch(TrianglePatch& TriPatch) const = 0; // // Common helper methods used during tessellation of various patterns. These might be helpful when writing custom // tessellation patterns. // protected: /** * Takes a triangle defined by three vertices and a TessellationLevel value. For each corner of the outer triangle, * an inner triangle corner is produced at the intersection of two lines extended perpendicular to the corner's two * adjacent edges running through the vertex of the subdivided outer edge nearest to that corner. * * U * O * / \ O - triangle corner * / \ x - closest vertex to the corner inserted along the edge during tessellation * / \ + - inner vertex resulting from intersection of two lines extened perpendicular to the edges * x x * / . . \ * / + \ * / innerU \ * * @note Vertices should be passed in CW order. */ UE_API void ComputeInnerConcentricTriangle(const FVector3d& U, const FVector3d& V, const FVector3d& W, const int EdgeTessellationLevel, FVector3d& InnerU, FVector3d& InnerV, FVector3d& InnerW) const; }; /** * Given an input mesh and a tessellation pattern, this operator generates a new tessellated mesh where the * triangles affected are subtriangulated according to the rules specified by the pattern. Per-vertex normals, UVs, * colors, and extended per-vertex attributes are linearly interpolated to the new vertices. Per-triangle group * identifiers/materials and extended triangle attributes for the new triangles are inherited from the corresponding * input mesh triangles they replaced. * * @note Currently does not support interpolation of the GenericAttributes besides Skin Weights. */ class FSelectiveTessellate { public: // // Inputs // /** * Set this to be able to cancel running operation. * @note currently not implemented */ FProgressCancel* Progress = nullptr; /** Should tessellation be multi-threaded. */ bool bUseParallel = true; /** Tessellation pattern determines how we tessellate edges and triangles. */ FTessellationPattern* Pattern = nullptr; // // Input/Output // /** The tessellated mesh. */ FDynamicMesh3* ResultMesh = nullptr; // // Output // /** * Stores any additional tessellation information that was requested by the caller. * The caller should set the pointers to request the information to be computed. * * @note The caller is responsible for managing the memmory the pointers are pointing to. * The operator simply populates the data structures if they are not null. */ struct FTessellationInformation { /** * If not null, construction of tessellated mesh will append a list of all the vertices that belong to * triangles affected by the tessellation. */ TArray* SelectedVertices = nullptr; }; FTessellationInformation TessInfo; protected: /** * Points to either the input mesh to be used to generate the new tessellated mesh or will point to a copy of the * mesh we are tessellating inplace. */ const FDynamicMesh3* Mesh = nullptr; /** * If true, the mesh to tessellate is contained in the ResultMesh and will be overwritten with the tessellation * result. */ bool bInPlace = false; public: /** * Tessellate the mesh inplace. If the tessellation fails or the user cancelled the operation, OutMesh will not * be changed. * //TODO: Add an option to turn off making the backup copy of the input mesh in case the computation fails. */ FSelectiveTessellate(FDynamicMesh3* OutMesh) : ResultMesh(OutMesh), bInPlace(true) { } /** * Tessellate the mesh and write the result into another mesh. This will overwrite any data stored in the OutMesh. */ FSelectiveTessellate(const FDynamicMesh3* Mesh, FDynamicMesh3* OutMesh) : ResultMesh(OutMesh), Mesh(Mesh), bInPlace(false) { } virtual ~FSelectiveTessellate() { } /** * @return EOperationValidationResult::Ok if we can apply operation, or error code if we cannot. */ virtual EOperationValidationResult Validate() { const bool bIsInvalid = bInPlace == false && (Mesh == nullptr || ResultMesh == nullptr || Pattern == nullptr); const bool bIsInvalidInPlace = bInPlace == true && (ResultMesh == nullptr || Pattern == nullptr); if (bIsInvalid || bIsInvalidInPlace) { return EOperationValidationResult::Failed_UnknownReason; } return EOperationValidationResult::Ok; } inline void SetPattern(FTessellationPattern* InPattern) { Pattern = InPattern; } /** * Generate tessellated geometry. * @return true if the algorithm succeeds, false if it failed or was cancelled by the user. */ UE_API virtual bool Compute(); // // Helper methods for setting up built-in tessellation patterns. Currently glsl, uniform and inner uniform // patterns are provided. You can create custom patterns by subclassing FTessellationPattern. // public: /** * Pattern where the inner area is tessellated using the style of the OpenGL tessellation shader. The inner area * of each tessellated triangle consists of multiple "rings". Areas between the rings are tessellated with the new * triangles. * * You can specify per-edge and per-triangle tessellation levels. * Per-edge tessellation level determines how many new vertices are inserted along the new edge. * Per-triangle tessellation level determines the number of new vertices along each of the edges of the first * inner ring. Each subsequent ring will have 2 fewer vertices. * * Triangles with a zero inner tessellation level but with a non-zero edge tessellation level (common for those triangles * adjacent to the triangles marked for tessellation) will have a single vertex inserted in the middle and connected * to all edge vertices (triangle fan). * * * O * / \ * O. .O * / O \ * O. / \ .O * / O. .O \ * / / O \ \ * / / / \ \ \ * O. / / \ \ .O * / O. / \ .O \ * / / O-------O \ \ * O. / . . \ .O * / O----O-------O----O \ * / . . . . \ * O----O----O-------O----O----O * * @param InMesh Mesh we are tessellating. * @param InEdgeTessLevels Array of InMesh->MaxEdgeID() size, where InEdgeTessLevels[EdgeID] is the number of new * vertices inserted along an EdgeID edge. * @param InInnerTessLevels Array of InMesh->MaxTriangleID() size, where InInnerTessLevels[TriangleID] is the * number of new vertices along each of the edges of the first inner ring. */ static UE_API TUniquePtr CreateConcentricRingsTessellationPattern(const FDynamicMesh3* InMesh, const TArray& InEdgeTessLevels, const TArray& InInnerTessLevels); /** * Tessellate the whole mesh such that all the edge and inner tessellation levels are equal to InTessellationLevel. */ static UE_API TUniquePtr CreateConcentricRingsTessellationPattern(const FDynamicMesh3* InMesh, const int InTessellationLevel); /** * Tessellate only selected triangles such that the edge and inner tessellation levels of those triangles are set to * InTessellationLevel. */ static UE_API TUniquePtr CreateConcentricRingsTessellationPattern(const FDynamicMesh3* InMesh, const int InTessellationLevel, const TArray& InTriangleList); /** * InEdgeFunc and InTriFunc functions will be evaluated on all edges and triangles respectively to determine their * desired tessellation levels. This can be helpful when the tessellation levels depend on some condition. For * example, how close/far the edge/triangle is from some point in space. * * @note not yet implemented */ static UE_API TUniquePtr CreateConcentricRingsTessellationPattern(const TFunctionRef InEdgeFunc, const TFunctionRef InTriFunc); /** * Tessellate only triangles that belong to a given triangle group id. Triangle groups are defined by the * FDynamicMesh3::GetTriangleGroup(). */ static UE_API TUniquePtr CreateConcentricRingsPatternFromTriangleGroup(const FDynamicMesh3* InMesh, const int InTessellationLevel, const int InPolygroupID); /** * Tessellate only triangles that belong to a given polygroup id within the polygroup layer attribute * (FDynamicMeshPolygroupAttribute) specified by its name. * * @return nullptr if InMesh has no attributes or if no polygroup layer attribute with the given name or id exists. */ static UE_API TUniquePtr CreateConcentricRingsPatternFromPolyGroup(const FDynamicMesh3* InMesh, const int InTessellationLevel, const FString& InLayerName, const int InPolygroupID); /** * Tessellate only triangles that belong to a given material (FDynamicMeshMaterialAttribute). * * @return nullptr if InMesh has no attributes, no material attribute or if no material attribute with the given id exists. */ static UE_API TUniquePtr CreateConcentricRingsPatternFromMaterial(const FDynamicMesh3* InMesh, const int InTessellationLevel, const int MaterialID); /** * Tessellate only triangles that are selected AND which belong to a given material (FDynamicMeshMaterialAttribute). * * @return nullptr if InMesh has no attributes, no material attribute, if no material attribute with the given id exists, or if selection is invalid */ static UE_API TUniquePtr CreateConcentricRingsPatternFromSelectionAndMaterial(const FDynamicMesh3* InMesh, const int InTessellationLevel, const int MaterialID, const TArray& SelectedTriangles); /** * Pattern where the inner area is tessellated using the uniform loop style subdivision. Each edge can have * different tessellation levels and edge vertices are connected to the inner uniformly tessellated sub-triangle. * * Triangles with a zero inner tessellation level but with a non-zero edge tessellation level (common for those triangles * adjacent to the triangles marked for tessellation) will have a single vertex inserted in the middle and connected * to all edge vertices (triangle fan). * * o * / \ * / \ * o o o * / / \ \ * / / \ \ * / o-----o \ * o / \ / \ o * / / \ / \ \ * / o-----o-----o \ * / \ * o----------o----------o * * @note not yet implemented */ static UE_API TUniquePtr CreateInnerUnifromTessellationPattern(const FDynamicMesh3* InMesh, const TArray& InEdgeTessLevels, const TArray& InInnerTessLevels); //TODO: add more variation of different ways to provide tessellation levels similar to the ConcentricRings pattern. /** * Pattern where the whole triangle is tessellated using the loop style subdivision. Defined by a single * tessellation level, so you can not specify per-triangle or per-edge levels separately. * * Triangles which are not marked for tessellation but which are adjacent to the triangles that are being tessellated * will have a single vertex inserted in the middle and connected to all edge vertices (triangle fan). * * O * / \ * / \ * O-----O * / \ / \ * / \ / \ * O-----O-----O * / \ / \ / \ * / \ / \ / \ * O-----O-----O-----O * * @note not yet implemented */ static UE_API TUniquePtr CreateUniformTessellationPattern(const FDynamicMesh3* InMesh, const int InTessellationLevel, const TArray& InTriangleList); //TODO: add more variation of different ways to provide tessellation levels similar to the ConcentricRings pattern. protected: /** @return true if we need to abort the computation. */ UE_API virtual bool Cancelled(); }; } // end namespace UE::Geometry } // end namespace UE #undef UE_API