You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
- Allow the Attribute Editor Tool to add/delete weight maps from DynamicMesh targets. - Enable transferring scalar weight maps back and forth between DynamicMesh and MeshDescription. - Serialize TDynamicAttributeBase::Name when its subclasses are serialized #rb nathan.mitchell #preflight 62439b2ab6084b9832307f6e [CL 19551734 by tyson brochu in ue5-main branch]
2152 lines
65 KiB
C++
2152 lines
65 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
#include "DynamicMeshEditor.h"
|
|
#include "DynamicMesh/DynamicMeshAttributeSet.h"
|
|
#include "Util/BufferUtil.h"
|
|
#include "MeshRegionBoundaryLoops.h"
|
|
#include "DynamicSubmesh3.h"
|
|
#include "DynamicMesh/DynamicVertexSkinWeightsAttribute.h"
|
|
#include "DynamicMesh/MeshNormals.h"
|
|
#include "MeshQueries.h"
|
|
#include "Selections/MeshConnectedComponents.h"
|
|
|
|
using namespace UE::Geometry;
|
|
|
|
|
|
|
|
void FDynamicMeshEditResult::GetAllTriangles(TArray<int>& 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::RemoveIsolatedVertices()
|
|
{
|
|
bool bSuccess = true;
|
|
for (int VID : Mesh->VertexIndicesItr())
|
|
{
|
|
if (!Mesh->IsReferencedVertex(VID))
|
|
{
|
|
constexpr bool bPreserveManifold = false;
|
|
bSuccess = Mesh->RemoveVertex(VID, bPreserveManifold) == EMeshResult::Ok && bSuccess;
|
|
}
|
|
}
|
|
return bSuccess;
|
|
}
|
|
|
|
|
|
bool FDynamicMeshEditor::StitchVertexLoopsMinimal(const TArray<int>& Loop1, const TArray<int>& Loop2, FDynamicMeshEditResult& ResultOut)
|
|
{
|
|
int N = Loop1.Num();
|
|
checkf(N == Loop2.Num(), TEXT("FDynamicMeshEditor::StitchVertexLoopsMinimal: 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<int> 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::StitchVertexLoopsMinimal: failed to add all triangles, and also failed to back out changes."));
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FDynamicMeshEditor::WeldVertexLoops(const TArray<int32>& Loop1, const TArray<int32>& Loop2)
|
|
{
|
|
int32 N = Loop1.Num();
|
|
checkf(N == Loop2.Num(), TEXT("FDynamicMeshEditor::WeldVertexLoops: loops are not the same length!"));
|
|
if (N != Loop2.Num())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int32 FailureCount = 0;
|
|
|
|
// collect set of edges
|
|
TArray<int32> Edges1, Edges2;
|
|
Edges1.SetNum(N);
|
|
Edges2.SetNum(N);
|
|
for (int32 i = 0; i < N; ++i)
|
|
{
|
|
int32 a = Loop1[i];
|
|
int32 b = Loop1[(i + 1) % N];
|
|
Edges1[i] = Mesh->FindEdge(a, b);
|
|
if (Edges1[i] == FDynamicMesh3::InvalidID)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int32 c = Loop2[i];
|
|
int32 d = Loop2[(i + 1) % N];
|
|
Edges2[i] = Mesh->FindEdge(c, d);
|
|
if (Edges2[i] == FDynamicMesh3::InvalidID)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// merge edges. Some merges may merge multiple edges, in which case we want to
|
|
// skip those when we encounter them later.
|
|
TArray<int32> SkipEdges;
|
|
for (int32 i = 0; i < N; ++i)
|
|
{
|
|
int32 Edge1 = Edges1[i];
|
|
int32 Edge2 = Edges2[i];
|
|
if (SkipEdges.Contains(Edge2)) // occurs at loop closures
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FDynamicMesh3::FMergeEdgesInfo MergeInfo;
|
|
EMeshResult Result = Mesh->MergeEdges(Edge1, Edge2, MergeInfo);
|
|
if (Result != EMeshResult::Ok)
|
|
{
|
|
FailureCount++;
|
|
}
|
|
else
|
|
{
|
|
if (MergeInfo.ExtraRemovedEdges.A != FDynamicMesh3::InvalidID)
|
|
{
|
|
SkipEdges.Add(MergeInfo.ExtraRemovedEdges.A);
|
|
}
|
|
if (MergeInfo.ExtraRemovedEdges.B != FDynamicMesh3::InvalidID)
|
|
{
|
|
SkipEdges.Add(MergeInfo.ExtraRemovedEdges.B);
|
|
}
|
|
}
|
|
}
|
|
|
|
return (FailureCount > 0);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool FDynamicMeshEditor::StitchSparselyCorrespondedVertexLoops(const TArray<int>& VertexIDs1, const TArray<int>& MatchedIndices1, const TArray<int>& VertexIDs2, const TArray<int>& MatchedIndices2, FDynamicMeshEditResult& ResultOut, bool bReverseOrientation)
|
|
{
|
|
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<int>& 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 += Distance(V, 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] += Distance(NextV, 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] += Distance(NextV, Vertex[1]);
|
|
Vertex[1] = NextV;
|
|
}
|
|
if (bReverseOrientation)
|
|
{
|
|
Swap(Tri.B, Tri.C);
|
|
}
|
|
int Tid = Mesh->AppendTriangle(Tri, NewGroupID);
|
|
if (Tid < 0)
|
|
{
|
|
goto operation_failed;
|
|
}
|
|
ResultOut.NewTriangles.Add(Tid);
|
|
}
|
|
}
|
|
|
|
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<int>& 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<int>& Triangles, bool bRemoveIsolatedVerts)
|
|
{
|
|
return RemoveTriangles(Triangles, bRemoveIsolatedVerts, [](int) {});
|
|
}
|
|
|
|
|
|
bool FDynamicMeshEditor::RemoveTriangles(const TArray<int>& Triangles, bool bRemoveIsolatedVerts, TFunctionRef<void(int)> 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;
|
|
}
|
|
|
|
|
|
|
|
int FDynamicMeshEditor::RemoveSmallComponents(double MinVolume, double MinArea, int MinTriangleCount)
|
|
{
|
|
FMeshConnectedComponents C(Mesh);
|
|
C.FindConnectedTriangles();
|
|
if (C.Num() == 1)
|
|
{
|
|
return 0;
|
|
}
|
|
int Removed = 0;
|
|
for (FMeshConnectedComponents::FComponent& Comp : C) {
|
|
bool bRemove = Comp.Indices.Num() < MinTriangleCount;
|
|
if (!bRemove)
|
|
{
|
|
FVector2d VolArea = TMeshQueries<FDynamicMesh3>::GetVolumeArea(*Mesh, Comp.Indices);
|
|
bRemove = VolArea.X < MinVolume || VolArea.Y < MinArea;
|
|
}
|
|
if (bRemove) {
|
|
RemoveTriangles(Comp.Indices, true);
|
|
Removed++;
|
|
}
|
|
}
|
|
return Removed;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 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<int>& Triangles, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut)
|
|
{
|
|
ResultOut.Reset();
|
|
IndexMaps.Initialize(Mesh);
|
|
|
|
for (int TriangleID : Triangles)
|
|
{
|
|
FIndex3i Tri = Mesh->GetTriangle(TriangleID);
|
|
|
|
int NewGroupID = Mesh->HasTriangleGroups() ? FindOrCreateDuplicateGroup(TriangleID, IndexMaps, ResultOut) : -1;
|
|
|
|
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<int>& Triangles, TArray<FLoopPairSet>& LoopSetOut, bool bHandleBoundaryVertices)
|
|
{
|
|
// find the region boundary loops
|
|
FMeshRegionBoundaryLoops RegionLoops(Mesh, Triangles, false);
|
|
bool bOK = RegionLoops.Compute();
|
|
if (!ensure(bOK))
|
|
{
|
|
return false;
|
|
}
|
|
TArray<FEdgeLoop>& Loops = RegionLoops.Loops;
|
|
|
|
// need to test Contains() many times
|
|
TSet<int> TriangleSet;
|
|
TriangleSet.Reserve(Triangles.Num() * 3);
|
|
for (int TriID : Triangles)
|
|
{
|
|
TriangleSet.Add(TriID);
|
|
}
|
|
|
|
// process each loop island
|
|
int NumLoops = Loops.Num();
|
|
LoopSetOut.SetNum(NumLoops);
|
|
TArray<int> FilteredTriangles;
|
|
for ( int li = 0; li < NumLoops; ++li)
|
|
{
|
|
FEdgeLoop& Loop = Loops[li];
|
|
FLoopPairSet& LoopPair = LoopSetOut[li];
|
|
LoopPair.OuterVertices = Loop.Vertices;
|
|
LoopPair.OuterEdges = Loop.Edges;
|
|
|
|
bool bSawBoundaryInLoop = false;
|
|
|
|
// duplicate the vertices
|
|
int NumVertices = Loop.Vertices.Num();
|
|
TMap<int, int> LoopVertexMap; LoopVertexMap.Reserve(NumVertices);
|
|
TArray<int> NewVertexLoop; NewVertexLoop.SetNum(NumVertices);
|
|
for (int vi = 0; vi < NumVertices; ++vi)
|
|
{
|
|
int VertID = Loop.Vertices[vi];
|
|
|
|
FilteredTriangles.Reset();
|
|
int TriRingCount = 0;
|
|
for (int RingTID : Mesh->VtxTrianglesItr(VertID))
|
|
{
|
|
if (TriangleSet.Contains(RingTID))
|
|
{
|
|
FilteredTriangles.Add(RingTID);
|
|
}
|
|
TriRingCount++;
|
|
}
|
|
bool bIsSubset = (FilteredTriangles.Num() < TriRingCount);
|
|
if ( bIsSubset )
|
|
{
|
|
checkSlow(!Mesh->SplitVertexWouldLeaveIsolated(VertID, FilteredTriangles));
|
|
DynamicMeshInfo::FVertexSplitInfo SplitInfo;
|
|
ensure(EMeshResult::Ok == Mesh->SplitVertex(VertID, FilteredTriangles, SplitInfo));
|
|
|
|
int NewVertID = SplitInfo.NewVertex;
|
|
LoopVertexMap.Add(Loop.Vertices[vi], NewVertID);
|
|
NewVertexLoop[vi] = NewVertID;
|
|
}
|
|
else if (bHandleBoundaryVertices)
|
|
{
|
|
// if we have a boundary vertex, we are going to duplicate it and use the duplicated
|
|
// vertex as the "old" one, and just keep the existing one on the "inner" loop.
|
|
// This means we have to rewrite vertex in the "outer" loop, and that loop will no longer actually be an EdgeLoop, so we set those edges to invalid
|
|
int32 NewVertID = Mesh->AppendVertex(*Mesh, VertID);
|
|
LoopVertexMap.Add(NewVertID, VertID);
|
|
LoopPair.OuterVertices[vi] = NewVertID;
|
|
LoopPair.OuterEdges[vi] = FDynamicMesh3::InvalidID;
|
|
LoopPair.OuterEdges[(vi == 0) ? NumVertices-1 : vi-1] = FDynamicMesh3::InvalidID;
|
|
NewVertexLoop[vi] = VertID;
|
|
bSawBoundaryInLoop = true;
|
|
}
|
|
else
|
|
{
|
|
ensure(false);
|
|
return false; // cannot proceed
|
|
}
|
|
}
|
|
|
|
FEdgeLoop InnerLoop;
|
|
if (!ensure(InnerLoop.InitializeFromVertices(Mesh, NewVertexLoop, false)))
|
|
{
|
|
return false;
|
|
}
|
|
LoopPair.InnerVertices = MoveTemp(InnerLoop.Vertices);
|
|
LoopPair.InnerEdges = MoveTemp(InnerLoop.Edges);
|
|
LoopPair.bOuterIncludesIsolatedVertices = bSawBoundaryInLoop;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void FDynamicMeshEditor::DisconnectTriangles(const TArray<int>& Triangles, bool bPreventBowties)
|
|
{
|
|
TSet<int> TriSet, BoundaryVerts;
|
|
TArray<int> NewVerts, OldVertsThatSplit;
|
|
TArray<int> FilteredTriangles;
|
|
DynamicMeshInfo::FVertexSplitInfo SplitInfo;
|
|
|
|
TriSet.Append(Triangles);
|
|
for (int TID : Triangles)
|
|
{
|
|
FIndex3i Nbrs = Mesh->GetTriNeighbourTris(TID);
|
|
FIndex3i Tri = Mesh->GetTriangle(TID);
|
|
for (int SubIdx = 0; SubIdx < 3; SubIdx++)
|
|
{
|
|
int NeighborTID = Nbrs[SubIdx];
|
|
if (!TriSet.Contains(NeighborTID))
|
|
{
|
|
BoundaryVerts.Add(Tri[SubIdx]);
|
|
BoundaryVerts.Add(Tri[(SubIdx + 1) % 3]);
|
|
}
|
|
}
|
|
}
|
|
for (int VID : BoundaryVerts)
|
|
{
|
|
FilteredTriangles.Reset();
|
|
int TriRingCount = 0;
|
|
for (int RingTID : Mesh->VtxTrianglesItr(VID))
|
|
{
|
|
if (TriSet.Contains(RingTID))
|
|
{
|
|
FilteredTriangles.Add(RingTID);
|
|
}
|
|
TriRingCount++;
|
|
}
|
|
|
|
if (FilteredTriangles.Num() < TriRingCount)
|
|
{
|
|
checkSlow(!Mesh->SplitVertexWouldLeaveIsolated(VID, FilteredTriangles));
|
|
ensure(EMeshResult::Ok == Mesh->SplitVertex(VID, FilteredTriangles, SplitInfo));
|
|
NewVerts.Add(SplitInfo.NewVertex);
|
|
OldVertsThatSplit.Add(SplitInfo.OriginalVertex);
|
|
}
|
|
}
|
|
if (bPreventBowties)
|
|
{
|
|
FDynamicMeshEditResult Result;
|
|
for (int VID : OldVertsThatSplit)
|
|
{
|
|
SplitBowties(VID, Result);
|
|
Result.Reset(); // don't actually keep results; they are not used in this fn
|
|
}
|
|
for (int VID : NewVerts)
|
|
{
|
|
SplitBowties(VID, Result);
|
|
Result.Reset(); // don't actually keep results; they are not used in this fn
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void FDynamicMeshEditor::SplitBowties(FDynamicMeshEditResult& ResultOut)
|
|
{
|
|
ResultOut.Reset();
|
|
TSet<int> AddedVerticesWithIDLessThanMax; // added vertices that we can't filter just by checking against original max id; this will be empty for compact meshes
|
|
for (int VertexID = 0, OriginalMaxID = Mesh->MaxVertexID(); VertexID < OriginalMaxID; VertexID++)
|
|
{
|
|
if (!Mesh->IsVertex(VertexID) || AddedVerticesWithIDLessThanMax.Contains(VertexID))
|
|
{
|
|
continue;
|
|
}
|
|
int32 NumVertsBefore = ResultOut.NewVertices.Num();
|
|
// TODO: may be faster to inline this call to reuse the contiguous triangle arrays?
|
|
SplitBowties(VertexID, ResultOut);
|
|
for (int Idx = NumVertsBefore; Idx < ResultOut.NewVertices.Num(); Idx++)
|
|
{
|
|
if (ResultOut.NewVertices[Idx] < OriginalMaxID)
|
|
{
|
|
AddedVerticesWithIDLessThanMax.Add(ResultOut.NewVertices[Idx]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void FDynamicMeshEditor::SplitBowties(int VertexID, FDynamicMeshEditResult& ResultOut)
|
|
{
|
|
TArray<int> TrianglesOut, ContiguousGroupLengths;
|
|
TArray<bool> GroupIsLoop;
|
|
DynamicMeshInfo::FVertexSplitInfo SplitInfo;
|
|
check(Mesh->IsVertex(VertexID));
|
|
if (ensure(EMeshResult::Ok == Mesh->GetVtxContiguousTriangles(VertexID, TrianglesOut, ContiguousGroupLengths, GroupIsLoop)))
|
|
{
|
|
if (ContiguousGroupLengths.Num() > 1)
|
|
{
|
|
// is bowtie
|
|
for (int GroupIdx = 1, GroupStartIdx = ContiguousGroupLengths[0]; GroupIdx < ContiguousGroupLengths.Num(); GroupStartIdx += ContiguousGroupLengths[GroupIdx++])
|
|
{
|
|
ensure(EMeshResult::Ok == Mesh->SplitVertex(VertexID, TArrayView<const int>(TrianglesOut.GetData() + GroupStartIdx, ContiguousGroupLengths[GroupIdx]), SplitInfo));
|
|
ResultOut.NewVertices.Add(SplitInfo.NewVertex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool FDynamicMeshEditor::ReinsertSubmesh(const FDynamicSubmesh3& Region, FOptionallySparseIndexMap& SubToNewV, TArray<int>* new_tris, EDuplicateTriBehavior DuplicateBehavior)
|
|
{
|
|
check(Region.GetBaseMesh() == Mesh);
|
|
const FDynamicMesh3& Sub = Region.GetSubmesh();
|
|
bool bAllOK = true;
|
|
|
|
FIndexFlagSet done_v(Sub.MaxVertexID(), Sub.TriangleCount()/2);
|
|
SubToNewV.Initialize(Sub.MaxVertexID(), Sub.VertexCount());
|
|
|
|
int NT = Sub.MaxTriangleID();
|
|
for (int ti = 0; ti < NT; ++ti )
|
|
{
|
|
if (Sub.IsTriangle(ti) == false)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FIndex3i sub_t = Sub.GetTriangle(ti);
|
|
int gid = Sub.GetTriangleGroup(ti);
|
|
|
|
FIndex3i new_t = FIndex3i::Zero();
|
|
for ( int j = 0; j < 3; ++j )
|
|
{
|
|
int sub_v = sub_t[j];
|
|
int new_v = -1;
|
|
if (done_v[sub_v] == false)
|
|
{
|
|
// first check if this is a boundary vtx on submesh and maps to a bdry vtx on base mesh
|
|
if (Sub.IsBoundaryVertex(sub_v))
|
|
{
|
|
int base_v = Region.MapVertexToBaseMesh(sub_v);
|
|
if (base_v >= 0 && Mesh->IsVertex(base_v) && Region.InBaseBorderVertices(base_v) == true)
|
|
{
|
|
// this should always be true
|
|
if (ensure(Mesh->IsBoundaryVertex(base_v)))
|
|
{
|
|
new_v = base_v;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if that didn't happen, append new vtx
|
|
if (new_v == -1)
|
|
{
|
|
new_v = Mesh->AppendVertex(Sub, sub_v);
|
|
}
|
|
|
|
SubToNewV.Set(sub_v, new_v);
|
|
done_v.Add(sub_v);
|
|
|
|
}
|
|
else
|
|
{
|
|
new_v = SubToNewV[sub_v];
|
|
}
|
|
|
|
new_t[j] = new_v;
|
|
}
|
|
|
|
// try to handle duplicate-tri case
|
|
if (DuplicateBehavior == EDuplicateTriBehavior::EnsureContinue)
|
|
{
|
|
ensure(Mesh->FindTriangle(new_t.A, new_t.B, new_t.C) == FDynamicMesh3::InvalidID);
|
|
}
|
|
else
|
|
{
|
|
int existing_tid = Mesh->FindTriangle(new_t.A, new_t.B, new_t.C);
|
|
if (existing_tid != FDynamicMesh3::InvalidID)
|
|
{
|
|
if (DuplicateBehavior == EDuplicateTriBehavior::EnsureAbort)
|
|
{
|
|
ensure(false);
|
|
return false;
|
|
}
|
|
else if (DuplicateBehavior == EDuplicateTriBehavior::UseExisting)
|
|
{
|
|
if (new_tris)
|
|
{
|
|
new_tris->Add(existing_tid);
|
|
}
|
|
continue;
|
|
}
|
|
else if (DuplicateBehavior == EDuplicateTriBehavior::Replace)
|
|
{
|
|
Mesh->RemoveTriangle(existing_tid, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int new_tid = Mesh->AppendTriangle(new_t, gid);
|
|
ensure(new_tid >= 0);
|
|
if (!Mesh->IsTriangle(new_tid))
|
|
{
|
|
bAllOK = false;
|
|
}
|
|
|
|
if (new_tris)
|
|
{
|
|
new_tris->Add(new_tid);
|
|
}
|
|
}
|
|
|
|
return bAllOK;
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
Normalize(Normal);
|
|
}
|
|
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);
|
|
NormalTriangle1[1] = Normals->AppendElement(Normal);
|
|
NormalTriangle1[2] = Normals->AppendElement(Normal);
|
|
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);
|
|
}
|
|
else
|
|
{
|
|
NormalTriangle2[j] = NormalTriangle1[i];
|
|
}
|
|
}
|
|
Normals->SetTriangle(QuadTris.B, NormalTriangle2);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void FDynamicMeshEditor::SetTriangleNormals(const TArray<int>& Triangles, const FVector3f& Normal)
|
|
{
|
|
check(Mesh->HasAttributes());
|
|
FDynamicMeshNormalOverlay* Normals = Mesh->Attributes()->PrimaryNormals();
|
|
|
|
TMap<int, int> Vertices;
|
|
|
|
for (int tid : Triangles)
|
|
{
|
|
if (Normals->IsSetTriangle(tid))
|
|
{
|
|
Normals->UnsetTriangle(tid);
|
|
}
|
|
|
|
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);
|
|
Vertices.Add(BaseTri[j], ElemTri[j]);
|
|
}
|
|
else
|
|
{
|
|
ElemTri[j] = *FoundElementID;
|
|
}
|
|
}
|
|
Normals->SetTriangle(tid, ElemTri);
|
|
}
|
|
}
|
|
|
|
|
|
void FDynamicMeshEditor::SetTriangleNormals(const TArray<int>& Triangles)
|
|
{
|
|
check(Mesh->HasAttributes());
|
|
FDynamicMeshNormalOverlay* Normals = Mesh->Attributes()->PrimaryNormals();
|
|
|
|
TSet<int32> TriangleSet(Triangles);
|
|
TUniqueFunction<bool(int32)> TrianglePredicate = [&](int32 TriangleID) { return TriangleSet.Contains(TriangleID); };
|
|
|
|
TMap<int, int> Vertices;
|
|
|
|
for (int tid : Triangles)
|
|
{
|
|
if (Normals->IsSetTriangle(tid))
|
|
{
|
|
Normals->UnsetTriangle(tid);
|
|
}
|
|
|
|
FIndex3i BaseTri = Mesh->GetTriangle(tid);
|
|
FIndex3i ElemTri;
|
|
for (int j = 0; j < 3; ++j)
|
|
{
|
|
const int* FoundElementID = Vertices.Find(BaseTri[j]);
|
|
if (FoundElementID == nullptr)
|
|
{
|
|
FVector3d VtxROINormal = FMeshNormals::ComputeVertexNormal(*Mesh, BaseTri[j], TrianglePredicate);
|
|
ElemTri[j] = Normals->AppendElement( (FVector3f)VtxROINormal);
|
|
Vertices.Add(BaseTri[j], ElemTri[j]);
|
|
}
|
|
else
|
|
{
|
|
ElemTri[j] = *FoundElementID;
|
|
}
|
|
}
|
|
Normals->SetTriangle(tid, ElemTri);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void FDynamicMeshEditor::SetTubeNormals(const TArray<int>& Triangles, const TArray<int>& VertexIDs1, const TArray<int>& MatchedIndices1, const TArray<int>& VertexIDs2, const TArray<int>& MatchedIndices2)
|
|
{
|
|
check(Mesh->HasAttributes());
|
|
check(MatchedIndices1.Num() == MatchedIndices2.Num());
|
|
int NumMatched = MatchedIndices1.Num();
|
|
FDynamicMeshNormalOverlay* Normals = Mesh->Attributes()->PrimaryNormals();
|
|
|
|
TArray<FVector3f> MatchedEdgeNormals[2]; // matched edge normals for the two sides
|
|
MatchedEdgeNormals[0].SetNum(NumMatched);
|
|
MatchedEdgeNormals[1].SetNum(NumMatched);
|
|
for (int LastMatchedIdx = NumMatched - 1, Idx = 0; Idx < NumMatched; LastMatchedIdx = Idx++)
|
|
{
|
|
// get edge indices
|
|
int M1[2]{ MatchedIndices1[LastMatchedIdx], MatchedIndices1[Idx] };
|
|
int M2[2]{ MatchedIndices2[LastMatchedIdx], MatchedIndices2[Idx] };
|
|
// make sure they're not the same index
|
|
if (M1[0] == M1[1])
|
|
{
|
|
M1[1] = (M1[1] + 1) % VertexIDs1.Num();
|
|
}
|
|
if (M2[0] == M2[1])
|
|
{
|
|
M2[1] = (M2[1] + 1) % VertexIDs2.Num();
|
|
}
|
|
FVector3f Corners[4]{ (FVector3f)Mesh->GetVertex(VertexIDs1[M1[0]]), (FVector3f)Mesh->GetVertex(VertexIDs1[M1[1]]), (FVector3f)Mesh->GetVertex(VertexIDs2[M2[0]]), (FVector3f)Mesh->GetVertex(VertexIDs2[M2[1]]) };
|
|
FVector3f Edges[2]{ Corners[1] - Corners[0], Corners[3] - Corners[2] };
|
|
FVector3f Across = Corners[2] - Corners[0];
|
|
MatchedEdgeNormals[0][LastMatchedIdx] = Normalized(Edges[0].Cross(Across));
|
|
MatchedEdgeNormals[1][LastMatchedIdx] = Normalized(Edges[1].Cross(Across));
|
|
}
|
|
|
|
TArray<FVector3f> MatchedVertNormals[2];
|
|
MatchedVertNormals[0].SetNum(NumMatched);
|
|
MatchedVertNormals[1].SetNum(NumMatched);
|
|
TArray<FVector3f> VertNormals[2];
|
|
VertNormals[0].SetNum(VertexIDs1.Num());
|
|
VertNormals[1].SetNum(VertexIDs2.Num());
|
|
for (int LastMatchedIdx = NumMatched - 1, Idx = 0; Idx < NumMatched; LastMatchedIdx = Idx++)
|
|
{
|
|
MatchedVertNormals[0][Idx] = Normalized(MatchedEdgeNormals[0][LastMatchedIdx] + MatchedEdgeNormals[0][Idx]);
|
|
MatchedVertNormals[1][Idx] = Normalized(MatchedEdgeNormals[1][LastMatchedIdx] + MatchedEdgeNormals[1][Idx]);
|
|
}
|
|
|
|
TMap<int, int> VertToElID;
|
|
for (int Side = 0; Side < 2; Side++)
|
|
{
|
|
const TArray<int>& MatchedIndices = Side == 0 ? MatchedIndices1 : MatchedIndices2;
|
|
const TArray<int>& VertexIDs = Side == 0 ? VertexIDs1 : VertexIDs2;
|
|
int NumVertices = VertNormals[Side].Num();
|
|
for (int LastMatchedIdx = NumMatched - 1, Idx = 0; Idx < NumMatched; LastMatchedIdx = Idx++)
|
|
{
|
|
int Start = MatchedIndices[LastMatchedIdx], End = MatchedIndices[Idx];
|
|
|
|
VertNormals[Side][End] = MatchedVertNormals[Side][Idx];
|
|
if (Start != End)
|
|
{
|
|
VertNormals[Side][Start] = MatchedVertNormals[Side][LastMatchedIdx];
|
|
|
|
FVector3d StartPos = Mesh->GetVertex(VertexIDs[Start]);
|
|
FVector3d Along = Mesh->GetVertex(VertexIDs[End]) - StartPos;
|
|
double SepSq = Along.SquaredLength();
|
|
if (SepSq < KINDA_SMALL_NUMBER)
|
|
{
|
|
for (int InsideIdx = (Start + 1) % NumVertices; InsideIdx != End; InsideIdx = (InsideIdx + 1) % NumVertices)
|
|
{
|
|
VertNormals[Side][InsideIdx] = VertNormals[Side][End]; // just copy the end normal in since all the vertices are almost in the same position
|
|
}
|
|
}
|
|
for (int InsideIdx = (Start + 1) % NumVertices; InsideIdx != End; InsideIdx = (InsideIdx + 1) % NumVertices)
|
|
{
|
|
double InterpT = (Mesh->GetVertex(VertexIDs[InsideIdx]) - StartPos).Dot(Along) / SepSq;
|
|
VertNormals[Side][InsideIdx] = InterpT * VertNormals[Side][End] + (1 - InterpT)* VertNormals[Side][Start];
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int Idx = 0; Idx < NumVertices; Idx++)
|
|
{
|
|
int VID = VertexIDs[Idx];
|
|
VertToElID.Add(VID, Normals->AppendElement(VertNormals[Side][Idx]));
|
|
}
|
|
}
|
|
for (int TID : Triangles)
|
|
{
|
|
FIndex3i Tri = Mesh->GetTriangle(TID);
|
|
FIndex3i ElTri(VertToElID[Tri.A], VertToElID[Tri.B], VertToElID[Tri.C]);
|
|
Normals->SetTriangle(TID, ElTri);
|
|
}
|
|
}
|
|
|
|
|
|
void FDynamicMeshEditor::SetGeneralTubeUVs(const TArray<int>& Triangles,
|
|
const TArray<int>& VertexIDs1, const TArray<int>& MatchedIndices1, const TArray<int>& VertexIDs2, const TArray<int>& MatchedIndices2,
|
|
const TArray<float>& UValues, const FVector3f& VDir,
|
|
float UVScaleFactor, const FVector2f& UVTranslation, int UVLayerIndex)
|
|
{
|
|
// not really a valid tube if only two vertices on either side
|
|
if (!ensure(VertexIDs1.Num() >= 3 && VertexIDs2.Num() >= 3))
|
|
{
|
|
return;
|
|
}
|
|
|
|
check(Mesh->HasAttributes());
|
|
check(MatchedIndices1.Num() == MatchedIndices2.Num());
|
|
check(UValues.Num() == MatchedIndices1.Num() + 1);
|
|
int NumMatched = MatchedIndices1.Num();
|
|
|
|
FDynamicMeshUVOverlay* UVs = Mesh->Attributes()->GetUVLayer(UVLayerIndex);
|
|
|
|
FVector3d RefPos = Mesh->GetVertex(VertexIDs1[0]);
|
|
auto GetUV = [this, &VDir, &UVScaleFactor, &UVTranslation, &RefPos](int MeshIdx, float UStart, float UEnd, float Param)
|
|
{
|
|
return FVector2f((Mesh->GetVertex(MeshIdx) - RefPos).Dot((FVector3d)VDir), FMath::Lerp(UStart, UEnd, Param)) * UVScaleFactor + UVTranslation;
|
|
};
|
|
|
|
TArray<FVector2f> VertUVs[2];
|
|
VertUVs[0].SetNum(VertexIDs1.Num()+1);
|
|
VertUVs[1].SetNum(VertexIDs2.Num()+1);
|
|
|
|
TMap<int, int> VertToElID;
|
|
int DuplicateMappingForLastVert[2]{ -1, -1 }; // second element ids for the first vertices, to handle the seam at the loop
|
|
for (int Side = 0; Side < 2; Side++)
|
|
{
|
|
const TArray<int>& MatchedIndices = Side == 0 ? MatchedIndices1 : MatchedIndices2;
|
|
const TArray<int>& VertexIDs = Side == 0 ? VertexIDs1 : VertexIDs2;
|
|
int NumVertices = VertexIDs.Num();
|
|
for (int Idx = 0; Idx < NumMatched; Idx++)
|
|
{
|
|
int NextIdx = Idx + 1;
|
|
int NextIdxLooped = NextIdx % NumMatched;
|
|
bool bOnLast = NextIdx == NumMatched;
|
|
|
|
int Start = MatchedIndices[Idx], End = MatchedIndices[NextIdxLooped];
|
|
int EndUnlooped = bOnLast ? NumVertices : End;
|
|
|
|
VertUVs[Side][EndUnlooped] = GetUV(VertexIDs[End], UValues[Idx], UValues[NextIdx], 1.0f);
|
|
if (Start != End)
|
|
{
|
|
VertUVs[Side][Start] = GetUV(VertexIDs[Start], UValues[Idx], UValues[NextIdx], 0.0f);
|
|
|
|
FVector3d StartPos = Mesh->GetVertex(VertexIDs[Start]);
|
|
FVector3d Along = Mesh->GetVertex(VertexIDs[End]) - StartPos;
|
|
double SepSq = Along.SquaredLength();
|
|
if (SepSq < KINDA_SMALL_NUMBER)
|
|
{
|
|
for (int InsideIdx = (Start + 1) % NumVertices; InsideIdx != End; InsideIdx = (InsideIdx + 1) % NumVertices)
|
|
{
|
|
VertUVs[Side][InsideIdx] = VertUVs[Side][EndUnlooped]; // just copy the end normal in since all the vertices are almost in the same position
|
|
}
|
|
}
|
|
for (int InsideIdx = (Start + 1) % NumVertices; InsideIdx != End; InsideIdx = (InsideIdx + 1) % NumVertices)
|
|
{
|
|
double InterpT = (Mesh->GetVertex(VertexIDs[InsideIdx]) - StartPos).Dot(Along) / SepSq;
|
|
VertUVs[Side][InsideIdx] = GetUV(VertexIDs[InsideIdx], UValues[Idx], UValues[NextIdx], InterpT);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int Idx = 0; Idx < NumVertices; Idx++)
|
|
{
|
|
int VID = VertexIDs[Idx];
|
|
VertToElID.Add(VID, UVs->AppendElement(VertUVs[Side][Idx]));
|
|
}
|
|
DuplicateMappingForLastVert[Side] = UVs->AppendElement(VertUVs[Side].Last());
|
|
}
|
|
|
|
bool bPastInitialVertices[2]{ false, false };
|
|
int FirstVID[2] = { VertexIDs1[0], VertexIDs2[0] };
|
|
for (int TriIdx = 0; TriIdx < Triangles.Num(); TriIdx++)
|
|
{
|
|
int TID = Triangles[TriIdx];
|
|
FIndex3i Tri = Mesh->GetTriangle(TID);
|
|
FIndex3i ElTri(VertToElID[Tri.A], VertToElID[Tri.B], VertToElID[Tri.C]);
|
|
|
|
// hacky special handling for the seam at the end of the loop -- the second time we see the start vertices, switch to the end seam elements
|
|
for (int Side = 0; Side < 2; Side++)
|
|
{
|
|
int FirstVIDSubIdx = Tri.IndexOf(FirstVID[Side]);
|
|
bPastInitialVertices[Side] = bPastInitialVertices[Side] || FirstVIDSubIdx == -1;
|
|
if (bPastInitialVertices[Side] && FirstVIDSubIdx >= 0)
|
|
{
|
|
ElTri[FirstVIDSubIdx] = DuplicateMappingForLastVert[Side];
|
|
}
|
|
}
|
|
UVs->SetTriangle(TID, ElTri);
|
|
}
|
|
}
|
|
|
|
|
|
void FDynamicMeshEditor::SetTriangleUVsFromProjection(const TArray<int>& Triangles, const FFrame3d& ProjectionFrame, float UVScaleFactor, const FVector2f& UVTranslation, bool bShiftToOrigin, int UVLayerIndex)
|
|
{
|
|
SetTriangleUVsFromProjection(Triangles, ProjectionFrame, FVector2f(UVScaleFactor, UVScaleFactor), UVTranslation, UVLayerIndex, bShiftToOrigin, false);
|
|
}
|
|
|
|
void FDynamicMeshEditor::SetTriangleUVsFromProjection(const TArray<int>& Triangles, const FFrame3d& ProjectionFrame, const FVector2f& UVScale,
|
|
const FVector2f& UVTranslation, int UVLayerIndex, bool bShiftToOrigin, bool bNormalizeBeforeScaling)
|
|
{
|
|
if (!Triangles.Num())
|
|
{
|
|
return;
|
|
}
|
|
|
|
check(Mesh->HasAttributes() && Mesh->Attributes()->NumUVLayers() > UVLayerIndex);
|
|
FDynamicMeshUVOverlay* UVs = Mesh->Attributes()->GetUVLayer(UVLayerIndex);
|
|
|
|
TMap<int, int> BaseToOverlayVIDMap;
|
|
TArray<int> AllUVIndices;
|
|
|
|
FAxisAlignedBox2f UVBounds(FAxisAlignedBox2f::Empty());
|
|
|
|
for (int TID : Triangles)
|
|
{
|
|
if (UVs->IsSetTriangle(TID))
|
|
{
|
|
UVs->UnsetTriangle(TID);
|
|
}
|
|
|
|
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);
|
|
AllUVIndices.Add(ElemTri[j]);
|
|
BaseToOverlayVIDMap.Add(BaseTri[j], ElemTri[j]);
|
|
}
|
|
else
|
|
{
|
|
ElemTri[j] = *FoundElementID;
|
|
}
|
|
}
|
|
UVs->SetTriangle(TID, ElemTri);
|
|
}
|
|
|
|
FVector2f UvScaleToUse = bNormalizeBeforeScaling ? FVector2f(UVScale[0] / UVBounds.Width(), UVScale[1] / UVBounds.Height())
|
|
: UVScale;
|
|
|
|
// 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 = (bShiftToOrigin) ? ((UV - UVBounds.Min) * UvScaleToUse) : (UV * UvScaleToUse);
|
|
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);
|
|
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);
|
|
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<FTransformSRT3d> 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())
|
|
{
|
|
if (!UVs->IsSetTriangle(TID))
|
|
{
|
|
continue;
|
|
}
|
|
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 += Distance(TriUVs[j], TriUVs[i]);
|
|
TotalEdgeLen += Distance(TriVs[j], 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<int>& Triangles, bool bInvertNormals)
|
|
{
|
|
for (int tid : Triangles)
|
|
{
|
|
Mesh->ReverseTriOrientation(tid);
|
|
}
|
|
if (bInvertNormals)
|
|
{
|
|
InvertTriangleNormals(Triangles);
|
|
}
|
|
}
|
|
|
|
|
|
void FDynamicMeshEditor::InvertTriangleNormals(const TArray<int>& Triangles)
|
|
{
|
|
// @todo re-use the TBitA
|
|
|
|
if (Mesh->HasVertexNormals())
|
|
{
|
|
TBitArray<FDefaultBitArrayAllocator> 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 (int NormalLayerIndex = 0; NormalLayerIndex < Mesh->Attributes()->NumNormalLayers(); NormalLayerIndex++)
|
|
{
|
|
FDynamicMeshNormalOverlay* NormalOverlay = Mesh->Attributes()->GetNormalLayer(NormalLayerIndex);
|
|
TBitArray<FDefaultBitArrayAllocator> DoneNormals(false, NormalOverlay->MaxElementID());
|
|
for (int TriangleID : Triangles)
|
|
{
|
|
FIndex3i ElemTri = NormalOverlay->GetTriangle(TriangleID);
|
|
for (int j = 0; j < 3; ++j)
|
|
{
|
|
if (NormalOverlay->IsElement(ElemTri[j]) && DoneNormals[ElemTri[j]] == false)
|
|
{
|
|
NormalOverlay->SetElement(ElemTri[j], -NormalOverlay->GetElement(ElemTri[j]));
|
|
DoneNormals[ElemTri[j]] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void FDynamicMeshEditor::CopyAttributes(int FromTriangleID, int ToTriangleID, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut)
|
|
{
|
|
if (Mesh->HasAttributes() == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
for (int UVLayerIndex = 0; UVLayerIndex < Mesh->Attributes()->NumUVLayers(); UVLayerIndex++)
|
|
{
|
|
FDynamicMeshUVOverlay* UVOverlay = Mesh->Attributes()->GetUVLayer(UVLayerIndex);
|
|
if (UVOverlay->IsSetTriangle(FromTriangleID))
|
|
{
|
|
FIndex3i FromElemTri = UVOverlay->GetTriangle(FromTriangleID);
|
|
FIndex3i ToElemTri = UVOverlay->GetTriangle(ToTriangleID);
|
|
for (int j = 0; j < 3; ++j)
|
|
{
|
|
int NewElemID = FindOrCreateDuplicateUV(FromElemTri[j], UVLayerIndex, IndexMaps);
|
|
ToElemTri[j] = NewElemID;
|
|
}
|
|
UVOverlay->SetTriangle(ToTriangleID, ToElemTri);
|
|
}
|
|
}
|
|
|
|
// Make sure the storage in NewNormalOverlayElements has a slot for each normal layer.
|
|
if (ResultOut.NewNormalOverlayElements.Num() < Mesh->Attributes()->NumNormalLayers())
|
|
{
|
|
ResultOut.NewNormalOverlayElements.AddDefaulted(Mesh->Attributes()->NumNormalLayers() - ResultOut.NewNormalOverlayElements.Num());
|
|
}
|
|
|
|
for (int NormalLayerIndex = 0; NormalLayerIndex < Mesh->Attributes()->NumNormalLayers(); NormalLayerIndex++)
|
|
{
|
|
FDynamicMeshNormalOverlay* NormalOverlay = Mesh->Attributes()->GetNormalLayer(NormalLayerIndex);
|
|
|
|
if (NormalOverlay->IsSetTriangle(FromTriangleID))
|
|
{
|
|
FIndex3i FromElemTri = NormalOverlay->GetTriangle(FromTriangleID);
|
|
FIndex3i ToElemTri = NormalOverlay->GetTriangle(ToTriangleID);
|
|
for (int j = 0; j < 3; ++j)
|
|
{
|
|
int NewElemID = FindOrCreateDuplicateNormal(FromElemTri[j], NormalLayerIndex, IndexMaps, &ResultOut);
|
|
ToElemTri[j] = NewElemID;
|
|
}
|
|
NormalOverlay->SetTriangle(ToTriangleID, ToElemTri);
|
|
}
|
|
}
|
|
|
|
if (Mesh->Attributes()->HasPrimaryColors())
|
|
{
|
|
FDynamicMeshColorOverlay* ColorOverlay = Mesh->Attributes()->PrimaryColors();
|
|
if (ColorOverlay->IsSetTriangle(FromTriangleID))
|
|
{
|
|
FIndex3i FromElemTri = ColorOverlay->GetTriangle(FromTriangleID);
|
|
FIndex3i ToElemTri = ColorOverlay->GetTriangle(ToTriangleID);
|
|
for (int j = 0; j < 3; ++j)
|
|
{
|
|
int NewElemID = FindOrCreateDuplicateColor(FromElemTri[j], IndexMaps, &ResultOut);
|
|
ToElemTri[j] = NewElemID;
|
|
}
|
|
ColorOverlay->SetTriangle(ToTriangleID, ToElemTri);
|
|
}
|
|
}
|
|
|
|
if (Mesh->Attributes()->HasMaterialID())
|
|
{
|
|
FDynamicMeshMaterialAttribute* MaterialIDs = Mesh->Attributes()->GetMaterialID();
|
|
MaterialIDs->SetValue(ToTriangleID, MaterialIDs->GetValue(FromTriangleID));
|
|
}
|
|
|
|
for (int PolygroupLayerIndex = 0; PolygroupLayerIndex < Mesh->Attributes()->NumPolygroupLayers(); PolygroupLayerIndex++)
|
|
{
|
|
FDynamicMeshPolygroupAttribute* Polygroup = Mesh->Attributes()->GetPolygroupLayer(PolygroupLayerIndex);
|
|
Polygroup->SetValue(ToTriangleID, Polygroup->GetValue(FromTriangleID));
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
NewElementID = UVOverlay->AppendElement(UVOverlay->GetElement(ElementID));
|
|
IndexMaps.SetUV(UVLayerIndex, ElementID, NewElementID);
|
|
}
|
|
return NewElementID;
|
|
}
|
|
|
|
|
|
|
|
int FDynamicMeshEditor::FindOrCreateDuplicateNormal(int ElementID, int NormalLayerIndex, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult* ResultOut)
|
|
{
|
|
int NewElementID = IndexMaps.GetNewNormal(NormalLayerIndex, ElementID);
|
|
if (NewElementID == IndexMaps.InvalidID())
|
|
{
|
|
FDynamicMeshNormalOverlay* NormalOverlay = Mesh->Attributes()->GetNormalLayer(NormalLayerIndex);
|
|
NewElementID = NormalOverlay->AppendElement(NormalOverlay->GetElement(ElementID));
|
|
IndexMaps.SetNormal(NormalLayerIndex, ElementID, NewElementID);
|
|
if (ResultOut)
|
|
{
|
|
check(ResultOut->NewNormalOverlayElements.Num() > NormalLayerIndex);
|
|
ResultOut->NewNormalOverlayElements[NormalLayerIndex].Add(NewElementID);
|
|
}
|
|
}
|
|
return NewElementID;
|
|
}
|
|
|
|
|
|
int FDynamicMeshEditor::FindOrCreateDuplicateColor(int ElementID, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult* ResultOut)
|
|
{
|
|
int NewElementID = IndexMaps.GetNewColor(ElementID);
|
|
if (NewElementID == IndexMaps.InvalidID())
|
|
{
|
|
FDynamicMeshColorOverlay* ColorOverlay = Mesh->Attributes()->PrimaryColors();
|
|
NewElementID = ColorOverlay->AppendElement(ColorOverlay->GetElement(ElementID));
|
|
IndexMaps.SetColor(ElementID, NewElementID);
|
|
if (ResultOut)
|
|
{
|
|
ResultOut->NewColorOverlayElements.Add(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);
|
|
|
|
if (Mesh->HasAttributes())
|
|
{
|
|
for (int WeightLayerIndex = 0; WeightLayerIndex < Mesh->Attributes()->NumWeightLayers(); ++WeightLayerIndex)
|
|
{
|
|
FDynamicMeshWeightAttribute* WeightAttr = Mesh->Attributes()->GetWeightLayer(WeightLayerIndex);
|
|
float Val;
|
|
WeightAttr->GetValue(VertexID, &Val);
|
|
WeightAttr->SetNewValue(NewVertexID, &Val);
|
|
}
|
|
}
|
|
}
|
|
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<FVector3d(int, const FVector3d&)> PositionTransform,
|
|
TFunction<FVector3d(int, const FVector3d&)> NormalTransform)
|
|
{
|
|
// todo: handle this case by making a copy?
|
|
check(AppendMesh != Mesh);
|
|
|
|
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->HasVertexUVs() && Mesh->HasVertexUVs())
|
|
{
|
|
FVector2f UV = AppendMesh->GetVertexUV(VertID);
|
|
Mesh->SetVertexUV(NewVertID, UV);
|
|
}
|
|
|
|
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);
|
|
if (ensure(NewTriID >= 0))
|
|
{
|
|
TriangleMap.Add(TriID, NewTriID);
|
|
}
|
|
}
|
|
|
|
|
|
// @todo can we have a template fn that does this?
|
|
|
|
if (AppendMesh->HasAttributes() && Mesh->HasAttributes())
|
|
{
|
|
int NumNormalLayers = FMath::Min(Mesh->Attributes()->NumNormalLayers(), AppendMesh->Attributes()->NumNormalLayers());
|
|
for (int NormalLayerIndex = 0; NormalLayerIndex < NumNormalLayers; NormalLayerIndex++)
|
|
{
|
|
const FDynamicMeshNormalOverlay* FromNormals = AppendMesh->Attributes()->GetNormalLayer(NormalLayerIndex);
|
|
FDynamicMeshNormalOverlay* ToNormals = Mesh->Attributes()->GetNormalLayer(NormalLayerIndex);
|
|
if (FromNormals != nullptr && ToNormals != nullptr)
|
|
{
|
|
FIndexMapi& NormalMap = IndexMapsOut.GetNormalMap(NormalLayerIndex);
|
|
NormalMap.Reserve(FromNormals->ElementCount());
|
|
AppendNormals(AppendMesh, FromNormals, ToNormals,
|
|
VertexMap, TriangleMap, NormalTransform, NormalMap);
|
|
}
|
|
}
|
|
|
|
int NumUVLayers = FMath::Min(Mesh->Attributes()->NumUVLayers(), AppendMesh->Attributes()->NumUVLayers());
|
|
for (int UVLayerIndex = 0; UVLayerIndex < NumUVLayers; UVLayerIndex++)
|
|
{
|
|
const FDynamicMeshUVOverlay* FromUVs = AppendMesh->Attributes()->GetUVLayer(UVLayerIndex);
|
|
FDynamicMeshUVOverlay* ToUVs = Mesh->Attributes()->GetUVLayer(UVLayerIndex);
|
|
if (FromUVs != nullptr && ToUVs != nullptr)
|
|
{
|
|
FIndexMapi& UVMap = IndexMapsOut.GetUVMap(UVLayerIndex);
|
|
UVMap.Reserve(FromUVs->ElementCount());
|
|
AppendUVs(AppendMesh, FromUVs, ToUVs,
|
|
VertexMap, TriangleMap, UVMap);
|
|
}
|
|
}
|
|
|
|
if (AppendMesh->Attributes()->HasPrimaryColors() && Mesh->Attributes()->HasPrimaryColors())
|
|
{
|
|
const FDynamicMeshColorOverlay* FromColors = AppendMesh->Attributes()->PrimaryColors();
|
|
FDynamicMeshColorOverlay* ToColors = Mesh->Attributes()->PrimaryColors();
|
|
if (FromColors != nullptr && ToColors != nullptr)
|
|
{
|
|
FIndexMapi& ColorMap = IndexMapsOut.GetColorMap();
|
|
ColorMap.Reserve(FromColors->ElementCount());
|
|
AppendColors(AppendMesh, FromColors, ToColors,
|
|
VertexMap, TriangleMap, ColorMap);
|
|
}
|
|
}
|
|
|
|
if (AppendMesh->Attributes()->HasMaterialID() && Mesh->Attributes()->HasMaterialID())
|
|
{
|
|
const FDynamicMeshMaterialAttribute* FromMaterialIDs = AppendMesh->Attributes()->GetMaterialID();
|
|
FDynamicMeshMaterialAttribute* ToMaterialIDs = Mesh->Attributes()->GetMaterialID();
|
|
for (const TPair<int32, int32>& MapTID : TriangleMap.GetForwardMap())
|
|
{
|
|
ToMaterialIDs->SetValue(MapTID.Value, FromMaterialIDs->GetValue(MapTID.Key));
|
|
}
|
|
}
|
|
|
|
int NumPolygroupLayers = FMath::Min(Mesh->Attributes()->NumPolygroupLayers(), AppendMesh->Attributes()->NumPolygroupLayers());
|
|
for (int PolygroupLayerIndex = 0; PolygroupLayerIndex < NumPolygroupLayers; PolygroupLayerIndex++)
|
|
{
|
|
// TODO: remap groups? this will be somewhat expensive...
|
|
const FDynamicMeshPolygroupAttribute* FromPolygroups = AppendMesh->Attributes()->GetPolygroupLayer(PolygroupLayerIndex);
|
|
FDynamicMeshPolygroupAttribute* ToPolygroups = Mesh->Attributes()->GetPolygroupLayer(PolygroupLayerIndex);
|
|
for (const TPair<int32, int32>& MapTID : TriangleMap.GetForwardMap())
|
|
{
|
|
ToPolygroups->SetValue(MapTID.Value, FromPolygroups->GetValue(MapTID.Key));
|
|
}
|
|
}
|
|
|
|
int NumWeightLayers = FMath::Min(Mesh->Attributes()->NumWeightLayers(), AppendMesh->Attributes()->NumWeightLayers());
|
|
for (int WeightLayerIndex = 0; WeightLayerIndex < NumWeightLayers; WeightLayerIndex++)
|
|
{
|
|
const FDynamicMeshWeightAttribute* FromWeights = AppendMesh->Attributes()->GetWeightLayer(WeightLayerIndex);
|
|
FDynamicMeshWeightAttribute* ToWeights = Mesh->Attributes()->GetWeightLayer(WeightLayerIndex);
|
|
for (const TPair<int32, int32>& MapVID : VertexMap.GetForwardMap())
|
|
{
|
|
float Weight;
|
|
FromWeights->GetValue(MapVID.Key, &Weight);
|
|
ToWeights->SetValue(MapVID.Value, &Weight);
|
|
}
|
|
}
|
|
|
|
for (const TPair<FName, TUniquePtr<FDynamicMeshVertexSkinWeightsAttribute>>& AttribPair : AppendMesh->Attributes()->GetSkinWeightsAttributes())
|
|
{
|
|
FDynamicMeshVertexSkinWeightsAttribute* ToAttrib = Mesh->Attributes()->GetSkinWeightsAttribute(AttribPair.Key);
|
|
if (ToAttrib)
|
|
{
|
|
ToAttrib->CopyThroughMapping(AttribPair.Value.Get(), IndexMapsOut);
|
|
}
|
|
}
|
|
|
|
for (const TPair<FName, TUniquePtr<FDynamicMeshAttributeBase>>& AttribPair : AppendMesh->Attributes()->GetAttachedAttributes())
|
|
{
|
|
if (Mesh->Attributes()->HasAttachedAttribute(AttribPair.Key))
|
|
{
|
|
FDynamicMeshAttributeBase* ToAttrib = Mesh->Attributes()->GetAttachedAttribute(AttribPair.Key);
|
|
ToAttrib->CopyThroughMapping(AttribPair.Value.Get(), IndexMapsOut);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void FDynamicMeshEditor::AppendMesh(const TTriangleMeshAdapter<double>* AppendMesh,
|
|
FMeshIndexMappings& IndexMapsOut,
|
|
TFunction<FVector3d(int, const FVector3d&)> PositionTransform)
|
|
{
|
|
IndexMapsOut.Reset();
|
|
//IndexMapsOut.Initialize(Mesh); // not supported
|
|
|
|
FIndexMapi& VertexMap = IndexMapsOut.GetVertexMap();
|
|
VertexMap.Reserve(AppendMesh->VertexCount());
|
|
int32 MaxVertexID = AppendMesh->MaxVertexID();
|
|
for ( int32 VertID = 0; VertID < MaxVertexID; ++VertID )
|
|
{
|
|
if (AppendMesh->IsVertex(VertID) == false) continue;
|
|
|
|
FVector3d Position = AppendMesh->GetVertex(VertID);
|
|
if (PositionTransform != nullptr)
|
|
{
|
|
Position = PositionTransform(VertID, Position);
|
|
}
|
|
int NewVertID = Mesh->AppendVertex(Position);
|
|
VertexMap.Add(VertID, NewVertID);
|
|
}
|
|
|
|
// set face normals if they exist
|
|
FDynamicMeshNormalOverlay* SetNormals = Mesh->HasAttributes() ? Mesh->Attributes()->PrimaryNormals() : nullptr;
|
|
|
|
FIndexMapi& TriangleMap = IndexMapsOut.GetTriangleMap();
|
|
int32 MaxTriangleID = AppendMesh->MaxTriangleID();
|
|
for (int TriID = 0; TriID < MaxTriangleID; ++TriID )
|
|
{
|
|
if (AppendMesh->IsTriangle(TriID) == false) continue;
|
|
|
|
int GroupID = FDynamicMesh3::InvalidID;
|
|
FIndex3i Tri = AppendMesh->GetTriangle(TriID);
|
|
FIndex3i NewTri = FIndex3i(VertexMap.GetTo(Tri.A), VertexMap.GetTo(Tri.B), VertexMap.GetTo(Tri.C));
|
|
int NewTriID = Mesh->AppendTriangle(NewTri.A, NewTri.B, NewTri.C, GroupID);
|
|
TriangleMap.Add(TriID, NewTriID);
|
|
|
|
if (SetNormals)
|
|
{
|
|
FVector3f TriNormal(Mesh->GetTriNormal(NewTriID)); //LWC_TODO: Precision loss
|
|
FIndex3i NormalTri;
|
|
for (int32 j = 0; j < 3; ++j)
|
|
{
|
|
NormalTri[j] = SetNormals->AppendElement(TriNormal);
|
|
SetNormals->SetParentVertex(NormalTri[j], NewTri[j]);
|
|
}
|
|
SetNormals->SetTriangle(NewTriID, NormalTri);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void FDynamicMeshEditor::AppendNormals(const FDynamicMesh3* AppendMesh,
|
|
const FDynamicMeshNormalOverlay* FromNormals, FDynamicMeshNormalOverlay* ToNormals,
|
|
const FIndexMapi& VertexMap, const FIndexMapi& TriangleMap,
|
|
TFunction<FVector3d(int, const FVector3d&)> 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);
|
|
NormalMapOut.Add(ElemID, NewElemID);
|
|
}
|
|
|
|
// now set new triangles
|
|
for (const TPair<int32, int32>& MapTID : TriangleMap.GetForwardMap())
|
|
{
|
|
if (FromNormals->IsSetTriangle(MapTID.Key))
|
|
{
|
|
FIndex3i ElemTri = FromNormals->GetTriangle(MapTID.Key);
|
|
for (int j = 0; j < 3; ++j)
|
|
{
|
|
ElemTri[j] = FromNormals->IsElement(ElemTri[j]) ? NormalMapOut.GetTo(ElemTri[j]) : FDynamicMesh3::InvalidID;
|
|
}
|
|
ToNormals->SetTriangle(MapTID.Value, 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())
|
|
{
|
|
FVector2f UV = FromUVs->GetElement(ElemID);
|
|
int NewElemID = ToUVs->AppendElement(UV);
|
|
UVMapOut.Add(ElemID, NewElemID);
|
|
}
|
|
|
|
// now set new triangles
|
|
for (const TPair<int32, int32>& MapTID : TriangleMap.GetForwardMap())
|
|
{
|
|
if (FromUVs->IsSetTriangle(MapTID.Key))
|
|
{
|
|
FIndex3i ElemTri = FromUVs->GetTriangle(MapTID.Key);
|
|
for (int j = 0; j < 3; ++j)
|
|
{
|
|
ElemTri[j] = FromUVs->IsElement(ElemTri[j]) ? UVMapOut.GetTo(ElemTri[j]) : FDynamicMesh3::InvalidID;
|
|
}
|
|
ToUVs->SetTriangle(MapTID.Value, ElemTri);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FDynamicMeshEditor::AppendColors(const FDynamicMesh3* AppendMesh,
|
|
const FDynamicMeshColorOverlay* FromOverlay, FDynamicMeshColorOverlay* ToOverlay,
|
|
const FIndexMapi& VertexMap, const FIndexMapi& TriangleMap,
|
|
FIndexMapi& MapOut)
|
|
{
|
|
// copy over color elements
|
|
for (int ElemID : FromOverlay->ElementIndicesItr())
|
|
{
|
|
int NewElemID = ToOverlay->AppendElement(FromOverlay->GetElement(ElemID));
|
|
MapOut.Add(ElemID, NewElemID);
|
|
}
|
|
|
|
// now set new triangles
|
|
for (const TPair<int32, int32>& MapTID : TriangleMap.GetForwardMap())
|
|
{
|
|
if (FromOverlay->IsSetTriangle(MapTID.Key))
|
|
{
|
|
FIndex3i ElemTri = FromOverlay->GetTriangle(MapTID.Key);
|
|
for (int j = 0; j < 3; ++j)
|
|
{
|
|
ElemTri[j] = FromOverlay->IsElement(ElemTri[j]) ? MapOut.GetTo(ElemTri[j]) : FDynamicMesh3::InvalidID;
|
|
}
|
|
ToOverlay->SetTriangle(MapTID.Value, ElemTri);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// can these be replaced w/ template function?
|
|
|
|
|
|
namespace UE
|
|
{
|
|
namespace DynamicMeshEditorInternals
|
|
{
|
|
|
|
|
|
// 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);
|
|
NewElementID = ToUVOverlay->AppendElement(FromUVOverlay->GetElement(FromElementID));
|
|
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);
|
|
NewElementID = ToNormalOverlay->AppendElement(FromNormalOverlay->GetElement(FromElementID));
|
|
IndexMaps.SetNormal(NormalLayerIndex, FromElementID, NewElementID);
|
|
}
|
|
return NewElementID;
|
|
}
|
|
|
|
// Utility function for ::AppendTriangles()
|
|
static int AppendTriangleColorAttribute(const FDynamicMesh3* FromMesh, int FromElementID, FDynamicMesh3* ToMesh, FMeshIndexMappings& IndexMaps)
|
|
{
|
|
int NewElementID = IndexMaps.GetNewColor(FromElementID);
|
|
if (NewElementID == IndexMaps.InvalidID())
|
|
{
|
|
const FDynamicMeshColorOverlay* FromOverlay = FromMesh->Attributes()->PrimaryColors();
|
|
FDynamicMeshColorOverlay* ToOverlay = ToMesh->Attributes()->PrimaryColors();
|
|
NewElementID = ToOverlay->AppendElement(FromOverlay->GetElement(FromElementID));
|
|
IndexMaps.SetColor(FromElementID, NewElementID);
|
|
}
|
|
return NewElementID;
|
|
}
|
|
|
|
|
|
|
|
// Utility function for ::AppendTriangles()
|
|
static void AppendTriangleAttributes(const FDynamicMesh3* FromMesh, int FromTriangleID, FDynamicMesh3* ToMesh, int ToTriangleID, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut)
|
|
{
|
|
if (FromMesh->HasAttributes() == false || ToMesh->HasAttributes() == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
for (int UVLayerIndex = 0; UVLayerIndex < FMath::Min(FromMesh->Attributes()->NumUVLayers(), ToMesh->Attributes()->NumUVLayers()); UVLayerIndex++)
|
|
{
|
|
const FDynamicMeshUVOverlay* FromUVOverlay = FromMesh->Attributes()->GetUVLayer(UVLayerIndex);
|
|
FDynamicMeshUVOverlay* ToUVOverlay = ToMesh->Attributes()->GetUVLayer(UVLayerIndex);
|
|
if (FromUVOverlay->IsSetTriangle(FromTriangleID))
|
|
{
|
|
FIndex3i FromElemTri = FromUVOverlay->GetTriangle(FromTriangleID);
|
|
FIndex3i ToElemTri = ToUVOverlay->GetTriangle(ToTriangleID);
|
|
for (int j = 0; j < 3; ++j)
|
|
{
|
|
check(FromElemTri[j] != FDynamicMesh3::InvalidID);
|
|
int NewElemID = AppendTriangleUVAttribute(FromMesh, FromElemTri[j], ToMesh, UVLayerIndex, IndexMaps);
|
|
ToElemTri[j] = NewElemID;
|
|
}
|
|
ToUVOverlay->SetTriangle(ToTriangleID, ToElemTri);
|
|
}
|
|
}
|
|
|
|
|
|
for (int NormalLayerIndex = 0; NormalLayerIndex < FMath::Min(FromMesh->Attributes()->NumNormalLayers(), ToMesh->Attributes()->NumNormalLayers()); NormalLayerIndex++)
|
|
{
|
|
const FDynamicMeshNormalOverlay* FromNormalOverlay = FromMesh->Attributes()->GetNormalLayer(NormalLayerIndex);
|
|
FDynamicMeshNormalOverlay* ToNormalOverlay = ToMesh->Attributes()->GetNormalLayer(NormalLayerIndex);
|
|
if (FromNormalOverlay->IsSetTriangle(FromTriangleID))
|
|
{
|
|
FIndex3i FromElemTri = FromNormalOverlay->GetTriangle(FromTriangleID);
|
|
FIndex3i ToElemTri = ToNormalOverlay->GetTriangle(ToTriangleID);
|
|
for (int j = 0; j < 3; ++j)
|
|
{
|
|
check(FromElemTri[j] != FDynamicMesh3::InvalidID);
|
|
int NewElemID = AppendTriangleNormalAttribute(FromMesh, FromElemTri[j], ToMesh, NormalLayerIndex, IndexMaps);
|
|
ToElemTri[j] = NewElemID;
|
|
}
|
|
ToNormalOverlay->SetTriangle(ToTriangleID, ToElemTri);
|
|
}
|
|
}
|
|
|
|
if (FromMesh->Attributes()->HasPrimaryColors() && ToMesh->Attributes()->HasPrimaryColors())
|
|
{
|
|
const FDynamicMeshColorOverlay* FromOverlay = FromMesh->Attributes()->PrimaryColors();
|
|
FDynamicMeshColorOverlay* ToOverlay = ToMesh->Attributes()->PrimaryColors();
|
|
if (FromOverlay->IsSetTriangle(FromTriangleID))
|
|
{
|
|
FIndex3i FromElemTri = FromOverlay->GetTriangle(FromTriangleID);
|
|
FIndex3i ToElemTri = ToOverlay->GetTriangle(ToTriangleID);
|
|
for (int j = 0; j < 3; ++j)
|
|
{
|
|
check(FromElemTri[j] != FDynamicMesh3::InvalidID);
|
|
int NewElemID = AppendTriangleColorAttribute(FromMesh, FromElemTri[j], ToMesh, IndexMaps);
|
|
ToElemTri[j] = NewElemID;
|
|
}
|
|
ToOverlay->SetTriangle(ToTriangleID, ToElemTri);
|
|
}
|
|
}
|
|
|
|
if (FromMesh->Attributes()->HasMaterialID() && ToMesh->Attributes()->HasMaterialID())
|
|
{
|
|
const FDynamicMeshMaterialAttribute* FromMaterialIDs = FromMesh->Attributes()->GetMaterialID();
|
|
FDynamicMeshMaterialAttribute* ToMaterialIDs = ToMesh->Attributes()->GetMaterialID();
|
|
ToMaterialIDs->SetValue(ToTriangleID, FromMaterialIDs->GetValue(FromTriangleID));
|
|
}
|
|
|
|
int NumPolygroupLayers = FMath::Min(FromMesh->Attributes()->NumPolygroupLayers(), ToMesh->Attributes()->NumPolygroupLayers());
|
|
for (int PolygroupLayerIndex = 0; PolygroupLayerIndex < NumPolygroupLayers; PolygroupLayerIndex++)
|
|
{
|
|
// TODO: remap groups? this will be somewhat expensive...
|
|
const FDynamicMeshPolygroupAttribute* FromPolygroups = FromMesh->Attributes()->GetPolygroupLayer(PolygroupLayerIndex);
|
|
FDynamicMeshPolygroupAttribute* ToPolygroups = ToMesh->Attributes()->GetPolygroupLayer(PolygroupLayerIndex);
|
|
ToPolygroups->SetValue(ToTriangleID, FromPolygroups->GetValue(FromTriangleID));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Utility function for ::AppendTriangles()
|
|
static void AppendVertexAttributes(const FDynamicMesh3* FromMesh, FDynamicMesh3* ToMesh, FMeshIndexMappings& IndexMaps)
|
|
{
|
|
|
|
if (FromMesh->HasAttributes() == false || ToMesh->HasAttributes() == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int NumWeightLayers = FMath::Min(FromMesh->Attributes()->NumWeightLayers(), ToMesh->Attributes()->NumWeightLayers());
|
|
for (int WeightLayerIndex = 0; WeightLayerIndex < NumWeightLayers; WeightLayerIndex++)
|
|
{
|
|
const FDynamicMeshWeightAttribute* FromWeights = FromMesh->Attributes()->GetWeightLayer(WeightLayerIndex);
|
|
FDynamicMeshWeightAttribute* ToWeights = ToMesh->Attributes()->GetWeightLayer(WeightLayerIndex);
|
|
for (const TPair<int32, int32>& MapVID : IndexMaps.GetVertexMap().GetForwardMap())
|
|
{
|
|
float Weight;
|
|
FromWeights->GetValue(MapVID.Key, &Weight);
|
|
ToWeights->SetValue(MapVID.Value, &Weight);
|
|
}
|
|
}
|
|
|
|
// Copy skin weight and generic attributes after full IndexMaps have been created.
|
|
for (const TPair<FName, TUniquePtr<FDynamicMeshVertexSkinWeightsAttribute>>& AttribPair : FromMesh->Attributes()->GetSkinWeightsAttributes())
|
|
{
|
|
FDynamicMeshVertexSkinWeightsAttribute* ToAttrib = ToMesh->Attributes()->GetSkinWeightsAttribute(AttribPair.Key);
|
|
if (ToAttrib)
|
|
{
|
|
ToAttrib->CopyThroughMapping(AttribPair.Value.Get(), IndexMaps);
|
|
}
|
|
}
|
|
|
|
for (const TPair<FName, TUniquePtr<FDynamicMeshAttributeBase>>& AttribPair : FromMesh->Attributes()->GetAttachedAttributes())
|
|
{
|
|
if (ToMesh->Attributes()->HasAttachedAttribute(AttribPair.Key))
|
|
{
|
|
FDynamicMeshAttributeBase* ToAttrib = ToMesh->Attributes()->GetAttachedAttribute(AttribPair.Key);
|
|
ToAttrib->CopyThroughMapping(AttribPair.Value.Get(), IndexMaps);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}} // namespace UE::DynamicMeshEditorInternals
|
|
|
|
void FDynamicMeshEditor::AppendTriangles(const FDynamicMesh3* SourceMesh, const TArrayView<const int>& SourceTriangles, FMeshIndexMappings& IndexMaps, FDynamicMeshEditResult& ResultOut, bool bComputeTriangleMap)
|
|
{
|
|
using namespace UE::DynamicMeshEditorInternals;
|
|
|
|
ResultOut.Reset();
|
|
IndexMaps.Initialize(Mesh);
|
|
|
|
int DefaultGroupID = FDynamicMesh3::InvalidID;
|
|
for (int SourceTriangleID : SourceTriangles)
|
|
{
|
|
check(SourceMesh->IsTriangle(SourceTriangleID));
|
|
if (SourceMesh->IsTriangle(SourceTriangleID) == false)
|
|
{
|
|
continue; // ignore missing triangles
|
|
}
|
|
|
|
FIndex3i Tri = SourceMesh->GetTriangle(SourceTriangleID);
|
|
|
|
// FindOrCreateDuplicateGroup
|
|
int NewGroupID = FDynamicMesh3::InvalidID;
|
|
if (Mesh->HasTriangleGroups())
|
|
{
|
|
if (SourceMesh->HasTriangleGroups())
|
|
{
|
|
int SourceGroupID = SourceMesh->GetTriangleGroup(SourceTriangleID);
|
|
if (SourceGroupID >= 0)
|
|
{
|
|
NewGroupID = IndexMaps.GetNewGroup(SourceGroupID);
|
|
if (NewGroupID == IndexMaps.InvalidID())
|
|
{
|
|
NewGroupID = Mesh->AllocateTriangleGroup();
|
|
IndexMaps.SetGroup(SourceGroupID, NewGroupID);
|
|
ResultOut.NewGroups.Add(NewGroupID);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If the source mesh does not have triangle groups, but the destination
|
|
// mesh does, create a default group for all triangles.
|
|
if (DefaultGroupID == FDynamicMesh3::InvalidID)
|
|
{
|
|
DefaultGroupID = Mesh->AllocateTriangleGroup();
|
|
ResultOut.NewGroups.Add(DefaultGroupID);
|
|
}
|
|
NewGroupID = DefaultGroupID;
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
if (bComputeTriangleMap)
|
|
{
|
|
IndexMaps.SetTriangle(SourceTriangleID, NewTriangleID);
|
|
}
|
|
ResultOut.NewTriangles.Add(NewTriangleID);
|
|
|
|
AppendTriangleAttributes(SourceMesh, SourceTriangleID, Mesh, NewTriangleID, IndexMaps, ResultOut);
|
|
|
|
//Mesh->CheckValidity(true);
|
|
}
|
|
|
|
AppendVertexAttributes(SourceMesh, Mesh, IndexMaps);
|
|
|
|
}
|
|
|
|
|
|
bool FDynamicMeshEditor::SplitMesh(const FDynamicMesh3* SourceMesh, TArray<FDynamicMesh3>& SplitMeshes, TFunctionRef<int(int)> TriIDToMeshID, int DeleteMeshID)
|
|
{
|
|
using namespace UE::DynamicMeshEditorInternals;
|
|
|
|
TMap<int, int> MeshIDToIndex;
|
|
int NumMeshes = 0;
|
|
bool bAlsoDelete = false;
|
|
for (int TID : SourceMesh->TriangleIndicesItr())
|
|
{
|
|
int MeshID = TriIDToMeshID(TID);
|
|
if (MeshID == DeleteMeshID)
|
|
{
|
|
bAlsoDelete = true;
|
|
continue;
|
|
}
|
|
if (!MeshIDToIndex.Contains(MeshID))
|
|
{
|
|
MeshIDToIndex.Add(MeshID, NumMeshes++);
|
|
}
|
|
}
|
|
|
|
if (!bAlsoDelete && NumMeshes < 2)
|
|
{
|
|
return false; // nothing to do, so don't bother filling the split meshes array
|
|
}
|
|
|
|
SplitMeshes.Reset();
|
|
SplitMeshes.SetNum(NumMeshes);
|
|
// enable matching attributes
|
|
for (FDynamicMesh3& M : SplitMeshes)
|
|
{
|
|
M.EnableMeshComponents(SourceMesh->GetComponentsFlags());
|
|
if (SourceMesh->HasAttributes())
|
|
{
|
|
M.EnableAttributes();
|
|
M.Attributes()->EnableMatchingAttributes(*SourceMesh->Attributes());
|
|
}
|
|
}
|
|
|
|
if (NumMeshes == 0) // full delete case, just leave the empty mesh
|
|
{
|
|
return true;
|
|
}
|
|
|
|
TArray<FMeshIndexMappings> Mappings; Mappings.Reserve(NumMeshes);
|
|
FDynamicMeshEditResult UnusedInvalidResultAccumulator; // only here because some functions require it
|
|
for (int Idx = 0; Idx < NumMeshes; Idx++)
|
|
{
|
|
FMeshIndexMappings& Map = Mappings.Emplace_GetRef();
|
|
Map.Initialize(&SplitMeshes[Idx]);
|
|
}
|
|
|
|
for (int SourceTID : SourceMesh->TriangleIndicesItr())
|
|
{
|
|
int MeshID = TriIDToMeshID(SourceTID);
|
|
if (MeshID == DeleteMeshID)
|
|
{
|
|
continue; // just skip triangles w/ the Delete Mesh ID
|
|
}
|
|
int MeshIndex = MeshIDToIndex[MeshID];
|
|
FDynamicMesh3& Mesh = SplitMeshes[MeshIndex];
|
|
FMeshIndexMappings& IndexMaps = Mappings[MeshIndex];
|
|
|
|
FIndex3i Tri = SourceMesh->GetTriangle(SourceTID);
|
|
|
|
// Find or create corresponding triangle group
|
|
int NewGID = FDynamicMesh3::InvalidID;
|
|
if (SourceMesh->HasTriangleGroups())
|
|
{
|
|
int SourceGroupID = SourceMesh->GetTriangleGroup(SourceTID);
|
|
if (SourceGroupID >= 0)
|
|
{
|
|
NewGID = IndexMaps.GetNewGroup(SourceGroupID);
|
|
if (NewGID == IndexMaps.InvalidID())
|
|
{
|
|
NewGID = Mesh.AllocateTriangleGroup();
|
|
IndexMaps.SetGroup(SourceGroupID, NewGID);
|
|
}
|
|
}
|
|
}
|
|
|
|
FIndex3i NewTri;
|
|
for (int j = 0; j < 3; ++j)
|
|
{
|
|
int SourceVID = Tri[j];
|
|
int NewVID = IndexMaps.GetNewVertex(SourceVID);
|
|
if (NewVID == IndexMaps.InvalidID())
|
|
{
|
|
NewVID = Mesh.AppendVertex(*SourceMesh, SourceVID);
|
|
IndexMaps.SetVertex(SourceVID, NewVID);
|
|
}
|
|
NewTri[j] = NewVID;
|
|
}
|
|
|
|
int NewTID = Mesh.AppendTriangle(NewTri, NewGID);
|
|
IndexMaps.SetTriangle(SourceTID, NewTID);
|
|
AppendTriangleAttributes(SourceMesh, SourceTID, &Mesh, NewTID, IndexMaps, UnusedInvalidResultAccumulator);
|
|
}
|
|
|
|
for (int Idx = 0; Idx < NumMeshes; Idx++)
|
|
{
|
|
AppendVertexAttributes(SourceMesh, &SplitMeshes[Idx], Mappings[Idx]);
|
|
}
|
|
|
|
return true;
|
|
} |