// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "AdaptiveTessellator.h" #include "HAL/Platform.h" #include "Misc/AssertionMacros.h" #include "IndexTypes.h" #include "LerpVert.h" #include "TriangleUtil.h" namespace Nanite { // Mesh topology implementation for FAdaptiveTesselation2. // // TriEdgeIndex is always in [0..2] and corresponds to enumerating triangle edges and vertices in order // The edge connects the vertices Tri[TriEdgeIndex], Tri[(TriEdgeIndex+1)%3]. // class FMinimalMesh { public: using FIndex3i = UE::Geometry::FIndex3i; using RealType = float; using VecType = FVector3f; enum class EEdgeSplitMode { AlwaysCrack, //< when splitting edges, always duplicate vertex CrackOnMaterialSeam, //< only split vertex if there is a material seam CrackFree //< never split, always average }; FMinimalMesh( TArray& InVerts, //< per-vertex information. original list will be preserved, but new entries will be added TArray& InIndexes, //< per triangle triplets of vertices pointing into InVerts. TArray& InMaterialIndexes, //< per-triangle material index EEdgeSplitMode InEdgeSplitMode, //< see enum bool bInTriangleSoup) //< if true, adjacency will be determined by vertex positions, otherwise based on indices (faster) : Verts(InVerts) , Indexes(InIndexes) , MaterialIndexes(InMaterialIndexes) , EdgeSplitMode(InEdgeSplitMode) , bTriangleSoup(bInTriangleSoup) { AdjEdges.Init( -1, HalfEdgeCount() ); // create the topology. directed edges will map to their opposite directed edge in opposite direction if (!bTriangleSoup) { struct FSortEdge { size_t EdgeIndex; int32 VtxA, VtxB; FORCEINLINE bool operator<(const FSortEdge& other) const { return VtxA < other.VtxA || (VtxA == other.VtxA && VtxB < other.VtxB); } }; TArray SortedHalfEdges; SortedHalfEdges.Reserve(AdjEdges.Num()); const size_t NumEdges = HalfEdgeCount(); for( size_t i = 0; i < NumEdges; ++i ) { const int32 V0 = Indexes[i]; const int32 V1 = Indexes[Cycle3(i)]; SortedHalfEdges.Emplace( i, V0 < V1 ? V0 : V1, V0 < V1 ? V1 : V0 ); } SortedHalfEdges.Sort(); for ( size_t i = 0; i < NumEdges-1; ++i) { if (SortedHalfEdges[i].VtxA == SortedHalfEdges[i+1].VtxA && SortedHalfEdges[i].VtxB == SortedHalfEdges[i+1].VtxB) { AdjEdges[SortedHalfEdges[i ].EdgeIndex] = SortedHalfEdges[i+1].EdgeIndex; AdjEdges[SortedHalfEdges[i+1].EdgeIndex] = SortedHalfEdges[i ].EdgeIndex; ++i; } } } else { FEdgeHash EdgeHash( HalfEdgeCount() ); for( int32 EdgeIndex = 0; EdgeIndex < HalfEdgeCount(); EdgeIndex++ ) { EdgeHash.ForAllMatching( EdgeIndex, true, [ this ]( int32 CornerIndex ) { return Verts[ Indexes[ CornerIndex ] ].Position; }, [&]( int32 EdgeIndex0, int32 EdgeIndex1 ) { if( AdjEdges[ EdgeIndex0 ] < 0 && AdjEdges[ EdgeIndex1 ] < 0 ) { AdjEdges[ EdgeIndex0 ] = EdgeIndex1; AdjEdges[ EdgeIndex1 ] = EdgeIndex0; } } ); } } } //////////////////////////////////////////////////////////////////////////////////////////// // Policy-interface implementation begin //////////////////////////////////////////////////////////////////////////////////////////// FORCEINLINE int32 GetVertexIndex(const int32 TriIndex, const int32 TriEdgeIndex) const { check(TriEdgeIndex < 3); check(TriIndex < MaxTriID()); return Indexes[TriIndex * 3 + TriEdgeIndex]; } FORCEINLINE FIndex3i GetTriangle(const int32 TriIndex) const { check(TriIndex < MaxTriID()); check(TriIndex >= 0); check(Indexes[TriIndex * 3 + 0] <= uint32(std::numeric_limits::max())); check(Indexes[TriIndex * 3 + 1] <= uint32(std::numeric_limits::max())); check(Indexes[TriIndex * 3 + 2] <= uint32(std::numeric_limits::max())); return { int32(Indexes[TriIndex * 3 + 0]), int32(Indexes[TriIndex * 3 + 1]), int32(Indexes[TriIndex * 3 + 2]) }; } FORCEINLINE FVector3f GetVertexPosition(const int32 TriIndex, const int32 TriEdgeIndex) const { return Verts[GetVertexIndex(TriIndex, TriEdgeIndex)].Position; } FORCEINLINE FVector3f GetVertexPosition(const int32 VertexIndex) const { return Verts[VertexIndex].Position; } FORCEINLINE void SetVertexPosition(const int32 VertexIndex, const FVector3f Position) const { Verts[VertexIndex].Position = Position; } FORCEINLINE void GetTriVertices(const int32 TriIndex, FVector3f& v0, FVector3f& v1, FVector3f& v2) const { v0 = Verts[GetVertexIndex(TriIndex, 0)].Position; v1 = Verts[GetVertexIndex(TriIndex, 1)].Position; v2 = Verts[GetVertexIndex(TriIndex, 2)].Position; } FORCEINLINE int32 MaxVertexID() const { return Verts.Num(); } FORCEINLINE bool IsValidVertex(const int32 VertexID) const { return true; } FORCEINLINE int32 MaxTriID() const { return Indexes.Num() / 3; } FORCEINLINE bool IsValidTri(const int32 TriIndex) const { return true; } // Return the triangle index on the other side of the half edge given by TriIndex, TriEdgeIndex or IndexConstants::InvalidID on boundary FORCEINLINE int32 GetAdjTriangle(const int32 TriIndex, const int32 TriEdgeIndex) const { const int32 AdjEdge = AdjEdges[TriIndex * 3 + TriEdgeIndex]; if (AdjEdge < 0) return IndexConstants::InvalidID; return AdjEdge / 3; } // Return the triangle index on the other side of the half edge given by TriIndex, TriEdgeIndex or IndexConstants::InvalidID on boundary FORCEINLINE TPair GetAdjEdge(const int32 TriIndex, const int32 TriEdgeIndex) const { const int32 AdjEdge = AdjEdges[TriIndex * 3 + TriEdgeIndex]; if (AdjEdge < 0) return { -1, -1 }; return { AdjEdge / 3, AdjEdge % 3 }; } FORCEINLINE bool EdgeManifoldCheck(const int32 TriIndex, const int32 TriEdgeIndex) const { const int32 EdgeIndex = TriIndex * 3 + TriEdgeIndex; const int32 AdjEdgeIndex = AdjEdges[EdgeIndex]; if (AdjEdgeIndex < 0) return true; // Manifoldness check if( Indexes[EdgeIndex] != Indexes[ Cycle3( AdjEdgeIndex ) ] || Indexes[ Cycle3( EdgeIndex ) ] != Indexes[ AdjEdgeIndex ] ) { return false; } return true; } FORCEINLINE bool AllowEdgeFlip(const int32 TriIndex, const int32 TriEdgeIndex, const int32 AdjTriIndex ) const { return (MaterialIndexes[TriIndex] == MaterialIndexes[AdjTriIndex]); } FORCEINLINE bool AllowEdgeSplit(const int32 TriIndex, const int32 TriEdgeIndex ) const { return true; } FORCEINLINE FVector3f GetTriangleNormal(const int32 TriIndex) const { const FVector3f& p0 = Verts[Indexes[TriIndex * 3 + 0]].Position; const FVector3f& p1 = Verts[Indexes[TriIndex * 3 + 1]].Position; const FVector3f& p2 = Verts[Indexes[TriIndex * 3 + 2]].Position; const FVector3f Edge01 = p1 - p0; const FVector3f Edge12 = p2 - p1; const FVector3f Edge20 = p0 - p2; return (Edge01 ^ Edge20).GetSafeNormal(); } // Split given triangle edge. The new position is SplitWeight * V0 + (1-SplitWeight) * V1. UE::Geometry::FEdgeSplitInfo SplitEdge(const int32 TriIndex, const int32 TriEdgeIndex, const float SplitWeight); // Introduce new vertex in triangle, connect to all three vertices and split into three new triangles. UE::Geometry::FPokeInfo PokeTriangle(const int32 TriIndex, const FVector3f Barycentrics); UE::Geometry::FFlipEdgeInfo FlipEdge(const int32 TriIndex, const int32 TriEdgeIndex); bool IsTriangleSoup() const { return bTriangleSoup; } //////////////////////////////////////////////////////////////////////////////////////////// // Policy-interface implementation end //////////////////////////////////////////////////////////////////////////////////////////// const FLerpVert& GetLerpVert(const size_t Idx) const { return Verts[Idx]; } private: // Added interpolation of corner vertices of triangle according to barycentric coordinates and return new vertex index. inline int32 AddInterpolatedVertex(const FVector3f Barycentrics, const int32 TriIndex) { ensure( FMath::Abs( Barycentrics.X + Barycentrics.Y + Barycentrics.Z - 1.0f ) < 1e-4f ); ensure( 0.0f <= Barycentrics.X && Barycentrics.X < 1.0f ); ensure( 0.0f <= Barycentrics.Y && Barycentrics.Y < 1.0f ); ensure( 0.0f <= Barycentrics.Z && Barycentrics.Z < 1.0f ); const FIndex3i Tri = GetTriangle(TriIndex); const FLerpVert& Vert0 = Verts[ Tri.A ]; const FLerpVert& Vert1 = Verts[ Tri.B ]; const FLerpVert& Vert2 = Verts[ Tri.C ]; FLerpVert NewVert; NewVert = Vert0 * Barycentrics.X; NewVert += Vert1 * Barycentrics.Y; NewVert += Vert2 * Barycentrics.Z; return Verts.Add( NewVert ); } // Added interpolation of corner vertices of edge according to barycentric coordinates and return new vertex index. inline int32 AddInterpolatedVertex(const FVector2f Barycentrics, int32 VertexIndex0, int32 VertexIndex1) { ensure(FMath::Abs(Barycentrics.X + Barycentrics.Y - 1.0f) < 1e-4f); ensure(0.0f <= Barycentrics.X && Barycentrics.X < 1.0f); ensure(0.0f <= Barycentrics.Y && Barycentrics.Y < 1.0f); const FLerpVert& Vert0 = Verts[VertexIndex0]; const FLerpVert& Vert1 = Verts[VertexIndex1]; FLerpVert NewVert; NewVert = Vert0 * Barycentrics.X; NewVert += Vert1 * Barycentrics.Y; return Verts.Add(NewVert); } FORCEINLINE int32 HalfEdgeCount() const { return Indexes.Num(); } void LinkEdge( int32 EdgeIndex0, int32 EdgeIndex1 ) { AdjEdges[ EdgeIndex0 ] = EdgeIndex1; if( EdgeIndex1 >= 0 ) { AdjEdges[ EdgeIndex1 ] = EdgeIndex0; check( Verts[ Indexes[ EdgeIndex0 ] ].Position == Verts[ Indexes[ Cycle3( EdgeIndex1 ) ] ].Position ); check( Verts[ Indexes[ EdgeIndex1 ] ].Position == Verts[ Indexes[ Cycle3( EdgeIndex0 ) ] ].Position ); } } private: friend class DisplacementPolicyFunctor; TArray& Verts; // per-vertex TArray& Indexes; // triangle as triplets TArray& MaterialIndexes; // per triangle material index TArray AdjEdges; // half-edge to neighboring half-edge (or -1 on boundary) const EEdgeSplitMode EdgeSplitMode; // how to we handle edge splits const bool bTriangleSoup; // if true, don't assume the vertex indices provide mesh connectivity }; inline UE::Geometry::FEdgeSplitInfo FMinimalMesh::SplitEdge(const int32 TriIndex, const int32 TriEdgeIndex, const float SplitWeight) { UE::Geometry::FEdgeSplitInfo SplitInfo; const FIndex3i Triangle = GetTriangle(TriIndex); int32 NewIndex = AddInterpolatedVertex(FVector2f(SplitWeight, 1.f - SplitWeight), GetVertexIndex(TriIndex, TriEdgeIndex), GetVertexIndex(TriIndex, Cycle3(TriEdgeIndex))); SplitInfo.NewVertIndex[0] = NewIndex; // Split edge int32 Edge[2]; Edge[0] = TriIndex * 3 + TriEdgeIndex; Edge[1] = AdjEdges[ Edge[0] ]; const int32 NumNewTris = Edge[1] < 0 ? 1 : 2; SplitInfo.OldTriIndex[0] = TriIndex; SplitInfo.OldTriIndex[1] = Edge[1] / 3; SplitInfo.NewTriIndex[0] = MaxTriID(); SplitInfo.NewTriIndex[1] = SplitInfo.NewTriIndex[0] + 1; if( Edge[1] < 0 ) { SplitInfo.OldTriIndex[1] = -1; SplitInfo.NewTriIndex[1] = -1; SplitInfo.NewVertIndex[1] = -1; } else { check( Verts[ Indexes[ Edge[0] ] ].Position == Verts[ Indexes[ Cycle3( Edge[1] ) ] ].Position ); check( Verts[ Indexes[ Edge[1] ] ].Position == Verts[ Indexes[ Cycle3( Edge[0] ) ] ].Position ); } /* v2 /|\ e1/ | \e2 / | \ v1/ 0|1 \v0 o====+====o v0\ 1|0 /v1 \ | / e2\ | /e1 \|/ v2 */ for( int32 j = 0; j < NumNewTris; j++ ) { const uint32 e0 = Edge[j]; const uint32 e1 = Cycle3( Edge[j] ); const uint32 e2 = Cycle3( Edge[j], 2 ); uint32 OldIndex0 = Indexes[ e0 ]; uint32 OldIndex1 = Indexes[ e1 ]; uint32 OldIndex2 = Indexes[ e2 ]; int32 OldAdjEdge1 = AdjEdges[ e1 ]; int32 OldAdjEdge2 = AdjEdges[ e2 ]; if( j == 1 ) { // only introduce seem when material indices are different. if (EdgeSplitMode == EEdgeSplitMode::CrackFree || (EdgeSplitMode == EEdgeSplitMode::CrackOnMaterialSeam && MaterialIndexes[SplitInfo.OldTriIndex[0]] == MaterialIndexes[SplitInfo.OldTriIndex[1]])) { SplitInfo.NewVertIndex[1] = SplitInfo.NewVertIndex[0]; } else { NewIndex = AddInterpolatedVertex(FVector2f(SplitWeight, 1.f - SplitWeight), OldIndex1, OldIndex0); SplitInfo.NewVertIndex[j] = NewIndex; } } Indexes.AddUninitialized(3); AdjEdges.AddUninitialized(3); MaterialIndexes.Add( CopyTemp( MaterialIndexes[ SplitInfo.OldTriIndex[j] ] ) ); // replace v0 uint32 i = SplitInfo.OldTriIndex[j] * 3; Indexes[ i + 0 ] = NewIndex; Indexes[ i + 1 ] = OldIndex1; Indexes[ i + 2 ] = OldIndex2; AdjEdges[ i + 0 ] = SplitInfo.NewTriIndex[j^1] * 3; LinkEdge( i + 1, OldAdjEdge1 ); AdjEdges[ i + 2 ] = SplitInfo.NewTriIndex[j] * 3 + 1; // replace v1 i = SplitInfo.NewTriIndex[j] * 3; Indexes[ i + 0 ] = OldIndex0; Indexes[ i + 1 ] = NewIndex; Indexes[ i + 2 ] = OldIndex2; AdjEdges[ i + 0 ] = SplitInfo.OldTriIndex[j^1] * 3; AdjEdges[ i + 1 ] = SplitInfo.OldTriIndex[j] * 3 + 2; LinkEdge( i + 2, OldAdjEdge2 ); } return SplitInfo; } inline UE::Geometry::FPokeInfo FMinimalMesh::PokeTriangle(const int32 TriIndex, const FVector3f Barycentrics) { UE::Geometry::FPokeInfo PokeInfo; const FIndex3i Tri = GetTriangle(TriIndex); const int32 NewIndex = AddInterpolatedVertex(Barycentrics, TriIndex); ensure( FMath::Abs( Barycentrics.X + Barycentrics.Y + Barycentrics.Z - 1.0f ) < 1e-4f ); ensure( 0.0f <= Barycentrics.X && Barycentrics.X < 1.0f ); ensure( 0.0f <= Barycentrics.Y && Barycentrics.Y < 1.0f ); ensure( 0.0f <= Barycentrics.Z && Barycentrics.Z < 1.0f ); const FLerpVert& Vert0 = Verts[ Tri.A ]; const FLerpVert& Vert1 = Verts[ Tri.B ]; const FLerpVert& Vert2 = Verts[ Tri.C ]; FLerpVert NewVert; NewVert = Vert0 * Barycentrics.X; NewVert += Vert1 * Barycentrics.Y; NewVert += Vert2 * Barycentrics.Z; int32 OldAdjEdges[3]; OldAdjEdges[0] = AdjEdges[ TriIndex * 3 + 0 ]; OldAdjEdges[1] = AdjEdges[ TriIndex * 3 + 1 ]; OldAdjEdges[2] = AdjEdges[ TriIndex * 3 + 2 ]; PokeInfo.NewTriIndex[0] = TriIndex; PokeInfo.NewTriIndex[1] = MaxTriID(); PokeInfo.NewTriIndex[2] = PokeInfo.NewTriIndex[1] + 1; // add 2 triangles Indexes.AddUninitialized(6); AdjEdges.AddUninitialized(6); MaterialIndexes.AddUninitialized(2); for( uint32 EdgeIndex = 0; EdgeIndex < 3; EdgeIndex++ ) { const uint32 e0 = EdgeIndex; const uint32 e1 = (1 << e0) & 3; const uint32 e2 = (1 << e1) & 3; uint32 i = PokeInfo.NewTriIndex[ EdgeIndex ] * 3; Indexes[ i + 0 ] = Tri[ e0 ]; Indexes[ i + 1 ] = Tri[ e1 ]; Indexes[ i + 2 ] = NewIndex; LinkEdge( i + 0, OldAdjEdges[ e0 ] ); AdjEdges[ i + 1 ] = PokeInfo.NewTriIndex[ e1 ] * 3 + 2; AdjEdges[ i + 2 ] = PokeInfo.NewTriIndex[ e2 ] * 3 + 1; MaterialIndexes[ PokeInfo.NewTriIndex[ EdgeIndex ] ] = MaterialIndexes[ TriIndex ]; } PokeInfo.EdgeIndex[0] = PokeInfo.EdgeIndex[1] = PokeInfo.EdgeIndex[2] = 0; PokeInfo.NewVertIndex = NewIndex; return PokeInfo; } inline UE::Geometry::FFlipEdgeInfo FMinimalMesh::FlipEdge(const int32 TriIndex, const int32 TriEdgeIndex) { const int32 EdgeIndex = TriIndex * 3 + TriEdgeIndex; const int32 AdjEdge = AdjEdges[ EdgeIndex ]; check(AdjEdge >= 0); const int32 AdjTriIndex = AdjEdge / 3; /* v2 v0,v1 vA /\ /|\ /\ e1/ \e2 e2/ | \e1 eD/ \eA / \ / | \ / \ v1/ e0 \v0 / | \ / \ o========o => v2 e0|e0 v2 vD vB v0\ e0 /v1 \ | / \ / \ / \ | / \ / e2\ /e1 e1\ | /e2 eC\ /eB \/ \|/ \/ v2 v1'v0 vC */ const int32 eA = TriIndex * 3 + ( EdgeIndex + 2 ) % 3; const int32 eB = AdjTriIndex * 3 + ( AdjEdge + 1 ) % 3; const int32 eC = AdjTriIndex * 3 + ( AdjEdge + 2 ) % 3; const int32 eD = TriIndex * 3 + ( EdgeIndex + 1 ) % 3; int32 IndexA = Indexes[ eA ]; int32 IndexB = Indexes[ eB ]; int32 IndexC = Indexes[ eC ]; int32 IndexD = Indexes[ eD ]; int32 AdjEdgeA = AdjEdges[ eA ]; int32 AdjEdgeB = AdjEdges[ eB ]; int32 AdjEdgeC = AdjEdges[ eC ]; int32 AdjEdgeD = AdjEdges[ eD ]; #ifndef DM3_NANITE_COMPATIBILITY // original nanite code Indexes[ TriIndex * 3 + 0 ] = IndexC; Indexes[ TriIndex * 3 + 1 ] = IndexA; Indexes[ TriIndex * 3 + 2 ] = IndexB; Indexes[ AdjTriIndex * 3 + 0 ] = IndexA; Indexes[ AdjTriIndex * 3 + 1 ] = IndexC; Indexes[ AdjTriIndex * 3 + 2 ] = IndexD; LinkEdge( TriIndex * 3, AdjTriIndex * 3 ); LinkEdge( TriIndex * 3 + 1, AdjEdgeA ); LinkEdge( TriIndex * 3 + 2, AdjEdgeB ); LinkEdge( AdjTriIndex * 3 + 1, AdjEdgeC ); LinkEdge( AdjTriIndex * 3 + 2, AdjEdgeD ); return { { TriIndex, AdjTriIndex }, { 0, 0 } }; #else // dynamic mesh-like Indexes[ AdjTriIndex * 3 + 0 ] = IndexC; Indexes[ AdjTriIndex * 3 + 1 ] = IndexA; Indexes[ AdjTriIndex * 3 + 2 ] = IndexB; Indexes[ TriIndex * 3 + 0 ] = IndexA; Indexes[ TriIndex * 3 + 1 ] = IndexC; Indexes[ TriIndex * 3 + 2 ] = IndexD; LinkEdge( AdjTriIndex * 3, TriIndex * 3 ); LinkEdge( TriIndex * 3 + 1, AdjEdgeC ); LinkEdge( TriIndex * 3 + 2, AdjEdgeD ); LinkEdge( AdjTriIndex * 3 + 1, AdjEdgeA ); LinkEdge( AdjTriIndex * 3 + 2, AdjEdgeB ); return { { TriIndex, AdjTriIndex }, { 0, 0 } }; #endif } } // namespace Nanite