// Copyright Epic Games, Inc. All Rights Reserved. #include "Selections/QuadGridPatch.h" #include "DynamicMesh/DynamicMesh3.h" #include "Algo/Reverse.h" using namespace UE::Geometry; namespace UELocal { static bool FindTriEdgeIndices( const FIndex3i& Triangle, int32 EdgeVert0, int32 EdgeVert1, int32& EdgeIndex0, int32& EdgeIndex1, int32& OtherIndex2) { for (int32 j = 0; j < 3; ++j) { if (Triangle[j] == EdgeVert0) { if (Triangle[(j + 1) % 3] == EdgeVert1) { EdgeIndex0 = j; EdgeIndex1 = (j + 1) % 3; OtherIndex2 = (j + 2) % 3; return true; } else if (Triangle[(j + 2) % 3] == EdgeVert1) { EdgeIndex0 = j; EdgeIndex1 = (j + 2) % 3; OtherIndex2 = (j + 1) % 3; return true; } } } return false; } } void FQuadGridPatch::InitializeFromQuadStrip(const FDynamicMesh3& Mesh, const TArray& SequentialQuadsIn, const TArray& FirstVertexSpan ) { int32 NumV = FirstVertexSpan.Num(); int32 NumQ = SequentialQuadsIn.Num(); check(NumQ == NumV-1); NumVertexColsU = NumV; NumVertexRowsV = 2; VertexSpans.SetNum(2); VertexSpans[0] = FirstVertexSpan; VertexSpans[1].Reserve(NumV); QuadTriangles.SetNum(1); QuadTriangles[0] = SequentialQuadsIn; for ( int32 k = 0; k < NumQ; ++k ) { int Vertex0 = FirstVertexSpan[k]; int Vertex1 = FirstVertexSpan[k+1]; FIndex2i& QuadTris = QuadTriangles[0][k]; FIndex3i TriA = Mesh.GetTriangle(QuadTris.A); FIndex3i TriB = Mesh.GetTriangle(QuadTris.B); // we want TriA to be the one containing the edge (Vertex0,Vertex1), so if it's TriB, swap them, and the quad if ( TriB.Contains(Vertex0) && TriB.Contains(Vertex1) ) { Swap(TriA, TriB); Swap(QuadTris.A, QuadTris.B); } // at this point we are not sure if OtherVertex matches with Vertex0 or Vertex1 int OtherVertex = IndexUtil::FindTriOtherVtx(Vertex0, Vertex1, TriA); bool bIsCase1 = (TriB.Contains(Vertex0) == false); int Other0 = (bIsCase1) ? OtherVertex : IndexUtil::FindTriOtherVtx(Vertex0, OtherVertex, TriB); int Other1 = (bIsCase1) ? IndexUtil::FindTriOtherVtx(Vertex1, OtherVertex, TriB) : OtherVertex; if (k == 0) { VertexSpans[1].Add(Other0); } checkSlow(VertexSpans[1].Last() == Other0); VertexSpans[1].Add(Other1); } } bool FQuadGridPatch::InitializeFromQuadPatch(const FDynamicMesh3& Mesh, const TArray>& QuadRowsIn, const TArray>& VertexSpansIn) { int32 NumV = VertexSpansIn[0].Num(); int32 NumQ = QuadRowsIn[0].Num(); if (NumQ != NumV - 1 || QuadRowsIn.Num() != VertexSpansIn.Num()-1 ) { ensure(false); return false; } NumVertexColsU = NumV; NumVertexRowsV = VertexSpansIn.Num(); for (int32 j = 1; j < NumVertexRowsV; ++j) { if (VertexSpansIn[j].Num() != VertexSpansIn[0].Num()) { ensure(false); return false; } } int32 NumQuadRows = QuadRowsIn.Num(); for (int32 j = 1; j < NumQuadRows; ++j) { if (QuadRowsIn[j].Num() != QuadRowsIn[0].Num()) { ensure(false); return false; } } VertexSpans = VertexSpansIn; QuadTriangles = QuadRowsIn; bool bAllOK = true; for (int32 j = 0; j < NumQuadRows && bAllOK; ++j) { for (int32 k = 0; k < NumQ && bAllOK; ++k) { // these should be the four vertices of the quad int VertexA = VertexSpans[j][k]; int VertexB = VertexSpans[j][k+1]; int VertexC = VertexSpans[j+1][k+1]; int VertexD = VertexSpans[j+1][k]; // figure out unique vertices from the quad info, and verify that there are 4 and they are A/B/C/D FIndex2i& QuadTris = QuadTriangles[j][k]; if (Mesh.IsTriangle(QuadTris.A) == false || Mesh.IsTriangle(QuadTris.B) == false) { bAllOK = false; break; } FIndex3i TriA = Mesh.GetTriangle(QuadTris.A); FIndex3i TriB = Mesh.GetTriangle(QuadTris.B); TArray> TriVerts({ TriA.A, TriA.B, TriA.C }); TriVerts.AddUnique(TriB.A); TriVerts.AddUnique(TriB.B); TriVerts.AddUnique(TriB.C); if (TriVerts.Num() != 4) { bAllOK = false; break; } if (TriVerts.Contains(VertexA) == false || TriVerts.Contains(VertexB) == false || TriVerts.Contains(VertexC) == false || TriVerts.Contains(VertexD) == false) { bAllOK = false; break; } // we want TriA to be the one containing the edge (Vertex0,Vertex1), so if it's TriB, swap them, and the quad if (TriB.Contains(VertexA) && TriB.Contains(VertexB)) { Swap(TriA, TriB); Swap(QuadTris.A, QuadTris.B); } } } if (!bAllOK) { ensure(false); VertexSpans.Reset(); QuadTriangles.Reset(); NumVertexColsU = NumVertexRowsV = 0; return false; } return true; } void FQuadGridPatch::ReverseRows() { Algo::Reverse(QuadTriangles); Algo::Reverse(VertexSpans); } bool FQuadGridPatch::AppendQuadPatchRows(FQuadGridPatch&& NextPatch, bool bChecked) { if ( NextPatch.NumVertexColsU != NumVertexColsU ) return false; if ( bChecked ) { const TArray& LastSpan = VertexSpans.Last(); const TArray& FirstSpan = NextPatch.VertexSpans[0]; for ( int32 k = 0; k < NumVertexColsU; ++k ) { if (LastSpan[k] != FirstSpan[k]) { check(false); return false; } } } int AddedV = NextPatch.NumVertexRowsV-1; for ( int32 k = 1; k < NextPatch.NumVertexRowsV; ++k ) { VertexSpans.Add( MoveTemp(NextPatch.VertexSpans[k]) ); } for ( int32 k = 0; k < NextPatch.NumVertexRowsV-1; ++k ) { QuadTriangles.Add( MoveTemp(NextPatch.QuadTriangles[k]) ); } NumVertexRowsV = VertexSpans.Num(); return true; } void FQuadGridPatch::GetAllTriangles(TArray& AllTrianglesOut) const { AllTrianglesOut.Reserve(AllTrianglesOut.Num() + 2*GetNumQuads()); for ( const TArray& QuadStrip : QuadTriangles ) { for (FIndex2i Quad : QuadStrip) { AllTrianglesOut.Add(Quad.A); AllTrianglesOut.Add(Quad.B); } } } FIndex3i FQuadGridPatch::GetQuadTriMappedToSpanIndices(const FDynamicMesh3& Mesh, int QuadRow, int QuadIndex, int TriIndex) const { check(TriIndex == 0 || TriIndex == 1); check(QuadRow >= 0 && QuadRow < (NumVertexRowsV-1) ); check(QuadIndex >= 0 && QuadIndex < (NumVertexColsU-1) ); FIndex3i Tri = Mesh.GetTriangle( QuadTriangles[QuadRow][QuadIndex][TriIndex] ); // C - D // | | // A - B FIndex4i QuadVertices( VertexSpans[QuadRow][QuadIndex], VertexSpans[QuadRow][QuadIndex+1], VertexSpans[QuadRow+1][QuadIndex], VertexSpans[QuadRow+1][QuadIndex+1] ); FIndex4i QuadIndices( EncodeRowColumnIndex(QuadRow, QuadIndex), EncodeRowColumnIndex(QuadRow,QuadIndex+1), EncodeRowColumnIndex(QuadRow+1, QuadIndex), EncodeRowColumnIndex(QuadRow+1,QuadIndex+1)); FIndex3i TriOrder, EncodedIndexTri = FIndex3i::Invalid(); if ( UELocal::FindTriEdgeIndices(Tri, QuadVertices.A, QuadVertices.D, TriOrder.A, TriOrder.B, TriOrder.C) ) { // edge A/D exists case EncodedIndexTri[TriOrder.A] = QuadIndices.A; EncodedIndexTri[TriOrder.B] = QuadIndices.D; EncodedIndexTri[TriOrder.C] = (Tri[TriOrder.C] == QuadVertices.C) ? QuadIndices.C : QuadIndices.B; } else if ( UELocal::FindTriEdgeIndices(Tri, QuadVertices.B, QuadVertices.C, TriOrder.A, TriOrder.B, TriOrder.C) ) { // edge B/C exists case EncodedIndexTri[TriOrder.A] = QuadIndices.B; EncodedIndexTri[TriOrder.B] = QuadIndices.C; EncodedIndexTri[TriOrder.C] = (Tri[TriOrder.C] == QuadVertices.D) ? QuadIndices.D : QuadIndices.A; } else { check(false); // ruh roh! it should have been one of those two other cases... } return EncodedIndexTri; } bool FQuadGridPatch::GetVertexColumn(int32 ColumnIndex, TArray& VerticesOut) const { if (ColumnIndex < 0 || ColumnIndex >= VertexSpans[0].Num()) { ensure(false); return false; } VerticesOut.Reset(); for (int32 k = 0; k < VertexSpans.Num(); ++k) { VerticesOut.Add(VertexSpans[k][ColumnIndex]); } return true; } int32 FQuadGridPatch::FindColumnIndex(int32 VertexID) const { for (const TArray& Span : VertexSpans) { int32 Index = Span.IndexOfByKey(VertexID); if (Index != INDEX_NONE) { return Index; } } return IndexConstants::InvalidID; } void FQuadGridPatch::GetSubPatchByQuadRange(int StartRow, int EndRow, int StartCol, int EndCol, FQuadGridPatch& PatchOut ) const { int NumQuadRows = (EndRow-StartRow)+1; int NumQuadCols = (EndCol-StartCol)+1; PatchOut.NumVertexRowsV = NumQuadRows+1; PatchOut.NumVertexColsU = NumQuadCols+1; PatchOut.VertexSpans.SetNum(PatchOut.NumVertexRowsV); PatchOut.QuadTriangles.SetNum(PatchOut.NumVertexRowsV-1); for ( int32 ri = 0; ri < NumQuadRows; ++ri ) { int32 row = StartRow + ri; for ( int32 ci = 0; ci < NumQuadCols; ++ci ) { int col = StartCol + ci; PatchOut.VertexSpans[ri].Add( VertexSpans[row][col] ); PatchOut.QuadTriangles[ri].Add( QuadTriangles[row][col] ); } PatchOut.VertexSpans[ri].Add( VertexSpans[row][EndCol+1] ); } // last row for ( int32 ci = 0; ci <= NumQuadCols; ++ci ) { int col = StartCol + ci; PatchOut.VertexSpans[NumQuadRows].Add( VertexSpans[EndRow+1][col] ); } } void FQuadGridPatch::SplitColumnsByPredicate( TFunctionRef PredicateFunc, TArray& SplitPatchesOut) const { int32 NumQuadCols = NumVertexColsU-1; int32 CurIndex = 0; int32 SpanStartIndex = 0; while ( CurIndex < NumQuadCols-1 ) { bool bShouldSplit = PredicateFunc(CurIndex, CurIndex+1); if ( bShouldSplit ) { FQuadGridPatch SubPatch; GetSubPatchByQuadRange(0, NumVertexRowsV-2, SpanStartIndex, CurIndex, SubPatch ); SplitPatchesOut.Add(MoveTemp(SubPatch)); CurIndex++; SpanStartIndex = CurIndex; } else { CurIndex++; } } // will we always have one final patch? FQuadGridPatch SubPatch; GetSubPatchByQuadRange(0, NumVertexRowsV-2, SpanStartIndex, CurIndex, SubPatch ); SplitPatchesOut.Add(MoveTemp(SubPatch)); } double FQuadGridPatch::GetQuadOpeningAngleDeg(const FDynamicMesh3& Mesh, int32 Column0, int32 Column1, int32 Row) const { check(Column1 == (Column0+1)); int32 Vertex0 = VertexSpans[Row][Column0]; int32 Vertex1 = VertexSpans[Row+1][Column0]; int32 EdgeID = Mesh.FindEdge(Vertex0, Vertex1); check(EdgeID != IndexConstants::InvalidID); FIndex2i EdgeTris = Mesh.GetEdgeT(EdgeID); check(EdgeTris.B != IndexConstants::InvalidID); FVector3d Normal0 = Mesh.GetTriNormal(EdgeTris.A); FVector3d Normal1 = Mesh.GetTriNormal(EdgeTris.B); return AngleD(Normal0, Normal1); }