// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "DynamicMeshEditor.h" #include "DynamicMeshAttributeSet.h" #include "Util/BufferUtil.h" #include "MeshRegionBoundaryLoops.h" void FMeshIndexMappings::Initialize(FDynamicMesh3* Mesh) { if (Mesh->HasAttributes()) { FDynamicMeshAttributeSet* Attribs = Mesh->Attributes(); UVMaps.SetNum(Attribs->NumUVLayers()); NormalMaps.SetNum(Attribs->NumNormalLayers()); } } void FDynamicMeshEditResult::GetAllTriangles(TArray& TrianglesOut) const { BufferUtil::AppendElements(TrianglesOut, NewTriangles); int NumQuads = NewQuads.Num(); for (int k = 0; k < NumQuads; ++k) { TrianglesOut.Add(NewQuads[k].A); TrianglesOut.Add(NewQuads[k].B); } int NumPolys = NewPolygons.Num(); for (int k = 0; k < NumPolys; ++k) { BufferUtil::AppendElements(TrianglesOut, NewPolygons[k]); } } bool FDynamicMeshEditor::StitchVertexLoopsMinimal(const TArray& Loop1, const TArray& Loop2, FDynamicMeshEditResult& ResultOut) { int N = Loop1.Num(); checkf(N == Loop2.Num(), TEXT("FDynamicMeshEditor::StitchLoop: loops are not the same length!")); if (N != Loop2.Num()) { return false; } ResultOut.NewQuads.Reserve(N); ResultOut.NewGroups.Reserve(N); int i = 0; for (; i < N; ++i) { int a = Loop1[i]; int b = Loop1[(i + 1) % N]; int c = Loop2[i]; int d = Loop2[(i + 1) % N]; int NewGroupID = Mesh->AllocateTriangleGroup(); ResultOut.NewGroups.Add(NewGroupID); FIndex3i t1(b, a, d); int tid1 = Mesh->AppendTriangle(t1, NewGroupID); FIndex3i t2(a, c, d); int tid2 = Mesh->AppendTriangle(t2, NewGroupID); ResultOut.NewQuads.Add(FIndex2i(tid1, tid2)); if (tid1 < 0 || tid2 < 0) { goto operation_failed; } } return true; operation_failed: // remove what we added so far if (ResultOut.NewQuads.Num()) { TArray Triangles; Triangles.Reserve(2*ResultOut.NewQuads.Num()); for (const FIndex2i& QuadTriIndices : ResultOut.NewQuads) { Triangles.Add(QuadTriIndices.A); Triangles.Add(QuadTriIndices.B); } if (!RemoveTriangles(Triangles, false)) { checkf(false, TEXT("FDynamicMeshEditor::StitchLoop: failed to add all triangles, and also failed to back out changes.")); } } return false; } bool FDynamicMeshEditor::StitchSparselyCorrespondedVertexLoops(const TArray& VertexIDs1, const TArray& MatchedIndices1, const TArray& VertexIDs2, const TArray& MatchedIndices2, FDynamicMeshEditResult& ResultOut) { int CorrespondN = MatchedIndices1.Num(); if (!ensureMsgf(CorrespondN == MatchedIndices2.Num(), TEXT("FDynamicMeshEditor::StitchSparselyCorrespondedVertices: correspondence arrays are not the same length!"))) { return false; } // TODO: support case of only one corresponded vertex & a connecting a full loop around? // this requires allowing start==end to not immediately stop the walk ... if (!ensureMsgf(CorrespondN >= 2, TEXT("Must have at least two corresponded vertices"))) { return false; } ResultOut.NewGroups.Reserve(CorrespondN); int i = 0; for (; i < CorrespondN; ++i) { int Starts[2] { MatchedIndices1[i], MatchedIndices2[i] }; int Ends[2] { MatchedIndices1[(i + 1) % CorrespondN], MatchedIndices2[(i + 1) % CorrespondN] }; auto GetWrappedSpanLen = [](const FDynamicMesh3* M, const TArray& VertexIDs, int StartInd, int EndInd) { float LenTotal = 0; FVector3d V = M->GetVertex(VertexIDs[StartInd]); for (int Ind = StartInd, IndNext; Ind != EndInd;) { IndNext = (Ind + 1) % VertexIDs.Num(); FVector3d VNext = M->GetVertex(VertexIDs[IndNext]); LenTotal += V.Distance(VNext); Ind = IndNext; V = VNext; } return LenTotal; }; float LenTotal[2] { GetWrappedSpanLen(Mesh, VertexIDs1, Starts[0], Ends[0]), GetWrappedSpanLen(Mesh, VertexIDs2, Starts[1], Ends[1]) }; float LenAlong[2] { FMathf::Epsilon, FMathf::Epsilon }; LenTotal[0] += FMathf::Epsilon; LenTotal[1] += FMathf::Epsilon; int NewGroupID = Mesh->AllocateTriangleGroup(); ResultOut.NewGroups.Add(NewGroupID); int Walks[2]{ Starts[0], Starts[1] }; FVector3d Vertex[2]{ Mesh->GetVertex(VertexIDs1[Starts[0]]), Mesh->GetVertex(VertexIDs2[Starts[1]]) }; while (Walks[0] != Ends[0] || Walks[1] != Ends[1]) { float PctAlong[2]{ LenAlong[0] / LenTotal[0], LenAlong[1] / LenTotal[1] }; bool bAdvanceSecond = (Walks[0] == Ends[0] || (Walks[1] != Ends[1] && PctAlong[0] > PctAlong[1])); FIndex3i Tri(VertexIDs1[Walks[0]], VertexIDs2[Walks[1]], -1); if (!bAdvanceSecond) { Walks[0] = (Walks[0] + 1) % VertexIDs1.Num(); Tri.C = VertexIDs1[Walks[0]]; FVector3d NextV = Mesh->GetVertex(Tri.C); LenAlong[0] += NextV.Distance(Vertex[0]); Vertex[0] = NextV; } else { Walks[1] = (Walks[1] + 1) % VertexIDs2.Num(); Tri.C = VertexIDs2[Walks[1]]; FVector3d NextV = Mesh->GetVertex(Tri.C); LenAlong[1] += NextV.Distance(Vertex[1]); Vertex[1] = NextV; } int Tid = Mesh->AppendTriangle(Tri, NewGroupID); ResultOut.NewTriangles.Add(Tid); if (Tid < 0) { goto operation_failed; } } } return true; operation_failed: // remove what we added so far if (ResultOut.NewTriangles.Num()) { ensureMsgf(RemoveTriangles(ResultOut.NewTriangles, false), TEXT("FDynamicMeshEditor::StitchSparselyCorrespondedVertexLoops: failed to add all triangles, and also failed to back out changes.")); } return false; } bool FDynamicMeshEditor::AddTriangleFan_OrderedVertexLoop(int CenterVertex, const TArray& VertexLoop, int GroupID, FDynamicMeshEditResult& ResultOut) { if (GroupID == -1) { GroupID = Mesh->AllocateTriangleGroup(); ResultOut.NewGroups.Add(GroupID); } int N = VertexLoop.Num(); ResultOut.NewTriangles.Reserve(N); int i = 0; for (i = 0; i < N; ++i) { int A = VertexLoop[i]; int B = VertexLoop[(i + 1) % N]; FIndex3i NewT(CenterVertex, B, A); int NewTID = Mesh->AppendTriangle(NewT, GroupID); if (NewTID < 0) { goto operation_failed; } ResultOut.NewTriangles.Add(NewTID); } return true; operation_failed: // remove what we added so far if (!RemoveTriangles(ResultOut.NewTriangles, false)) { checkf(false, TEXT("FDynamicMeshEditor::AddTriangleFan: failed to add all triangles, and also failed to back out changes.")); } return false; } bool FDynamicMeshEditor::RemoveTriangles(const TArray& Triangles, bool bRemoveIsolatedVerts) { return RemoveTriangles(Triangles, bRemoveIsolatedVerts, [](int) {}); } bool FDynamicMeshEditor::RemoveTriangles(const TArray& Triangles, bool bRemoveIsolatedVerts, TFunctionRef OnRemoveTriFunc) { bool bAllOK = true; int NumTriangles = Triangles.Num(); for (int i = 0; i < NumTriangles; ++i) { if (Mesh->IsTriangle(Triangles[i]) == false) { continue; } OnRemoveTriFunc(Triangles[i]); EMeshResult result = Mesh->RemoveTriangle(Triangles[i], bRemoveIsolatedVerts, false); if (result != EMeshResult::Ok) { bAllOK = false; } } return bAllOK; } /** * Make a copy of provided triangles, with new vertices. You provide IndexMaps because * you know if you are doing a small subset or a full-mesh-copy. */ void FDynamicMeshEditor::DuplicateTriangles(const TArray& Triangles, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut) { ResultOut.Reset(); IndexMaps.Initialize(Mesh); for (int TriangleID : Triangles) { FIndex3i Tri = Mesh->GetTriangle(TriangleID); int NewGroupID = FindOrCreateDuplicateGroup(TriangleID, IndexMaps, ResultOut); FIndex3i NewTri; NewTri[0] = FindOrCreateDuplicateVertex(Tri[0], IndexMaps, ResultOut); NewTri[1] = FindOrCreateDuplicateVertex(Tri[1], IndexMaps, ResultOut); NewTri[2] = FindOrCreateDuplicateVertex(Tri[2], IndexMaps, ResultOut); int NewTriangleID = Mesh->AppendTriangle(NewTri, NewGroupID); IndexMaps.SetTriangle(TriangleID, NewTriangleID); ResultOut.NewTriangles.Add(NewTriangleID); CopyAttributes(TriangleID, NewTriangleID, IndexMaps, ResultOut); //Mesh->CheckValidity(true); } } bool FDynamicMeshEditor::DisconnectTriangles(const TArray& Triangles, TArray& LoopSetOut) { check(Mesh->HasAttributes() == false); // not supported yet // find the region boundary loops FMeshRegionBoundaryLoops RegionLoops(Mesh, Triangles, false); bool bOK = RegionLoops.Compute(); check(bOK); if (!bOK) { return false; } TArray& Loops = RegionLoops.Loops; // need to test Contains() many times TSet TriangleSet; TriangleSet.Reserve(Triangles.Num() * 3); for (int TriID : Triangles) { TriangleSet.Add(TriID); } // process each loop island int NumLoops = Loops.Num(); LoopSetOut.SetNum(NumLoops); for ( int li = 0; li < NumLoops; ++li) { FEdgeLoop& Loop = Loops[li]; FLoopPairSet& LoopPair = LoopSetOut[li]; LoopPair.LoopA = Loop; // duplicate the vertices int NumVertices = Loop.Vertices.Num(); TMap LoopVertexMap; LoopVertexMap.Reserve(NumVertices); TArray NewVertexLoop; NewVertexLoop.SetNum(NumVertices); for (int vi = 0; vi < NumVertices; ++vi) { int VertID = Loop.Vertices[vi]; int NewVertID = Mesh->AppendVertex(*Mesh, VertID); LoopVertexMap.Add(Loop.Vertices[vi], NewVertID); NewVertexLoop[vi] = NewVertID; } // for each border triangle, rewrite vertices int NumEdges = Loop.Edges.Num(); for (int ei = 0; ei < NumEdges; ++ei) { int EdgeID = Loop.Edges[ei]; FIndex2i EdgeTris = Mesh->GetEdgeT(EdgeID); int EditTID = TriangleSet.Contains(EdgeTris.A) ? EdgeTris.A : EdgeTris.B; if (EditTID == FDynamicMesh3::InvalidID) { continue; // happens on final edge, and on input boundary edges } FIndex3i OldTri = Mesh->GetTriangle(EditTID); FIndex3i NewTri = OldTri; int Modified = 0; for (int j = 0; j < 3; ++j) { const int* NewVertID = LoopVertexMap.Find(OldTri[j]); if (NewVertID != nullptr) { NewTri[j] = *NewVertID; ++Modified; } } if (Modified > 0) { Mesh->SetTriangle(EditTID, NewTri, false); } } LoopPair.LoopB.InitializeFromVertices(Mesh, NewVertexLoop, false); } return true; } FVector3f FDynamicMeshEditor::ComputeAndSetQuadNormal(const FIndex2i& QuadTris, bool bIsPlanar) { FVector3f Normal(0, 0, 1); if (bIsPlanar) { Normal = (FVector3f)Mesh->GetTriNormal(QuadTris.A); } else { Normal = (FVector3f)Mesh->GetTriNormal(QuadTris.A); Normal += (FVector3f)Mesh->GetTriNormal(QuadTris.B); Normal.Normalize(); } SetQuadNormals(QuadTris, Normal); return Normal; } void FDynamicMeshEditor::SetQuadNormals(const FIndex2i& QuadTris, const FVector3f& Normal) { check(Mesh->HasAttributes()); FDynamicMeshNormalOverlay* Normals = Mesh->Attributes()->PrimaryNormals(); FIndex3i Triangle1 = Mesh->GetTriangle(QuadTris.A); FIndex3i NormalTriangle1; NormalTriangle1[0] = Normals->AppendElement(Normal, Triangle1[0]); NormalTriangle1[1] = Normals->AppendElement(Normal, Triangle1[1]); NormalTriangle1[2] = Normals->AppendElement(Normal, Triangle1[2]); Normals->SetTriangle(QuadTris.A, NormalTriangle1); if (Mesh->IsTriangle(QuadTris.B)) { FIndex3i Triangle2 = Mesh->GetTriangle(QuadTris.B); FIndex3i NormalTriangle2; for (int j = 0; j < 3; ++j) { int i = Triangle1.IndexOf(Triangle2[j]); if (i == -1) { NormalTriangle2[j] = Normals->AppendElement(Normal, Triangle2[j]); } else { NormalTriangle2[j] = NormalTriangle1[i]; } } Normals->SetTriangle(QuadTris.B, NormalTriangle2); } } void FDynamicMeshEditor::SetTriangleNormals(const TArray& Triangles, const FVector3f& Normal) { check(Mesh->HasAttributes()); FDynamicMeshNormalOverlay* Normals = Mesh->Attributes()->PrimaryNormals(); TMap Vertices; for (int tid : Triangles) { FIndex3i BaseTri = Mesh->GetTriangle(tid); FIndex3i ElemTri; for (int j = 0; j < 3; ++j) { const int* FoundElementID = Vertices.Find(BaseTri[j]); if (FoundElementID == nullptr) { ElemTri[j] = Normals->AppendElement(Normal, BaseTri[j]); Vertices.Add(BaseTri[j], ElemTri[j]); } else { ElemTri[j] = *FoundElementID; } } Normals->SetTriangle(tid, ElemTri); } } void FDynamicMeshEditor::SetTriangleUVsFromProjection(const TArray& Triangles, const FFrame3d& ProjectionFrame, float UVScaleFactor, const FVector2f& UVTranslation, int UVLayerIndex) { if (!Triangles.Num()) { return; } check(Mesh->HasAttributes() && Mesh->Attributes()->NumUVLayers() > UVLayerIndex); FDynamicMeshUVOverlay* UVs = Mesh->Attributes()->GetUVLayer(UVLayerIndex); TMap BaseToOverlayVIDMap; TArray AllUVIndices; FAxisAlignedBox2f UVBounds(FAxisAlignedBox2f::Empty()); for (int TID : Triangles) { FIndex3i BaseTri = Mesh->GetTriangle(TID); FIndex3i ElemTri; for (int j = 0; j < 3; ++j) { const int* FoundElementID = BaseToOverlayVIDMap.Find(BaseTri[j]); if (FoundElementID == nullptr) { FVector2f UV = (FVector2f)ProjectionFrame.ToPlaneUV(Mesh->GetVertex(BaseTri[j]), 2); UVBounds.Contain(UV); ElemTri[j] = UVs->AppendElement(UV, BaseTri[j]); AllUVIndices.Add(ElemTri[j]); BaseToOverlayVIDMap.Add(BaseTri[j], ElemTri[j]); } else { ElemTri[j] = *FoundElementID; } } UVs->SetTriangle(TID, ElemTri); } // shift UVs so that their bbox min-corner is at origin and scaled by external scale factor for (int UVID : AllUVIndices) { FVector2f UV = UVs->GetElement(UVID); FVector2f TransformedUV = (UV - UVBounds.Min) * UVScaleFactor; TransformedUV += UVTranslation; UVs->SetElement(UVID, TransformedUV); } } void FDynamicMeshEditor::SetQuadUVsFromProjection(const FIndex2i& QuadTris, const FFrame3d& ProjectionFrame, float UVScaleFactor, const FVector2f& UVTranslation, int UVLayerIndex) { check(Mesh->HasAttributes() && Mesh->Attributes()->NumUVLayers() > UVLayerIndex ); FDynamicMeshUVOverlay* UVs = Mesh->Attributes()->GetUVLayer(UVLayerIndex); FIndex4i AllUVIndices(-1, -1, -1, -1); FVector2f AllUVs[4]; // project first triangle FIndex3i Triangle1 = Mesh->GetTriangle(QuadTris.A); FIndex3i UVTriangle1; for (int j = 0; j < 3; ++j) { FVector2f UV = (FVector2f)ProjectionFrame.ToPlaneUV(Mesh->GetVertex(Triangle1[j]), 2); UVTriangle1[j] = UVs->AppendElement(UV, Triangle1[j]); AllUVs[j] = UV; AllUVIndices[j] = UVTriangle1[j]; } UVs->SetTriangle(QuadTris.A, UVTriangle1); // project second triangle if (Mesh->IsTriangle(QuadTris.B)) { FIndex3i Triangle2 = Mesh->GetTriangle(QuadTris.B); FIndex3i UVTriangle2; for (int j = 0; j < 3; ++j) { int i = Triangle1.IndexOf(Triangle2[j]); if (i == -1) { FVector2f UV = (FVector2f)ProjectionFrame.ToPlaneUV(Mesh->GetVertex(Triangle2[j]), 2); UVTriangle2[j] = UVs->AppendElement(UV, Triangle2[j]); AllUVs[3] = UV; AllUVIndices[3] = UVTriangle2[j]; } else { UVTriangle2[j] = UVTriangle1[i]; } } UVs->SetTriangle(QuadTris.B, UVTriangle2); } // shift UVs so that their bbox min-corner is at origin and scaled by external scale factor FAxisAlignedBox2f UVBounds(FAxisAlignedBox2f::Empty()); UVBounds.Contain(AllUVs[0]); UVBounds.Contain(AllUVs[1]); UVBounds.Contain(AllUVs[2]); if (AllUVIndices[3] != -1) { UVBounds.Contain(AllUVs[3]); } for (int j = 0; j < 4; ++j) { if (AllUVIndices[j] != -1) { FVector2f TransformedUV = (AllUVs[j] - UVBounds.Min) * UVScaleFactor; TransformedUV += UVTranslation; UVs->SetElement(AllUVIndices[j], TransformedUV); } } } void FDynamicMeshEditor::RescaleAttributeUVs(float UVScale, bool bWorldSpace, int UVLayerIndex, TOptional ToWorld) { check(Mesh->HasAttributes() && Mesh->Attributes()->NumUVLayers() > UVLayerIndex ); FDynamicMeshUVOverlay* UVs = Mesh->Attributes()->GetUVLayer(UVLayerIndex); if (bWorldSpace) { FVector2f TriUVs[3]; FVector3d TriVs[3]; float TotalEdgeUVLen = 0; double TotalEdgeLen = 0; for (int TID : Mesh->TriangleIndicesItr()) { UVs->GetTriElements(TID, TriUVs[0], TriUVs[1], TriUVs[2]); Mesh->GetTriVertices(TID, TriVs[0], TriVs[1], TriVs[2]); if (ToWorld.IsSet()) { for (int i = 0; i < 3; i++) { TriVs[i] = ToWorld->TransformPosition(TriVs[i]); } } for (int j = 2, i = 0; i < 3; j = i++) { TotalEdgeUVLen += TriUVs[j].Distance(TriUVs[i]); TotalEdgeLen += TriVs[j].Distance(TriVs[i]); } } if (TotalEdgeUVLen > KINDA_SMALL_NUMBER) { float AvgUVScale = TotalEdgeLen / TotalEdgeUVLen; UVScale *= AvgUVScale; } } for (int UVID : UVs->ElementIndicesItr()) { FVector2f UV; UVs->GetElement(UVID, UV); UVs->SetElement(UVID, UV*UVScale); } } void FDynamicMeshEditor::ReverseTriangleOrientations(const TArray& Triangles, bool bInvertNormals) { for (int tid : Triangles) { Mesh->ReverseTriOrientation(tid); } if (bInvertNormals) { InvertTriangleNormals(Triangles); } } void FDynamicMeshEditor::InvertTriangleNormals(const TArray& Triangles) { // @todo re-use the TBitA if (Mesh->HasVertexNormals()) { TBitArray DoneVertices(false, Mesh->MaxVertexID()); for (int TriangleID : Triangles) { FIndex3i Tri = Mesh->GetTriangle(TriangleID); for (int j = 0; j < 3; ++j) { if (DoneVertices[Tri[j]] == false) { Mesh->SetVertexNormal(Tri[j], -Mesh->GetVertexNormal(Tri[j])); DoneVertices[Tri[j]] = true; } } } } if (Mesh->HasAttributes()) { for (FDynamicMeshNormalOverlay* Normals : Mesh->Attributes()->GetAllNormalLayers()) { TBitArray DoneNormals(false, Normals->MaxElementID()); for (int TriangleID : Triangles) { FIndex3i ElemTri = Normals->GetTriangle(TriangleID); for (int j = 0; j < 3; ++j) { if (DoneNormals[ElemTri[j]] == false) { Normals->SetElement(ElemTri[j], -Normals->GetElement(ElemTri[j])); DoneNormals[ElemTri[j]] = true; } } } } } } void FDynamicMeshEditor::CopyAttributes(int FromTriangleID, int ToTriangleID, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut) { if (Mesh->HasAttributes() == false) { return; } int UVLayerIndex = 0; for (FDynamicMeshUVOverlay* UVOverlay : Mesh->Attributes()->GetAllUVLayers()) { FIndex3i FromElemTri = UVOverlay->GetTriangle(FromTriangleID); FIndex3i ToElemTri = UVOverlay->GetTriangle(ToTriangleID); for (int j = 0; j < 3; ++j) { if (FromElemTri[j] != FDynamicMesh3::InvalidID ) { int NewElemID = FindOrCreateDuplicateUV(FromElemTri[j], UVLayerIndex, IndexMaps); ToElemTri[j] = NewElemID; } } UVOverlay->SetTriangle(ToTriangleID, ToElemTri); UVLayerIndex++; } int NormalLayerIndex = 0; for (FDynamicMeshNormalOverlay* NormalOverlay : Mesh->Attributes()->GetAllNormalLayers()) { FIndex3i FromElemTri = NormalOverlay->GetTriangle(FromTriangleID); FIndex3i ToElemTri = NormalOverlay->GetTriangle(ToTriangleID); for (int j = 0; j < 3; ++j) { if (FromElemTri[j] != FDynamicMesh3::InvalidID) { int NewElemID = FindOrCreateDuplicateNormal(FromElemTri[j], NormalLayerIndex, IndexMaps); ToElemTri[j] = NewElemID; } } NormalOverlay->SetTriangle(ToTriangleID, ToElemTri); NormalLayerIndex++; } } int FDynamicMeshEditor::FindOrCreateDuplicateUV(int ElementID, int UVLayerIndex, FMeshIndexMappings& IndexMaps) { int NewElementID = IndexMaps.GetNewUV(UVLayerIndex, ElementID); if (NewElementID == IndexMaps.InvalidID()) { FDynamicMeshUVOverlay* UVOverlay = Mesh->Attributes()->GetUVLayer(UVLayerIndex); // need to determine new parent vertex. It should be in the map already! int ParentVertexID = UVOverlay->GetParentVertex(ElementID); int NewParentVertexID = IndexMaps.GetNewVertex(ParentVertexID); check(NewParentVertexID != IndexMaps.InvalidID()); NewElementID = UVOverlay->AppendElement( UVOverlay->GetElement(ElementID), NewParentVertexID); IndexMaps.SetUV(UVLayerIndex, ElementID, NewElementID); } return NewElementID; } int FDynamicMeshEditor::FindOrCreateDuplicateNormal(int ElementID, int NormalLayerIndex, FMeshIndexMappings& IndexMaps) { int NewElementID = IndexMaps.GetNewNormal(NormalLayerIndex, ElementID); if (NewElementID == IndexMaps.InvalidID()) { FDynamicMeshNormalOverlay* NormalOverlay = Mesh->Attributes()->GetNormalLayer(NormalLayerIndex); // need to determine new parent vertex. It should be in the map already! int ParentVertexID = NormalOverlay->GetParentVertex(ElementID); int NewParentVertexID = IndexMaps.GetNewVertex(ParentVertexID); check(NewParentVertexID != IndexMaps.InvalidID()); NewElementID = NormalOverlay->AppendElement( NormalOverlay->GetElement(ElementID), NewParentVertexID); IndexMaps.SetNormal(NormalLayerIndex, ElementID, NewElementID); } return NewElementID; } int FDynamicMeshEditor::FindOrCreateDuplicateVertex(int VertexID, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut) { int NewVertexID = IndexMaps.GetNewVertex(VertexID); if (NewVertexID == IndexMaps.InvalidID()) { NewVertexID = Mesh->AppendVertex(*Mesh, VertexID); IndexMaps.SetVertex(VertexID, NewVertexID); ResultOut.NewVertices.Add(NewVertexID); } return NewVertexID; } int FDynamicMeshEditor::FindOrCreateDuplicateGroup(int TriangleID, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut) { int GroupID = Mesh->GetTriangleGroup(TriangleID); int NewGroupID = IndexMaps.GetNewGroup(GroupID); if (NewGroupID == IndexMaps.InvalidID()) { NewGroupID = Mesh->AllocateTriangleGroup(); IndexMaps.SetGroup(GroupID, NewGroupID); ResultOut.NewGroups.Add(NewGroupID); } return NewGroupID; } void FDynamicMeshEditor::AppendMesh(const FDynamicMesh3* AppendMesh, FMeshIndexMappings& IndexMapsOut, TFunction PositionTransform, TFunction NormalTransform) { IndexMapsOut.Reset(); IndexMapsOut.Initialize(Mesh); FIndexMapi& VertexMap = IndexMapsOut.GetVertexMap(); VertexMap.Reserve(AppendMesh->VertexCount()); for (int VertID : AppendMesh->VertexIndicesItr()) { FVector3d Position = AppendMesh->GetVertex(VertID); if (PositionTransform != nullptr) { Position = PositionTransform(VertID, Position); } int NewVertID = Mesh->AppendVertex(Position); VertexMap.Add(VertID, NewVertID); if (AppendMesh->HasVertexNormals() && Mesh->HasVertexNormals()) { FVector3f Normal = AppendMesh->GetVertexNormal(VertID); if (NormalTransform != nullptr) { Normal = (FVector3f)NormalTransform(VertID, (FVector3d)Normal); } Mesh->SetVertexNormal(NewVertID, Normal); } if (AppendMesh->HasVertexColors() && Mesh->HasVertexColors()) { FVector3f Color = AppendMesh->GetVertexColor(VertID); Mesh->SetVertexColor(NewVertID, Color); } } FIndexMapi& TriangleMap = IndexMapsOut.GetTriangleMap(); bool bAppendGroups = AppendMesh->HasTriangleGroups() && Mesh->HasTriangleGroups(); FIndexMapi& GroupsMap = IndexMapsOut.GetGroupMap(); for (int TriID : AppendMesh->TriangleIndicesItr()) { // append trigroup int GroupID = FDynamicMesh3::InvalidID; if (bAppendGroups) { GroupID = AppendMesh->GetTriangleGroup(TriID); if (GroupID != FDynamicMesh3::InvalidID) { const int* FoundNewGroupID = GroupsMap.FindTo(GroupID); if (FoundNewGroupID == nullptr) { int NewGroupID = Mesh->AllocateTriangleGroup(); GroupsMap.Add(GroupID, NewGroupID); GroupID = NewGroupID; } else { GroupID = *FoundNewGroupID; } } } FIndex3i Tri = AppendMesh->GetTriangle(TriID); int NewTriID = Mesh->AppendTriangle(VertexMap.GetTo(Tri.A), VertexMap.GetTo(Tri.B), VertexMap.GetTo(Tri.C), GroupID); TriangleMap.Add(TriID, NewTriID); } // @todo support multiple UV/normal layer copying // @todo can we have a template fn that does this? if (AppendMesh->HasAttributes() && Mesh->HasAttributes()) { const FDynamicMeshNormalOverlay* FromNormals = AppendMesh->Attributes()->PrimaryNormals(); FDynamicMeshNormalOverlay* ToNormals = Mesh->Attributes()->PrimaryNormals(); if (FromNormals != nullptr && ToNormals != nullptr) { FIndexMapi& NormalMap = IndexMapsOut.GetNormalMap(0); NormalMap.Reserve(FromNormals->ElementCount()); AppendNormals(AppendMesh, FromNormals, ToNormals, VertexMap, TriangleMap, NormalTransform, NormalMap); } const FDynamicMeshUVOverlay* FromUVs = AppendMesh->Attributes()->PrimaryUV(); FDynamicMeshUVOverlay* ToUVs = Mesh->Attributes()->PrimaryUV(); if (FromUVs != nullptr && ToUVs != nullptr) { FIndexMapi& UVMap = IndexMapsOut.GetUVMap(0); UVMap.Reserve(FromUVs->ElementCount()); AppendUVs(AppendMesh, FromUVs, ToUVs, VertexMap, TriangleMap, UVMap); } } } void FDynamicMeshEditor::AppendNormals(const FDynamicMesh3* AppendMesh, const FDynamicMeshNormalOverlay* FromNormals, FDynamicMeshNormalOverlay* ToNormals, const FIndexMapi& VertexMap, const FIndexMapi& TriangleMap, TFunction NormalTransform, FIndexMapi& NormalMapOut) { // copy over normals for (int ElemID : FromNormals->ElementIndicesItr()) { int ParentVertID = FromNormals->GetParentVertex(ElemID); FVector3f Normal = FromNormals->GetElement(ElemID); if (NormalTransform != nullptr) { Normal = (FVector3f)NormalTransform(ParentVertID, (FVector3d)Normal); } int NewElemID = ToNormals->AppendElement(Normal, VertexMap.GetTo(ParentVertID)); NormalMapOut.Add(ElemID, NewElemID); } // now set new triangles for (int TriID : AppendMesh->TriangleIndicesItr()) { FIndex3i ElemTri = FromNormals->GetTriangle(TriID); int NewTriID = TriangleMap.GetTo(TriID); for (int j = 0; j < 3; ++j) { ElemTri[j] = FromNormals->IsElement(ElemTri[j]) ? NormalMapOut.GetTo(ElemTri[j]) : FDynamicMesh3::InvalidID; } ToNormals->SetTriangle(NewTriID, ElemTri); } } void FDynamicMeshEditor::AppendUVs(const FDynamicMesh3* AppendMesh, const FDynamicMeshUVOverlay* FromUVs, FDynamicMeshUVOverlay* ToUVs, const FIndexMapi& VertexMap, const FIndexMapi& TriangleMap, FIndexMapi& UVMapOut) { // copy over uv elements for (int ElemID : FromUVs->ElementIndicesItr()) { int ParentVertID = FromUVs->GetParentVertex(ElemID); FVector2f UV = FromUVs->GetElement(ElemID); int NewElemID = ToUVs->AppendElement(UV, VertexMap.GetTo(ParentVertID)); UVMapOut.Add(ElemID, NewElemID); } // now set new triangles for (int TriID : AppendMesh->TriangleIndicesItr()) { FIndex3i ElemTri = FromUVs->GetTriangle(TriID); int NewTriID = TriangleMap.GetTo(TriID); for (int j = 0; j < 3; ++j) { ElemTri[j] = FromUVs->IsElement(ElemTri[j]) ? UVMapOut.GetTo(ElemTri[j]) : FDynamicMesh3::InvalidID; } ToUVs->SetTriangle(NewTriID, ElemTri); } } // can these be replaced w/ template function? // Utility function for ::AppendTriangles() static int AppendTriangleUVAttribute(const FDynamicMesh3* FromMesh, int FromElementID, FDynamicMesh3* ToMesh, int UVLayerIndex, FMeshIndexMappings& IndexMaps) { int NewElementID = IndexMaps.GetNewUV(UVLayerIndex, FromElementID); if (NewElementID == IndexMaps.InvalidID()) { const FDynamicMeshUVOverlay* FromUVOverlay = FromMesh->Attributes()->GetUVLayer(UVLayerIndex); FDynamicMeshUVOverlay* ToUVOverlay = ToMesh->Attributes()->GetUVLayer(UVLayerIndex); // need to determine new parent vertex. It should be in the map already! int ParentVertexID = FromUVOverlay->GetParentVertex(FromElementID); int NewParentVertexID = IndexMaps.GetNewVertex(ParentVertexID); check(NewParentVertexID != IndexMaps.InvalidID()); NewElementID = ToUVOverlay->AppendElement( FromUVOverlay->GetElement(FromElementID), NewParentVertexID); IndexMaps.SetUV(UVLayerIndex, FromElementID, NewElementID); } return NewElementID; } // Utility function for ::AppendTriangles() static int AppendTriangleNormalAttribute(const FDynamicMesh3* FromMesh, int FromElementID, FDynamicMesh3* ToMesh, int NormalLayerIndex, FMeshIndexMappings& IndexMaps) { int NewElementID = IndexMaps.GetNewNormal(NormalLayerIndex, FromElementID); if (NewElementID == IndexMaps.InvalidID()) { const FDynamicMeshNormalOverlay* FromNormalOverlay = FromMesh->Attributes()->GetNormalLayer(NormalLayerIndex); FDynamicMeshNormalOverlay* ToNormalOverlay = ToMesh->Attributes()->GetNormalLayer(NormalLayerIndex); // need to determine new parent vertex. It should be in the map already! int ParentVertexID = FromNormalOverlay->GetParentVertex(FromElementID); int NewParentVertexID = IndexMaps.GetNewVertex(ParentVertexID); check(NewParentVertexID != IndexMaps.InvalidID()); NewElementID = ToNormalOverlay->AppendElement( FromNormalOverlay->GetElement(FromElementID), NewParentVertexID); IndexMaps.SetNormal(NormalLayerIndex, FromElementID, NewElementID); } return NewElementID; } // Utility function for ::AppendTriangles() static void AppendAttributes(const FDynamicMesh3* FromMesh, int FromTriangleID, FDynamicMesh3* ToMesh, int ToTriangleID, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut) { if (FromMesh->HasAttributes() == false || ToMesh->HasAttributes() == false) { return; } // todo: copy all attribute layers check(FromMesh->Attributes()->GetNumUVLayers() == 1); check(FromMesh->Attributes()->GetNumNormalLayers() == 1); const FDynamicMeshUVOverlay* FromUVOverlay = FromMesh->Attributes()->PrimaryUV(); FDynamicMeshUVOverlay* ToUVOverlay = ToMesh->Attributes()->PrimaryUV(); { FIndex3i FromElemTri = FromUVOverlay->GetTriangle(FromTriangleID); FIndex3i ToElemTri = ToUVOverlay->GetTriangle(ToTriangleID); for (int j = 0; j < 3; ++j) { if (FromElemTri[j] != FDynamicMesh3::InvalidID) { int NewElemID = AppendTriangleUVAttribute(FromMesh, FromElemTri[j], ToMesh, 0, IndexMaps); ToElemTri[j] = NewElemID; } } ToUVOverlay->SetTriangle(ToTriangleID, ToElemTri); } const FDynamicMeshNormalOverlay* FromNormalOverlay = FromMesh->Attributes()->PrimaryNormals(); FDynamicMeshNormalOverlay* ToNormalOverlay = ToMesh->Attributes()->PrimaryNormals(); { FIndex3i FromElemTri = FromNormalOverlay->GetTriangle(FromTriangleID); FIndex3i ToElemTri = ToNormalOverlay->GetTriangle(ToTriangleID); for (int j = 0; j < 3; ++j) { if (FromElemTri[j] != FDynamicMesh3::InvalidID) { int NewElemID = AppendTriangleNormalAttribute(FromMesh, FromElemTri[j], ToMesh, 0, IndexMaps); ToElemTri[j] = NewElemID; } } ToNormalOverlay->SetTriangle(ToTriangleID, ToElemTri); } } void FDynamicMeshEditor::AppendTriangles(const FDynamicMesh3* SourceMesh, const TArray& SourceTriangles, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut) { ResultOut.Reset(); IndexMaps.Initialize(Mesh); for (int SourceTriangleID : SourceTriangles) { check(SourceMesh->IsTriangle(SourceTriangleID)); if (SourceMesh->IsTriangle(SourceTriangleID) == false) { continue; // ignore missing triangles } FIndex3i Tri = SourceMesh->GetTriangle(SourceTriangleID); // FindOrCreateDuplicateGroup int SourceGroupID = SourceMesh->GetTriangleGroup(SourceTriangleID); int NewGroupID = IndexMaps.GetNewGroup(SourceGroupID); if (NewGroupID == IndexMaps.InvalidID()) { NewGroupID = Mesh->AllocateTriangleGroup(); IndexMaps.SetGroup(SourceGroupID, NewGroupID); ResultOut.NewGroups.Add(NewGroupID); } // FindOrCreateDuplicateVertex FIndex3i NewTri; for (int j = 0; j < 3; ++j) { int SourceVertexID = Tri[j]; int NewVertexID = IndexMaps.GetNewVertex(SourceVertexID); if (NewVertexID == IndexMaps.InvalidID()) { NewVertexID = Mesh->AppendVertex(*SourceMesh, SourceVertexID); IndexMaps.SetVertex(SourceVertexID, NewVertexID); ResultOut.NewVertices.Add(NewVertexID); } NewTri[j] = NewVertexID; } int NewTriangleID = Mesh->AppendTriangle(NewTri, NewGroupID); IndexMaps.SetTriangle(SourceTriangleID, NewTriangleID); ResultOut.NewTriangles.Add(NewTriangleID); AppendAttributes(SourceMesh, SourceTriangleID, Mesh, NewTriangleID, IndexMaps, ResultOut); //Mesh->CheckValidity(true); } }