// Copyright Epic Games, Inc. All Rights Reserved. #include "Operations/PNTriangles.h" #include "IndexTypes.h" #include "VectorTypes.h" #include "DynamicMesh/MeshNormals.h" #include "DynamicMesh/DynamicMesh3.h" #include "Async/ParallelFor.h" #include "Util/ProgressCancel.h" #include "Operations/UniformTessellate.h" using namespace UE::Geometry; namespace FPNTrianglesLocals { /** * PN Triangle is represented by a control points cage. Each edge has 2 control points and * 1 control point is inside the triangle. Additionally, each edge contains one control point * of the normal component. We separate control points into 2 categories to avoid repeating * computations since control points on edges are shared. */ using FTriangleControlPoint = FVector3d; struct FEdgeControlPoints { FVector3d Point1; FVector3d Point2; FVector3d NormalTriA; FVector3d NormalTriB; }; struct FControlPoints { TArray OnEdges; // Map edge ID to the edge control points TArray OnTriangles; // Map triangle ID to the triangle center control point }; /** * Compute PN Triangle control points and optionally their normal component. * * @param Mesh The mesh used to compute the per-triangle control points. * @param UseNormals If normals are disabled for the Mesh then pass manually computed normals. * @param bComputePNNormals Compute control points for the normal component for calculating quadratically varying normals. * @param ProgressCancel Set this to be able to cancel running operation. * @param OutControlPoints Contains all the control points for the Mesh. * * @return true if the operation succeeded, false if it failed or was canceled by the user. */ bool ComputeControlPoints(FControlPoints& OutControlPoints, const FDynamicMesh3& Mesh, const FMeshNormals* UseNormals, const bool bComputePNNormals, FProgressCancel* ProgressCancel) { if (!Mesh.HasAttributes() && !Mesh.HasVertexNormals() && UseNormals == nullptr) { return false; // Mesh must have normals } // We know ahead of time the total number of control points: // 2 control points per edge and one in the middle of each triangle. OutControlPoints.OnEdges.SetNum(Mesh.MaxEdgeID()); OutControlPoints.OnTriangles.SetNum(Mesh.MaxTriangleID()); auto ComputeControlPoint = [](const FVector3d& Vertex1, const FVector3d& Vertex2, const FVector3f& Normal) { FVector3d Edge12 = Vertex2 - Vertex1; double Weight12 = Edge12.Dot(FVector3d(Normal)); FVector3d Point = (2.0*Vertex1 + Vertex2 - Weight12*FVector3d(Normal))/3.0; return Point; }; auto ComputeControlNormal = [](const FVector3d& Vertex1, const FVector3d& Vertex2, const FVector3f& Normal1, const FVector3f& Normal2) { FVector3d Edge12 = Vertex2 - Vertex1; FVector3f Normal21 = Normal2 + Normal1; double Divisor = Edge12.Dot(Edge12); FVector3f Normal; if (FMath::IsNearlyZero(Divisor)) { Normal = 0.5f * Normal21; // If edge is degenerate then simply interpolate between the normals } else { double NWeight12 = 2.0*Edge12.Dot(FVector3d(Normal21))/Divisor; Normal = Normalized(Normal21 - NWeight12*FVector3f(Edge12)); } return Normal; }; // First compute only the control points on the edges ParallelFor(Mesh.MaxEdgeID(), [&](int32 EID) { if (ProgressCancel && ProgressCancel->Cancelled()) { return; } if (Mesh.IsEdge(EID)) { FIndex2i EdgeV = Mesh.GetEdgeV(EID); FVector3d Vertex1 = Mesh.GetVertex(EdgeV.A); FVector3d Vertex2 = Mesh.GetVertex(EdgeV.B); FIndex2i EdgeTri = Mesh.GetEdgeT(EID); FVector3f Normal1, Normal2; if (Mesh.HasAttributes()) { const FDynamicMeshNormalOverlay* NormalOverlay = Mesh.Attributes()->PrimaryNormals(); if (NormalOverlay->IsSetTriangle(EdgeTri.A)) { NormalOverlay->GetElementAtVertex(EdgeTri.A, EdgeV.A, Normal1); NormalOverlay->GetElementAtVertex(EdgeTri.A, EdgeV.B, Normal2); } else { Normal1 = static_cast(Mesh.GetTriNormal(EdgeTri.A)); Normal2 = Normal1; } } else { Normal1 = (UseNormals != nullptr) ? (FVector3f)(*UseNormals)[EdgeV.A] : Mesh.GetVertexNormal(EdgeV.A); Normal2 = (UseNormals != nullptr) ? (FVector3f)(*UseNormals)[EdgeV.B] : Mesh.GetVertexNormal(EdgeV.B); } FEdgeControlPoints& ControlPnts = OutControlPoints.OnEdges[EID]; if (bComputePNNormals) { ControlPnts.NormalTriA = FVector3d(ComputeControlNormal(Vertex1, Vertex2, Normal1, Normal2)); } FVector3d Point1ForTriA = ComputeControlPoint(Vertex1, Vertex2, Normal1); FVector3d Point2ForTriA = ComputeControlPoint(Vertex2, Vertex1, Normal2); if (Mesh.HasAttributes() && Mesh.Attributes()->PrimaryNormals()) { const FDynamicMeshNormalOverlay* NormalOverlay = Mesh.Attributes()->PrimaryNormals(); if (EdgeTri.B != FDynamicMesh3::InvalidID && NormalOverlay->IsSeamEdge(EID)) { if (NormalOverlay->IsSetTriangle(EdgeTri.B)) { NormalOverlay->GetElementAtVertex(EdgeTri.B, EdgeV.A, Normal1); NormalOverlay->GetElementAtVertex(EdgeTri.B, EdgeV.B, Normal2); } else { Normal1 = static_cast(Mesh.GetTriNormal(EdgeTri.B)); Normal2 = Normal1; } FVector3d Point1ForTriB = ComputeControlPoint(Vertex1, Vertex2, Normal1); FVector3d Point2ForTriB = ComputeControlPoint(Vertex2, Vertex1, Normal2); // Following the core idea of Crack-free PN Triangles we average the control point we'd like // with the control point our neighbor triangle would like. Point1ForTriA = (Point1ForTriA + Point1ForTriB) / 2.0; Point2ForTriA = (Point2ForTriA + Point2ForTriB) / 2.0; if (bComputePNNormals) { ControlPnts.NormalTriB = FVector3d(ComputeControlNormal(Vertex1, Vertex2, Normal1, Normal2)); } } } ControlPnts.Point1 = Point1ForTriA; ControlPnts.Point2 = Point2ForTriA; } }); if (ProgressCancel && ProgressCancel->Cancelled()) { return false; } // Compute the center control point for each triangle ParallelFor(Mesh.MaxTriangleID(), [&](int32 TID) { if (ProgressCancel && ProgressCancel->Cancelled()) { return; } if (Mesh.IsTriangle(TID)) { const FIndex3i& TriEdgeID = Mesh.GetTriEdgesRef(TID); FVector3d ControlMidpoint = FVector3d::Zero(); for (int EIdx = 0; EIdx < 3; ++EIdx) { const FEdgeControlPoints& ControlPnts = OutControlPoints.OnEdges[TriEdgeID[EIdx]]; ControlMidpoint += ControlPnts.Point1 + ControlPnts.Point2; } ControlMidpoint /= 6.0; const FIndex3i& TriVertID = Mesh.GetTriangleRef(TID); FVector3d VertexMidpoint = (Mesh.GetVertex(TriVertID.A) + Mesh.GetVertex(TriVertID.B) + Mesh.GetVertex(TriVertID.C))/3.0; OutControlPoints.OnTriangles[TID] = ControlMidpoint + (ControlMidpoint - VertexMidpoint)/2.0; } }); if (ProgressCancel && ProgressCancel->Cancelled()) { return false; } return true; } /** * Tessellate the mesh by recursively subdividing it using loop-style subdivision. Keep track of the new vertices * added and which original triangles (before tessellation) they belong to. New vertices on original non-boundary * edges can belong to either of the original triangles that share the edge. * * @param Mesh The mesh we are tessellating. * @param Level How many times we are recursively subdividing the Mesh. * @param ProgressCancel Set this to be able to cancel running operation. * @param OutNewVertices Array of tuples of the new vertex ID and the original triangle ID the vertex belongs to. * @param OutMesh Result of the tessellation. * * @return true if the operation succeeded, false if it failed or was canceled by the user. */ bool TessellateMesh(const FDynamicMesh3& Mesh, const int32 Level, FProgressCancel* ProgressCancel, TArray& OutNewVertices, FDynamicMesh3& OutMesh) { checkSlow(Level >= 0); if (Level < 0) { return false; } else if (Level == 0) { return true; // nothing to do } FUniformTessellate Tessellator(&Mesh, &OutMesh); Tessellator.TessellationNum = Level; Tessellator.bComputeMappings = true; if (Tessellator.Validate() != EOperationValidationResult::Ok) { return false; } if (Tessellator.Compute() == false) { return false; } const int32 NewVertCount = Tessellator.VertexEdgeMap.Num() + Tessellator.VertexTriangleMap.Num(); OutNewVertices.Reserve(NewVertCount); for (auto It = Tessellator.VertexEdgeMap.CreateConstIterator(); It; ++It) { FIndex2i EdgeTri = It.Value(); OutNewVertices.Add(FIndex2i(It.Key(), EdgeTri.A)); } for (auto It = Tessellator.VertexTriangleMap.CreateConstIterator(); It; ++It) { OutNewVertices.Add(FIndex2i(It.Key(), It.Value())); } checkSlow(OutNewVertices.Num() == NewVertCount); return true; } /** * Displace the vertices created from the tessellation using the cubic patch formula based on their barycentric * coordinates. * * @param OriginalMesh The original mesh (before tessellation). * @param FControlPoints PN Triangle control points. * @param VerticesToDisplace Array of vertices into the Mesh that we are displacing. * @param bComputePNNormals Should we be computing quadratically varying normals using control points. * @param ProgressCancel Set this to be able to cancel running operation. * @param Mesh The tessellated mesh whose vertices we are displacing. * * @return true if the operation succeeded, false if it failed or was canceled by the user. */ bool DisplaceAndSetQuadraticNormals(const FDynamicMesh3& OriginalMesh, const FControlPoints& FControlPoints, const TArray& VerticesToDisplace, const bool bComputePNNormals, FProgressCancel* ProgressCancel, FDynamicMesh3& Mesh) { bool bHasVertexNormals = Mesh.HasVertexNormals(); bool bHasAttributes = Mesh.HasAttributes(); // Iterate over every new vertex and compute its displacement and optionally a normal ParallelFor(VerticesToDisplace.Num(), [&](int32 IDX) { if (ProgressCancel && ProgressCancel->Cancelled()) { return; } FIndex2i NewVtx = VerticesToDisplace[IDX]; int VertexID = NewVtx[0]; // ID of the new vertex added with tessellation int OriginalTriangleID = NewVtx[1]; // ID of the original triangle new vertex belongs to // Get the topology information of the original triangle FIndex3i TriVertex = OriginalMesh.GetTriangle(OriginalTriangleID); FIndex3i TriEdges = OriginalMesh.GetTriEdges(OriginalTriangleID); FVector3d Bary = VectorUtil::BarycentricCoords(Mesh.GetVertexRef(VertexID), OriginalMesh.GetVertexRef(TriVertex.A), OriginalMesh.GetVertexRef(TriVertex.B), OriginalMesh.GetVertexRef(TriVertex.C)); FVector3d BarySquared = Bary*Bary; // Displaced vertex. First compute contribution of the original control points at the original vertices FVector3d NewPos = Bary[0]*BarySquared[0]*OriginalMesh.GetVertexRef(TriVertex.A) + Bary[1]*BarySquared[1]*OriginalMesh.GetVertexRef(TriVertex.B) + Bary[2]*BarySquared[2]*OriginalMesh.GetVertexRef(TriVertex.C); // Compute contribution of the control points at the edges for (int EIDX = 0; EIDX < 3; ++EIDX) { int EID = TriEdges[EIDX]; FIndex2i EdgeV = OriginalMesh.GetEdgeRef(EID).Vert; const FEdgeControlPoints& ControlPnts = FControlPoints.OnEdges[EID]; // Check that the orientation of the edge is consistent so we use the correct barycentric values int EdgeVtx1 = EdgeV[0]; int EdgeVtx2 = EdgeV[1]; IndexUtil::OrientTriEdgeAndFindOtherVtx(EdgeVtx1, EdgeVtx2, TriVertex); int BaryIdx1 = EdgeV[0] == EdgeVtx1 ? EIDX : (EIDX + 1) % 3; int BaryIdx2 = EdgeV[0] == EdgeVtx1 ? (EIDX + 1) % 3 : EIDX; NewPos += (3.0*BarySquared[BaryIdx1]*Bary[BaryIdx2])*ControlPnts.Point1 + (3.0*BarySquared[BaryIdx2]*Bary[BaryIdx1])*ControlPnts.Point2; } // Finally compute contribution of the single control point inside the triangle NewPos += (6.0*Bary[0]*Bary[1]*Bary[2])*FControlPoints.OnTriangles[OriginalTriangleID]; Mesh.SetVertex(VertexID, NewPos); if (bComputePNNormals) { if (bHasVertexNormals) { // Compute contribution of the normal control points at the original vertices FVector3d NewNormal(BarySquared[0]*OriginalMesh.GetVertexNormal(TriVertex.A) + BarySquared[1]*OriginalMesh.GetVertexNormal(TriVertex.B) + BarySquared[2]*OriginalMesh.GetVertexNormal(TriVertex.C)); // Compute contribution of the normal control points at the edges for (int EIDX = 0; EIDX < 3; ++EIDX) { int EID = TriEdges[EIDX]; const FEdgeControlPoints& ControlPnts = FControlPoints.OnEdges[EID]; NewNormal += Bary[EIDX]*Bary[(EIDX + 1) % 3]*ControlPnts.NormalTriA; } Normalize(NewNormal); Mesh.SetVertexNormal(VertexID, FVector3f(NewNormal)); } if (bHasAttributes) { //TODO: Compute per-element quadractic PN normals } } }); if (ProgressCancel && ProgressCancel->Cancelled()) { return false; } return true; } } bool FPNTriangles::Compute() { using namespace FPNTrianglesLocals; check(TessellationLevel >= 0); check(Mesh != nullptr); if (TessellationLevel < 0 || Mesh == nullptr) { return false; } if (TessellationLevel == 0) { return true; // nothing to do } // Compute per-vertex normals if no normals exist FMeshNormals Normals; bool bHasNormals = Mesh->HasVertexNormals() || (Mesh->HasAttributes() && Mesh->Attributes()->PrimaryNormals() != nullptr); if (bHasNormals == false) { Normals = FMeshNormals(Mesh); Normals.ComputeVertexNormals(); } FMeshNormals* UseNormals = (bHasNormals) ? nullptr : &Normals; // Compute PN triangle control points for each flat triangle of the original mesh FControlPoints FControlPoints; // TODO: We disable quadratically varying normal computation until we fully support seam normal overlay edges. // We might remove them completely since using quadratically varying normals would imply that we should be using // the same approach when computing normals inside the shaders for lighting computations // (see Tessellation on Any Budget, GDC, 2011). const bool bComputePNNormals = false; bool bOk = ComputeControlPoints(FControlPoints, *Mesh, UseNormals, bComputePNNormals, Progress); if (bOk == false) { return false; } // Tessellate the original mesh TArray NewVertices; FDynamicMesh3 ResultMesh; bOk = TessellateMesh(*Mesh, TessellationLevel, Progress, NewVertices, ResultMesh); if (bOk == false) { return false; } // Compute displacement and optionally quadratically varying normals bOk = DisplaceAndSetQuadraticNormals(*Mesh, FControlPoints, NewVertices, bComputePNNormals, Progress, ResultMesh); if (bOk == false) { return false; } if (bRecalculateNormals && bComputePNNormals == false) { if (ResultMesh.HasAttributes()) { FMeshNormals NewNormals(&ResultMesh); FDynamicMeshNormalOverlay* NormalOverlay = ResultMesh.Attributes()->PrimaryNormals(); NewNormals.RecomputeOverlayNormals(NormalOverlay); NewNormals.CopyToOverlay(NormalOverlay); } else if (ResultMesh.HasVertexNormals()) { FMeshNormals::QuickComputeVertexNormals(ResultMesh); } } Mesh->Copy(ResultMesh); return true; }