You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
1743 lines
54 KiB
C++
1743 lines
54 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Operations/MeshBevel.h"
|
|
|
|
#include "GroupTopology.h"
|
|
#include "EdgeLoop.h"
|
|
#include "DynamicMesh/DynamicMeshChangeTracker.h"
|
|
#include "MeshWeights.h"
|
|
#include "CompGeom/PolygonTriangulation.h"
|
|
#include "DynamicMesh/MeshNormals.h"
|
|
#include "DynamicMesh/MeshIndexUtil.h"
|
|
#include "MeshRegionBoundaryLoops.h"
|
|
#include "Operations/PolyEditingEdgeUtil.h"
|
|
#include "Operations/PolyEditingUVUtil.h"
|
|
#include "Algo/Count.h"
|
|
#include "Distance/DistLine3Line3.h"
|
|
|
|
using namespace UE::Geometry;
|
|
|
|
// Uncomment to enable various checks and ensures in the Bevel code.
|
|
// We cannot default-enable these checks even in Debug builds, because
|
|
// Bevel still frequently hits some of these cases, and those checks/ensures
|
|
// make it very difficult to debug a game using Bevel in Geometry Script (eg Lyra sample)
|
|
//#define ENABLE_MESH_BEVEL_DEBUG
|
|
|
|
#ifdef ENABLE_MESH_BEVEL_DEBUG
|
|
#define MESH_BEVEL_DEBUG_CHECK(Expr) checkSlow(Expr)
|
|
#define MESH_BEVEL_DEBUG_ENSURE(Expr) ensure(Expr)
|
|
#else
|
|
#define MESH_BEVEL_DEBUG_CHECK(Expr)
|
|
#define MESH_BEVEL_DEBUG_ENSURE(Expr) !!(Expr)
|
|
#endif
|
|
|
|
|
|
namespace UELocal
|
|
{
|
|
static void QuadsToTris(const FDynamicMesh3& Mesh, const TArray<FIndex2i>& Quads, TArray<int32>& TrisOut, bool bReset)
|
|
{
|
|
int32 N = Quads.Num();
|
|
if (bReset)
|
|
{
|
|
TrisOut.Reset();
|
|
TrisOut.Reserve(2 * N);
|
|
}
|
|
for (const FIndex2i& Quad : Quads)
|
|
{
|
|
if (Mesh.IsTriangle(Quad.A))
|
|
{
|
|
TrisOut.Add(Quad.A);
|
|
}
|
|
if (Mesh.IsTriangle(Quad.B))
|
|
{
|
|
TrisOut.Add(Quad.B);
|
|
}
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FMeshBevel::InitializeFromGroupTopology(const FDynamicMesh3& Mesh, const FGroupTopology& Topology)
|
|
{
|
|
ResultInfo = FGeometryResult(EGeometryResultType::InProgress);
|
|
|
|
// set up initial problem inputs
|
|
for (int32 TopoEdgeID = 0; TopoEdgeID < Topology.Edges.Num(); ++TopoEdgeID)
|
|
{
|
|
if (Topology.IsIsolatedLoop(TopoEdgeID))
|
|
{
|
|
FEdgeLoop NewLoop;
|
|
NewLoop.InitializeFromEdges(&Mesh, Topology.Edges[TopoEdgeID].Span.Edges);
|
|
AddBevelEdgeLoop(Mesh, NewLoop);
|
|
}
|
|
else
|
|
{
|
|
AddBevelGroupEdge(Mesh, Topology, TopoEdgeID);
|
|
}
|
|
|
|
if (ResultInfo.CheckAndSetCancelled(Progress))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// precompute topological information necessary to apply bevel to vertices/edges/loops
|
|
BuildVertexSets(Mesh);
|
|
}
|
|
|
|
|
|
void FMeshBevel::InitializeFromGroupTopologyEdges(const FDynamicMesh3& Mesh, const FGroupTopology& Topology, const TArray<int32>& GroupEdges)
|
|
{
|
|
ResultInfo = FGeometryResult(EGeometryResultType::InProgress);
|
|
|
|
// set up initial problem inputs
|
|
for (int32 TopoEdgeID : GroupEdges)
|
|
{
|
|
if (Topology.IsIsolatedLoop(TopoEdgeID))
|
|
{
|
|
FEdgeLoop NewLoop;
|
|
NewLoop.InitializeFromEdges(&Mesh, Topology.Edges[TopoEdgeID].Span.Edges);
|
|
AddBevelEdgeLoop(Mesh, NewLoop);
|
|
}
|
|
else
|
|
{
|
|
AddBevelGroupEdge(Mesh, Topology, TopoEdgeID);
|
|
}
|
|
|
|
if (ResultInfo.CheckAndSetCancelled(Progress))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// precompute topological information necessary to apply bevel to vertices/edges/loops
|
|
BuildVertexSets(Mesh);
|
|
}
|
|
|
|
|
|
bool FMeshBevel::InitializeFromGroupTopologyFaces(const FDynamicMesh3& Mesh, const FGroupTopology& Topology, const TArray<int32>& GroupFaces)
|
|
{
|
|
FGroupTopologySelection Selection;
|
|
Selection.SelectedGroupIDs.Append(GroupFaces);
|
|
TArray<int32> Triangles;
|
|
Topology.GetSelectedTriangles(Selection, Triangles);
|
|
return InitializeFromTriangleSet(Mesh, Triangles);
|
|
}
|
|
|
|
bool FMeshBevel::InitializeFromTriangleSet(const FDynamicMesh3& Mesh, const TArray<int32>& Triangles)
|
|
{
|
|
ResultInfo = FGeometryResult(EGeometryResultType::InProgress);
|
|
|
|
FMeshRegionBoundaryLoops RegionLoops(&Mesh, Triangles, true);
|
|
if (RegionLoops.bFailed)
|
|
{
|
|
ResultInfo.SetFailed();
|
|
return false;
|
|
}
|
|
|
|
// cannot bevel a selection-bowtie vertex, so we have to check for those and fail here
|
|
TSet<int32> AllVertices;
|
|
for (const FEdgeLoop& Loop : RegionLoops.Loops)
|
|
{
|
|
for (int32 vid : Loop.Vertices)
|
|
{
|
|
if (AllVertices.Contains(vid))
|
|
{
|
|
return false;
|
|
}
|
|
AllVertices.Add(vid);
|
|
}
|
|
}
|
|
|
|
for (const FEdgeLoop& Loop : RegionLoops.Loops)
|
|
{
|
|
AddBevelEdgeLoop(Mesh, Loop);
|
|
}
|
|
|
|
// precompute topological information necessary to apply bevel to vertices/edges/loops
|
|
BuildVertexSets(Mesh);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
bool FMeshBevel::Apply(FDynamicMesh3& Mesh, FDynamicMeshChangeTracker* ChangeTracker)
|
|
{
|
|
// disconnect along bevel graph edges/vertices and save necessary info
|
|
UnlinkEdges(Mesh, ChangeTracker);
|
|
if (ResultInfo.CheckAndSetCancelled(Progress))
|
|
{
|
|
return false;
|
|
}
|
|
UnlinkLoops(Mesh, ChangeTracker);
|
|
if (ResultInfo.CheckAndSetCancelled(Progress))
|
|
{
|
|
return false;
|
|
}
|
|
UnlinkVertices(Mesh, ChangeTracker);
|
|
if (ResultInfo.CheckAndSetCancelled(Progress))
|
|
{
|
|
return false;
|
|
}
|
|
FixUpUnlinkedBevelEdges(Mesh);
|
|
if (ResultInfo.CheckAndSetCancelled(Progress))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// update vertex positions
|
|
DisplaceVertices(Mesh, InsetDistance);
|
|
if (ResultInfo.CheckAndSetCancelled(Progress))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// mesh the bevel corners and edges
|
|
CreateBevelMeshing(Mesh);
|
|
if (ResultInfo.CheckAndSetCancelled(Progress))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// build output list of triangles so it can be re-used in operations below if useful
|
|
NewTriangles.Reset();
|
|
for (FBevelVertex& Vertex : Vertices)
|
|
{
|
|
NewTriangles.Append(Vertex.NewTriangles);
|
|
}
|
|
for (FBevelEdge& Edge : Edges)
|
|
{
|
|
UELocal::QuadsToTris(Mesh, Edge.StripQuads, NewTriangles, false);
|
|
}
|
|
for (FBevelLoop& Loop : Loops)
|
|
{
|
|
UELocal::QuadsToTris(Mesh, Loop.StripQuads, NewTriangles, false);
|
|
}
|
|
|
|
|
|
// compute normals
|
|
ComputeNormals(Mesh);
|
|
if (ResultInfo.CheckAndSetCancelled(Progress))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// compute UVs
|
|
ComputeUVs(Mesh);
|
|
if (ResultInfo.CheckAndSetCancelled(Progress))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ComputeMaterialIDs(Mesh);
|
|
if (ResultInfo.CheckAndSetCancelled(Progress))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// todo: compute other attribs
|
|
|
|
ResultInfo.SetSuccess(true, Progress);
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
FMeshBevel::FBevelVertex* FMeshBevel::GetBevelVertexFromVertexID(int32 VertexID)
|
|
{
|
|
int32* FoundIndex = VertexIDToIndexMap.Find(VertexID);
|
|
if (FoundIndex == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
return &Vertices[*FoundIndex];
|
|
}
|
|
|
|
|
|
|
|
void FMeshBevel::AddBevelGroupEdge(const FDynamicMesh3& Mesh, const FGroupTopology& Topology, int32 GroupEdgeID)
|
|
{
|
|
const TArray<int32>& MeshEdgeList = Topology.Edges[GroupEdgeID].Span.Edges;
|
|
|
|
// currently cannot handle boundary edges
|
|
if ( Algo::CountIf(MeshEdgeList, [&Mesh](int EdgeID) { return Mesh.IsBoundaryEdge(EdgeID); }) > 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
FIndex2i EdgeCornerIDs = Topology.Edges[GroupEdgeID].EndpointCorners;
|
|
|
|
FBevelEdge Edge;
|
|
|
|
for (int32 ci = 0; ci < 2; ++ci)
|
|
{
|
|
int32 CornerID = EdgeCornerIDs[ci];
|
|
FGroupTopology::FCorner Corner = Topology.Corners[CornerID];
|
|
int32 VertexID = Corner.VertexID;
|
|
Edge.bEndpointBoundaryFlag[ci] = Mesh.IsBoundaryVertex(VertexID);
|
|
int32 IncomingEdgeID = (ci == 0) ? MeshEdgeList[0] : MeshEdgeList.Last();
|
|
|
|
FBevelVertex* VertInfo = GetBevelVertexFromVertexID(VertexID);
|
|
if (VertInfo == nullptr)
|
|
{
|
|
FBevelVertex NewVertex;
|
|
NewVertex.CornerID = CornerID;
|
|
NewVertex.VertexID = VertexID;
|
|
int32 NewIndex = Vertices.Num();
|
|
Vertices.Add(NewVertex);
|
|
VertexIDToIndexMap.Add(VertexID, NewIndex);
|
|
VertInfo = &Vertices[NewIndex];
|
|
}
|
|
VertInfo->IncomingBevelMeshEdges.Add(IncomingEdgeID);
|
|
VertInfo->IncomingBevelTopoEdges.Add(GroupEdgeID);
|
|
}
|
|
|
|
Edge.MeshEdges.Append(MeshEdgeList);
|
|
Edge.MeshVertices.Append(Topology.Edges[GroupEdgeID].Span.Vertices);
|
|
Edge.GroupEdgeID = GroupEdgeID;
|
|
Edge.GroupIDs = Topology.Edges[GroupEdgeID].Groups;
|
|
|
|
Edge.MeshEdgeTris.Reserve(Edge.MeshEdges.Num());
|
|
for (int32 eid : Edge.MeshEdges)
|
|
{
|
|
Edge.MeshEdgeTris.Add(Mesh.GetEdgeT(eid));
|
|
}
|
|
|
|
Edges.Add(MoveTemp(Edge));
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void FMeshBevel::AddBevelEdgeLoop(const FDynamicMesh3& Mesh, const FEdgeLoop& MeshEdgeLoop)
|
|
{
|
|
// currently cannot handle boundary edges
|
|
if ( Algo::CountIf(MeshEdgeLoop.Edges, [&Mesh](int EdgeID) { return Mesh.IsBoundaryEdge(EdgeID); }) > 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
FBevelLoop Loop;
|
|
Loop.MeshEdges = MeshEdgeLoop.Edges;
|
|
Loop.MeshVertices = MeshEdgeLoop.Vertices;
|
|
|
|
Loop.MeshEdgeTris.Reserve(Loop.MeshEdges.Num());
|
|
for (int32 eid : Loop.MeshEdges)
|
|
{
|
|
Loop.MeshEdgeTris.Add(Mesh.GetEdgeT(eid));
|
|
}
|
|
|
|
Loops.Add(Loop);
|
|
}
|
|
|
|
|
|
|
|
|
|
void FMeshBevel::BuildVertexSets(const FDynamicMesh3& Mesh)
|
|
{
|
|
// can be parallel
|
|
for (FBevelVertex& Vertex : Vertices)
|
|
{
|
|
// get sorted list of triangles around the vertex
|
|
TArray<int> GroupLengths;
|
|
TArray<bool> bGroupIsLoop;
|
|
EMeshResult Result = Mesh.GetVtxContiguousTriangles(Vertex.VertexID, Vertex.SortedTriangles, GroupLengths, bGroupIsLoop);
|
|
if ( Result != EMeshResult::Ok || GroupLengths.Num() != 1 || Vertex.SortedTriangles.Num() < 2)
|
|
{
|
|
Vertex.VertexType = EBevelVertexType::Unknown;
|
|
continue;
|
|
}
|
|
|
|
// GetVtxContiguousTriangles does not return triangles sorted in a consistent direction. This check will
|
|
// reverse the ordering such that it is consistently walking counter-clockwise around the vertex (I think...)
|
|
FIndex3i Tri0 = Mesh.GetTriangle(Vertex.SortedTriangles[0]).GetCycled(Vertex.VertexID);
|
|
FIndex3i Tri1 = Mesh.GetTriangle(Vertex.SortedTriangles[1]).GetCycled(Vertex.VertexID);
|
|
if (Tri0.C == Tri1.B)
|
|
{
|
|
Algo::Reverse(Vertex.SortedTriangles);
|
|
}
|
|
|
|
if (Mesh.IsBoundaryVertex(Vertex.VertexID))
|
|
{
|
|
Vertex.VertexType = EBevelVertexType::BoundaryVertex;
|
|
continue;
|
|
}
|
|
|
|
|
|
MESH_BEVEL_DEBUG_CHECK(Vertex.IncomingBevelMeshEdges.Num() != 0); // shouldn't ever happen
|
|
if (Vertex.IncomingBevelMeshEdges.Num() == 1)
|
|
{
|
|
BuildTerminatorVertex(Vertex, Mesh);
|
|
}
|
|
else
|
|
{
|
|
BuildJunctionVertex(Vertex, Mesh);
|
|
}
|
|
|
|
if (ResultInfo.CheckAndSetCancelled(Progress))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// At a 'Terminator' vertex we are going to split the one-ring and fill the bevel-side with a quad, which
|
|
// will usually leave a single-triangle hole. However if two Terminator vertices are directly connected
|
|
// via a non-bevel mesh edge, the two triangles will be connected, ie the hole is quad-shaped. It's easier
|
|
// to detect this case here /before/ we split the mesh up into disconnected parts...
|
|
for (FBevelVertex& Vertex : Vertices)
|
|
{
|
|
if (Vertex.VertexType == EBevelVertexType::TerminatorVertex)
|
|
{
|
|
int32 OtherVertexID = Vertex.TerminatorInfo.B;
|
|
int32* OtherBevelVtxIdx = VertexIDToIndexMap.Find(OtherVertexID);
|
|
if (OtherBevelVtxIdx != nullptr)
|
|
{
|
|
// does other vertex have to be a terminator? or can this also happen w/ a junction?
|
|
FBevelVertex& OtherVertex = Vertices[*OtherBevelVtxIdx];
|
|
if (OtherVertex.VertexType == EBevelVertexType::TerminatorVertex)
|
|
{
|
|
// want to skip this if the ring-split edge is already a bevel edge
|
|
int32 MeshEdgeID = Mesh.FindEdge(Vertex.VertexID, OtherVertex.VertexID);
|
|
MESH_BEVEL_DEBUG_CHECK(MeshEdgeID >= 0);
|
|
if (Mesh.IsEdge(MeshEdgeID) &&
|
|
Vertex.TerminatorInfo.A == MeshEdgeID &&
|
|
OtherVertex.TerminatorInfo.A == MeshEdgeID && // do we need the other vertex to use the same edge here? (is this actually a hard constraint on that edge that we should be enforcing??)
|
|
Vertex.IncomingBevelMeshEdges.Contains(MeshEdgeID) == false )
|
|
{
|
|
Vertex.ConnectedBevelVertex = *OtherBevelVtxIdx;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FMeshBevel::BuildJunctionVertex(FBevelVertex& Vertex, const FDynamicMesh3& Mesh)
|
|
{
|
|
//
|
|
// Now split up the single contiguous one-ring into "Wedges" created by the incoming split-edges
|
|
//
|
|
|
|
// find first split edge, and the triangle/index "after" that first split edge
|
|
int32 NT = Vertex.SortedTriangles.Num();
|
|
int32 StartTriIndex = -1;
|
|
for (int32 k = 0; k < NT; ++k)
|
|
{
|
|
if (FindSharedEdgeInTriangles(Mesh, Vertex.SortedTriangles[k], Vertex.SortedTriangles[(k + 1) % NT]) == Vertex.IncomingBevelMeshEdges[0])
|
|
{
|
|
StartTriIndex = (k + 1) % NT; // start at second tri, so that bevel-edge is first edge in wedge
|
|
break;
|
|
}
|
|
}
|
|
if (StartTriIndex == -1)
|
|
{
|
|
Vertex.VertexType = EBevelVertexType::Unknown;
|
|
return;
|
|
}
|
|
|
|
// now walk around the one-ring tris, accumulating into current Wedge until we hit another split-edge,
|
|
// at which point a new Wedge is spawned
|
|
int32 CurTriIndex = StartTriIndex;
|
|
FOneRingWedge CurWedge;
|
|
CurWedge.WedgeVertex = Vertex.VertexID;
|
|
CurWedge.Triangles.Add(Vertex.SortedTriangles[CurTriIndex]);
|
|
CurWedge.BorderEdges.A = Vertex.IncomingBevelMeshEdges[0];
|
|
for (int32 k = 0; k < NT; ++k)
|
|
{
|
|
int32 CurTri = Vertex.SortedTriangles[CurTriIndex % NT];
|
|
int32 NextTri = Vertex.SortedTriangles[(CurTriIndex + 1) % NT];
|
|
int32 SharedEdge = FindSharedEdgeInTriangles(Mesh, CurTri, NextTri);
|
|
MESH_BEVEL_DEBUG_CHECK(SharedEdge != -1);
|
|
if (Vertex.IncomingBevelMeshEdges.Contains(SharedEdge))
|
|
{
|
|
// if we found a bevel-edge, close the current wedge and start a new one
|
|
CurWedge.BorderEdges.B = SharedEdge;
|
|
Vertex.Wedges.Add(CurWedge);
|
|
CurWedge = FOneRingWedge();
|
|
CurWedge.WedgeVertex = Vertex.VertexID;
|
|
CurWedge.BorderEdges.A = SharedEdge;
|
|
}
|
|
CurWedge.Triangles.Add(NextTri);
|
|
CurTriIndex++;
|
|
}
|
|
// ?? is there a chance that we have a final open wedge here? we iterate one extra time so it shouldn't happen (or could we get an extra wedge then??)
|
|
|
|
for (FOneRingWedge& Wedge : Vertex.Wedges)
|
|
{
|
|
Wedge.BorderEdgeTriEdgeIndices.A = Mesh.GetTriEdges(Wedge.Triangles[0]).IndexOf(Wedge.BorderEdges.A);
|
|
Wedge.BorderEdgeTriEdgeIndices.B = Mesh.GetTriEdges(Wedge.Triangles.Last()).IndexOf(Wedge.BorderEdges.B);
|
|
}
|
|
|
|
if (Vertex.Wedges.Num() > 1)
|
|
{
|
|
Vertex.VertexType = EBevelVertexType::JunctionVertex;
|
|
}
|
|
else
|
|
{
|
|
Vertex.VertexType = EBevelVertexType::Unknown;
|
|
}
|
|
}
|
|
|
|
|
|
void FMeshBevel::BuildTerminatorVertex(FBevelVertex& Vertex, const FDynamicMesh3& Mesh)
|
|
{
|
|
Vertex.VertexType = EBevelVertexType::Unknown;
|
|
if (MESH_BEVEL_DEBUG_ENSURE(Vertex.IncomingBevelMeshEdges.Num() == 1) == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int32 IncomingEdgeID = Vertex.IncomingBevelMeshEdges[0];
|
|
int32 NumTris = Vertex.SortedTriangles.Num();
|
|
|
|
// We have one edge coming into the vertex one ring, our main problem is to pick a second
|
|
// edge to split the one-ring with. There is no obvious right answer in many cases.
|
|
|
|
// "other" split edge that we pick
|
|
int32 RingSplitEdgeID = -1;
|
|
|
|
// NOTE: code below assumes we have polygroups on the mesh. In theory we could also support not having polygroups,
|
|
// by (eg) picking an arbitrary edge "furthest" from IncomingEdgeID in the exponential map
|
|
|
|
// Find the ordered set of triangles that are not in either of the groups connected to IncomingEdgeID. Eg imagine
|
|
// a cube corner, if we want to bevel IncomingEdgeID along one of the cube edges, we want to add the new edge
|
|
// in the "furthest" face (perpendicular to the edge)
|
|
FIndex2i IncomingEdgeT = Mesh.GetEdgeT(IncomingEdgeID);
|
|
FIndex2i IncomingEdgeGroups(Mesh.GetTriangleGroup(IncomingEdgeT.A), Mesh.GetTriangleGroup(IncomingEdgeT.B));
|
|
|
|
// Find the first index of Vertex.SortedTriangles that we want to remove.
|
|
// This should not be able to fail but if it does, we will just start at 0
|
|
// and maybe end up with some bevel failures below
|
|
int32 NumTriangles = Vertex.SortedTriangles.Num();
|
|
int32 StartIndex = 0;
|
|
for (int32 k = 0; k < NumTriangles; ++k)
|
|
{
|
|
int32 gid = Mesh.GetTriangleGroup(Vertex.SortedTriangles[k]);
|
|
if (IncomingEdgeGroups.Contains(gid))
|
|
{
|
|
StartIndex = k;
|
|
break;
|
|
}
|
|
}
|
|
|
|
TArray<int32> OtherGroupTris; // sorted wedge of triangles that are not in either of the groups connected to incoming edge
|
|
TArray<int32> OtherGroups; // list of group IDs encountered, in-order
|
|
for ( int32 k = 0; k < NumTriangles; ++k )
|
|
{
|
|
// Vertex.SortedTriangles was ordered, ie sequential triangles were connected, but if we filter
|
|
// out some triangles it may no longer be sequential in OtherGroupTris, leading to badness.
|
|
// But the two groups we are removing should be contiguous, so if we start there, then the
|
|
// remaining tris should be contiguous. StartIndex found above should give us that triangle.
|
|
int32 ShiftedIndex = (StartIndex + k) % NumTriangles;
|
|
int32 tid = Vertex.SortedTriangles[ShiftedIndex];
|
|
int32 gid = Mesh.GetTriangleGroup(tid);
|
|
if (IncomingEdgeGroups.Contains(gid) == false)
|
|
{
|
|
OtherGroupTris.Add(tid);
|
|
OtherGroups.AddUnique(gid);
|
|
}
|
|
}
|
|
int32 NumRemainingTris = OtherGroupTris.Num();
|
|
|
|
// Determine which edge to split at in the "other" group triangles. If we only have one group
|
|
// then we can try to pick the "middlest" edge. The worst case is when there is only one triangle,
|
|
// then we are picking a not-very-good edge no matter what (potentially we should do an edge split or
|
|
// face poke in that situation). If we have multiple groups then we probably want to pick one
|
|
// of the group-boundary edges inside the triangle-span, ideally the "middlest" but currently we
|
|
// are just picking one arbitrarily...
|
|
if (OtherGroups.Num() == 1)
|
|
{
|
|
Vertex.NewGroupID = OtherGroups[0];
|
|
if (OtherGroupTris.Num() == 1)
|
|
{
|
|
FIndex3i TriEdges = Mesh.GetTriEdges(OtherGroupTris[0]);
|
|
for (int32 j = 0; j < 3; ++j)
|
|
{
|
|
if (Mesh.GetEdgeV(TriEdges[j]).Contains(Vertex.VertexID))
|
|
{
|
|
RingSplitEdgeID = TriEdges[j];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (OtherGroupTris.Num() == 2)
|
|
{
|
|
RingSplitEdgeID = FindSharedEdgeInTriangles(Mesh, OtherGroupTris[0], OtherGroupTris[1]);
|
|
}
|
|
else
|
|
{
|
|
// try using the 'middlest' triangle as the 'middlest' edge
|
|
// TODO: should compute opening angles here and pick the edge closest to the middle of the angular span!!
|
|
int32 j = OtherGroupTris.Num() / 2;
|
|
RingSplitEdgeID = FindSharedEdgeInTriangles(Mesh, OtherGroupTris[j], OtherGroupTris[j+1]);
|
|
|
|
// If the OtherGroupTris list ended up being not contiguous (see above for how that can happen), then
|
|
// it's possible that (j) and (j+1) are not connected and the share-edge search will fail. In that case
|
|
// we will just linear-search for two connected triangles. If this fails then this vertex will not be bevelled.
|
|
if (RingSplitEdgeID == -1)
|
|
{
|
|
for (int32 k = 0; k < NumRemainingTris; ++k)
|
|
{
|
|
RingSplitEdgeID = FindSharedEdgeInTriangles(Mesh, OtherGroupTris[k], OtherGroupTris[(k+1)%NumRemainingTris]);
|
|
if (RingSplitEdgeID != -1)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Search for two adjacent triangles in different groups that have a shared edge. This search
|
|
// may need to wrap around if we got unlucky in the triangle ordering in OtherGroupTris
|
|
for (int32 k = 0; k < OtherGroupTris.Num(); ++k)
|
|
{
|
|
int32 TriangleA = OtherGroupTris[k], TriangleB = OtherGroupTris[(k+1)%NumRemainingTris];
|
|
if (Mesh.GetTriangleGroup(TriangleA) != Mesh.GetTriangleGroup(TriangleB))
|
|
{
|
|
RingSplitEdgeID = FindSharedEdgeInTriangles(Mesh, TriangleA, TriangleB);
|
|
Vertex.NewGroupID = -1; // allocate a new group for this triangle, this is usually what one would want
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (RingSplitEdgeID == -1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// save edgeid/vertexid for the 'terminator edge' that we will disconnect at
|
|
FIndex2i SplitEdgeV = Mesh.GetEdgeV(RingSplitEdgeID);
|
|
Vertex.TerminatorInfo = FIndex2i(RingSplitEdgeID, SplitEdgeV.OtherElement(Vertex.VertexID));
|
|
|
|
// split the terminator vertex into two wedges
|
|
TArray<int32> SplitTriSets[2];
|
|
if (SplitInteriorVertexTrianglesIntoSubsets(&Mesh, Vertex.VertexID, IncomingEdgeID, RingSplitEdgeID, SplitTriSets[0], SplitTriSets[1]) == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// make the wedge list
|
|
Vertex.Wedges.SetNum(2);
|
|
Vertex.Wedges[0].WedgeVertex = Vertex.VertexID;
|
|
Vertex.Wedges[0].Triangles.Append(SplitTriSets[0]);
|
|
Vertex.Wedges[1].WedgeVertex = Vertex.VertexID;
|
|
Vertex.Wedges[1].Triangles.Append(SplitTriSets[1]);
|
|
|
|
// We need to know the two border edges of each wedge, because we will use this information
|
|
// in later stages. Note that this block is not specific to the TerminatorVertex case
|
|
for (FOneRingWedge& Wedge : Vertex.Wedges)
|
|
{
|
|
int32 NumWedgeTris = Wedge.Triangles.Num();
|
|
|
|
FIndex2i VtxEdges0 = IndexUtil::FindVertexEdgesInTriangle(Mesh, Wedge.Triangles[0], Vertex.VertexID);
|
|
if (NumWedgeTris == 1)
|
|
{
|
|
// If the wedge only has one tri, both edges are the border edges.
|
|
// Note that these are not sorted, ie B might not be the edge shared with the next Wedge.
|
|
// Currently this does not matter but it might in the future?
|
|
Wedge.BorderEdges.A = VtxEdges0.A;
|
|
Wedge.BorderEdges.B = VtxEdges0.B;
|
|
}
|
|
else
|
|
{
|
|
// the wedge-border-edge is *not* the edge connected to the next triangle in the wedge-triangle-list
|
|
Wedge.BorderEdges.A = ( Mesh.GetEdgeT(VtxEdges0.A).Contains(Wedge.Triangles[1]) ) ? VtxEdges0.B : VtxEdges0.A;
|
|
// final wedge-border-edge is the same case, with the last and second-last tris
|
|
FIndex2i VtxEdges1 = IndexUtil::FindVertexEdgesInTriangle(Mesh, Wedge.Triangles[NumWedgeTris-1], Vertex.VertexID);
|
|
Wedge.BorderEdges.B = ( Mesh.GetEdgeT(VtxEdges1.A).Contains(Wedge.Triangles[NumWedgeTris-2]) ) ? VtxEdges1.B : VtxEdges1.A;
|
|
}
|
|
// save the index of the border edge in it's triangle, so that when we disconnect the wedges later,
|
|
// we can find the new edge ID
|
|
Wedge.BorderEdgeTriEdgeIndices.A = Mesh.GetTriEdges(Wedge.Triangles[0]).IndexOf(Wedge.BorderEdges.A);
|
|
Wedge.BorderEdgeTriEdgeIndices.B = Mesh.GetTriEdges(Wedge.Triangles.Last()).IndexOf(Wedge.BorderEdges.B);
|
|
}
|
|
|
|
Vertex.VertexType = EBevelVertexType::TerminatorVertex;
|
|
}
|
|
|
|
|
|
void FMeshBevel::UnlinkEdges(FDynamicMesh3& Mesh, FDynamicMeshChangeTracker* ChangeTracker)
|
|
{
|
|
for (FBevelEdge& Edge : Edges)
|
|
{
|
|
UnlinkBevelEdgeInterior(Mesh, Edge, ChangeTracker);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
namespace UELocal
|
|
{
|
|
// decomposition of a vertex one-ring into two connected triangle subsets
|
|
struct FVertexSplit
|
|
{
|
|
int32 VertexID;
|
|
bool bOK;
|
|
TArray<int32> TriSets[2];
|
|
};
|
|
|
|
// walk along a sequence of vertex-splits and make sure that the split triangle sets
|
|
// maintain consistent "sides" (see call in UnlinkBevelEdgeInterior for more details)
|
|
static void ReconcileTriangleSets(TArray<FVertexSplit>& SplitSequence)
|
|
{
|
|
int32 N = SplitSequence.Num();
|
|
TArray<int32> PrevTriSet0;
|
|
for (int32 k = 0; k < N; ++k)
|
|
{
|
|
if (PrevTriSet0.Num() == 0 && SplitSequence[k].TriSets[0].Num() > 0)
|
|
{
|
|
PrevTriSet0 = SplitSequence[k].TriSets[0];
|
|
}
|
|
else
|
|
{
|
|
bool bFoundInSet0 = false;
|
|
for (int32 tid : SplitSequence[k].TriSets[0])
|
|
{
|
|
if (PrevTriSet0.Contains(tid))
|
|
{
|
|
bFoundInSet0 = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!bFoundInSet0)
|
|
{
|
|
Swap(SplitSequence[k].TriSets[0], SplitSequence[k].TriSets[1]);
|
|
}
|
|
PrevTriSet0 = SplitSequence[k].TriSets[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
|
|
void FMeshBevel::UnlinkBevelEdgeInterior(
|
|
FDynamicMesh3& Mesh,
|
|
FBevelEdge& BevelEdge,
|
|
FDynamicMeshChangeTracker* ChangeTracker)
|
|
{
|
|
// figure out what sets of triangles to split each vertex into
|
|
int32 N = BevelEdge.MeshVertices.Num();
|
|
|
|
TArray<UELocal::FVertexSplit> SplitsToProcess;
|
|
SplitsToProcess.SetNum(N);
|
|
|
|
// precompute triangle sets for each vertex we want to split, by "cutting" the one ring into two halves based
|
|
// on edges - 2 edges for interior vertices, and 1 edge for a boundary vertex at the start/end of the edge-span
|
|
|
|
SplitsToProcess[0] = UELocal::FVertexSplit{ BevelEdge.MeshVertices[0], false };
|
|
if (BevelEdge.bEndpointBoundaryFlag[0])
|
|
{
|
|
SplitsToProcess[0].bOK = SplitBoundaryVertexTrianglesIntoSubsets(&Mesh, SplitsToProcess[0].VertexID, BevelEdge.MeshEdges[0],
|
|
SplitsToProcess[0].TriSets[0], SplitsToProcess[0].TriSets[1]);
|
|
}
|
|
for (int32 k = 1; k < N - 1; ++k)
|
|
{
|
|
SplitsToProcess[k].VertexID = BevelEdge.MeshVertices[k];
|
|
if (Mesh.IsBoundaryVertex(SplitsToProcess[k].VertexID))
|
|
{
|
|
SplitsToProcess[k].bOK = false;
|
|
}
|
|
else
|
|
{
|
|
SplitsToProcess[k].bOK = SplitInteriorVertexTrianglesIntoSubsets(&Mesh, SplitsToProcess[k].VertexID,
|
|
BevelEdge.MeshEdges[k-1], BevelEdge.MeshEdges[k], SplitsToProcess[k].TriSets[0], SplitsToProcess[k].TriSets[1]);
|
|
}
|
|
}
|
|
SplitsToProcess[N-1] = UELocal::FVertexSplit{ BevelEdge.MeshVertices[N - 1], false };
|
|
if (BevelEdge.bEndpointBoundaryFlag[1])
|
|
{
|
|
SplitsToProcess[N-1].bOK = SplitBoundaryVertexTrianglesIntoSubsets(&Mesh, SplitsToProcess[N-1].VertexID, BevelEdge.MeshEdges[N-2],
|
|
SplitsToProcess[N-1].TriSets[0], SplitsToProcess[N-1].TriSets[1]);
|
|
}
|
|
|
|
// SplitInteriorVertexTrianglesIntoSubsets does not consistently order its output sets, ie, if you imagine [Edge0,Edge1] as a path
|
|
// cutting through the one ring, the "side" that Set0 and Set1 end up is arbitrary, and depends on the ordering of edges in the triangles of Edge1.
|
|
// This might ideally be fixed in the future, but for the time being, all we need is consistency. So we walk from the start of the
|
|
// edge to the end, checking for overlap between each tri-one-ring-wedge. If Split[k].TriSet0 does not overlap with Split[k-1].TriSet0, then
|
|
// we want to swap TriSet0 and TriSet1 at Split[k].
|
|
UELocal::ReconcileTriangleSets(SplitsToProcess);
|
|
|
|
// apply vertex splits and accumulate new list
|
|
N = SplitsToProcess.Num();
|
|
for (int32 k = 0; k < N; ++k)
|
|
{
|
|
const UELocal::FVertexSplit& Split = SplitsToProcess[k];
|
|
if (ChangeTracker)
|
|
{
|
|
ChangeTracker->SaveVertexOneRingTriangles(Split.VertexID, true);
|
|
}
|
|
|
|
bool bDone = false;
|
|
if (Split.bOK)
|
|
{
|
|
FDynamicMesh3::FVertexSplitInfo SplitInfo;
|
|
EMeshResult Result = Mesh.SplitVertex(Split.VertexID, Split.TriSets[0], SplitInfo);
|
|
if (MESH_BEVEL_DEBUG_ENSURE(Result == EMeshResult::Ok))
|
|
{
|
|
BevelEdge.NewMeshVertices.Add(SplitInfo.NewVertex);
|
|
bDone = true;
|
|
}
|
|
}
|
|
if (!bDone)
|
|
{
|
|
BevelEdge.NewMeshVertices.Add(Split.VertexID);
|
|
}
|
|
}
|
|
|
|
// now build edge correspondences
|
|
N = BevelEdge.MeshVertices.Num();
|
|
MESH_BEVEL_DEBUG_CHECK(N == BevelEdge.NewMeshVertices.Num());
|
|
for (int32 k = 0; k < N-1; ++k)
|
|
{
|
|
int32 Edge0 = BevelEdge.MeshEdges[k];
|
|
int32 Edge1 = Mesh.FindEdge(BevelEdge.NewMeshVertices[k], BevelEdge.NewMeshVertices[k + 1]);
|
|
BevelEdge.NewMeshEdges.Add(Edge1);
|
|
MESH_BEVEL_DEBUG_CHECK(Edge1 >= 0);
|
|
if ( Mesh.IsEdge(Edge1) && Edge0 != Edge1 && MeshEdgePairs.Contains(Edge0) == false )
|
|
{
|
|
MeshEdgePairs.Add(Edge0, Edge1);
|
|
MeshEdgePairs.Add(Edge1, Edge0);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void FMeshBevel::UnlinkBevelLoop(FDynamicMesh3& Mesh, FBevelLoop& BevelLoop, FDynamicMeshChangeTracker* ChangeTracker)
|
|
{
|
|
int32 N = BevelLoop.MeshVertices.Num();
|
|
|
|
TArray<UELocal::FVertexSplit> SplitsToProcess;
|
|
SplitsToProcess.SetNum(N);
|
|
|
|
// precompute triangle sets for each vertex we want to split
|
|
for (int32 k = 0; k < N; ++k)
|
|
{
|
|
SplitsToProcess[k].VertexID = BevelLoop.MeshVertices[k];
|
|
if (Mesh.IsBoundaryVertex(SplitsToProcess[k].VertexID))
|
|
{
|
|
// cannot split boundary vertex
|
|
SplitsToProcess[k].bOK = false;
|
|
}
|
|
else
|
|
{
|
|
int32 PrevEdge = (k == 0) ? BevelLoop.MeshEdges.Last() : BevelLoop.MeshEdges[k-1];
|
|
int32 CurEdge = BevelLoop.MeshEdges[k];
|
|
SplitsToProcess[k].bOK = SplitInteriorVertexTrianglesIntoSubsets(&Mesh, SplitsToProcess[k].VertexID,
|
|
PrevEdge, CurEdge, SplitsToProcess[k].TriSets[0], SplitsToProcess[k].TriSets[1]);
|
|
}
|
|
}
|
|
|
|
// fix up triangle sets - see call in UnlinkBevelEdgeInterior() for more info
|
|
UELocal::ReconcileTriangleSets(SplitsToProcess);
|
|
|
|
// apply vertex splits and accumulate new list
|
|
N = SplitsToProcess.Num();
|
|
for (int32 k = 0; k < N; ++k)
|
|
{
|
|
const UELocal::FVertexSplit& Split = SplitsToProcess[k];
|
|
if (ChangeTracker)
|
|
{
|
|
ChangeTracker->SaveVertexOneRingTriangles(Split.VertexID, true);
|
|
}
|
|
|
|
bool bDone = false;
|
|
if (Split.bOK)
|
|
{
|
|
FDynamicMesh3::FVertexSplitInfo SplitInfo;
|
|
EMeshResult Result = Mesh.SplitVertex(Split.VertexID, Split.TriSets[1], SplitInfo);
|
|
if (MESH_BEVEL_DEBUG_ENSURE(Result == EMeshResult::Ok))
|
|
{
|
|
BevelLoop.NewMeshVertices.Add(SplitInfo.NewVertex);
|
|
bDone = true;
|
|
}
|
|
}
|
|
if (!bDone)
|
|
{
|
|
BevelLoop.NewMeshVertices.Add(Split.VertexID); // failed to split, so we have a shared vertex on both "sides"
|
|
}
|
|
}
|
|
|
|
// now build edge correspondences
|
|
N = BevelLoop.MeshVertices.Num();
|
|
MESH_BEVEL_DEBUG_CHECK(N == BevelLoop.NewMeshVertices.Num());
|
|
for (int32 k = 0; k < N; ++k)
|
|
{
|
|
int32 Edge0 = BevelLoop.MeshEdges[k];
|
|
int32 Edge1 = Mesh.FindEdge(BevelLoop.NewMeshVertices[k], BevelLoop.NewMeshVertices[(k + 1)%N]);
|
|
BevelLoop.NewMeshEdges.Add(Edge1);
|
|
MESH_BEVEL_DEBUG_CHECK(Edge1 >= 0);
|
|
if (Mesh.IsEdge(Edge1) && Edge0 != Edge1 && MeshEdgePairs.Contains(Edge0) == false )
|
|
{
|
|
MeshEdgePairs.Add(Edge0, Edge1);
|
|
MeshEdgePairs.Add(Edge1, Edge0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMeshBevel::UnlinkLoops(FDynamicMesh3& Mesh, FDynamicMeshChangeTracker* ChangeTracker)
|
|
{
|
|
for (FBevelLoop& Loop : Loops)
|
|
{
|
|
UnlinkBevelLoop(Mesh, Loop, ChangeTracker);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void FMeshBevel::UnlinkVertices(FDynamicMesh3& Mesh, FDynamicMeshChangeTracker* ChangeTracker)
|
|
{
|
|
// TODO: currently have to do terminator vertices first because we do some of the
|
|
// determination inside the unlink code...
|
|
|
|
for (FBevelVertex& Vertex : Vertices)
|
|
{
|
|
if (Vertex.VertexType == EBevelVertexType::TerminatorVertex)
|
|
{
|
|
UnlinkTerminatorVertex(Mesh, Vertex, ChangeTracker);
|
|
}
|
|
}
|
|
|
|
for (FBevelVertex& Vertex : Vertices)
|
|
{
|
|
if (Vertex.VertexType == EBevelVertexType::JunctionVertex)
|
|
{
|
|
UnlinkJunctionVertex(Mesh, Vertex, ChangeTracker);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMeshBevel::UnlinkJunctionVertex(FDynamicMesh3& Mesh, FBevelVertex& Vertex, FDynamicMeshChangeTracker* ChangeTracker)
|
|
{
|
|
MESH_BEVEL_DEBUG_CHECK(Vertex.VertexType == EBevelVertexType::JunctionVertex);
|
|
|
|
if (ChangeTracker)
|
|
{
|
|
ChangeTracker->SaveVertexOneRingTriangles(Vertex.VertexID, true);
|
|
}
|
|
|
|
int32 NumWedges = Vertex.Wedges.Num();
|
|
MESH_BEVEL_DEBUG_CHECK(NumWedges > 1);
|
|
|
|
// Split triangles around vertex into separate tri-sets based on wedges.
|
|
// This will create a new vertex for each wedge.
|
|
for (int32 k = 1; k < NumWedges; ++k)
|
|
{
|
|
FOneRingWedge& Wedge = Vertex.Wedges[k];
|
|
|
|
FDynamicMesh3::FVertexSplitInfo SplitInfo;
|
|
EMeshResult Result = Mesh.SplitVertex(Vertex.VertexID, Wedge.Triangles, SplitInfo);
|
|
if (MESH_BEVEL_DEBUG_ENSURE(Result == EMeshResult::Ok))
|
|
{
|
|
Wedge.WedgeVertex = SplitInfo.NewVertex;
|
|
}
|
|
}
|
|
|
|
// update end start/end pairs for each wedge. If we created new edges above, this is
|
|
// the first time we will encounter them, so save in edge correspondence map
|
|
for (int32 k = 0; k < NumWedges; ++k)
|
|
{
|
|
FOneRingWedge& Wedge = Vertex.Wedges[k];
|
|
for (int32 j = 0; j < 2; ++j)
|
|
{
|
|
int32 OldWedgeEdgeID = Wedge.BorderEdges[j];
|
|
int32 OldWedgeEdgeIndex = Wedge.BorderEdgeTriEdgeIndices[j];
|
|
int32 TriangleID = (j == 0) ? Wedge.Triangles[0] : Wedge.Triangles.Last();
|
|
int32 CurWedgeEdgeID = Mesh.GetTriEdges(TriangleID)[OldWedgeEdgeIndex];
|
|
if (OldWedgeEdgeID != CurWedgeEdgeID)
|
|
{
|
|
if (MeshEdgePairs.Contains(OldWedgeEdgeID) == false)
|
|
{
|
|
MeshEdgePairs.Add(OldWedgeEdgeID, CurWedgeEdgeID);
|
|
MeshEdgePairs.Add(CurWedgeEdgeID, OldWedgeEdgeID);
|
|
}
|
|
Wedge.BorderEdges[j] = CurWedgeEdgeID;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FMeshBevel::UnlinkTerminatorVertex(FDynamicMesh3& Mesh, FBevelVertex& BevelVertex, FDynamicMeshChangeTracker* ChangeTracker)
|
|
{
|
|
MESH_BEVEL_DEBUG_CHECK(BevelVertex.VertexType == EBevelVertexType::TerminatorVertex);
|
|
MESH_BEVEL_DEBUG_CHECK(BevelVertex.Wedges.Num() == 2);
|
|
|
|
if (ChangeTracker)
|
|
{
|
|
ChangeTracker->SaveVertexOneRingTriangles(BevelVertex.VertexID, true);
|
|
}
|
|
|
|
// split the vertex
|
|
FDynamicMesh3::FVertexSplitInfo SplitInfo;
|
|
EMeshResult Result = Mesh.SplitVertex(BevelVertex.VertexID, BevelVertex.Wedges[1].Triangles, SplitInfo);
|
|
if (MESH_BEVEL_DEBUG_ENSURE(Result == EMeshResult::Ok))
|
|
{
|
|
BevelVertex.Wedges[1].WedgeVertex = SplitInfo.NewVertex;
|
|
|
|
// update end start/end pairs for each wedge, and save in the edge correspondence map.
|
|
// Note that this is the same block as in UnlinkJunctionVertex
|
|
int32 NumWedges = BevelVertex.Wedges.Num();
|
|
for (int32 k = 0; k < NumWedges; ++k)
|
|
{
|
|
FOneRingWedge& Wedge = BevelVertex.Wedges[k];
|
|
for (int32 j = 0; j < 2; ++j)
|
|
{
|
|
int32 OldWedgeEdgeID = Wedge.BorderEdges[j];
|
|
int32 OldWedgeEdgeIndex = Wedge.BorderEdgeTriEdgeIndices[j];
|
|
int32 TriangleID = (j == 0) ? Wedge.Triangles[0] : Wedge.Triangles.Last();
|
|
int32 CurWedgeEdgeID = Mesh.GetTriEdges(TriangleID)[OldWedgeEdgeIndex];
|
|
if (OldWedgeEdgeID != CurWedgeEdgeID)
|
|
{
|
|
if (MeshEdgePairs.Contains(OldWedgeEdgeID) == false)
|
|
{
|
|
MeshEdgePairs.Add(OldWedgeEdgeID, CurWedgeEdgeID);
|
|
MeshEdgePairs.Add(CurWedgeEdgeID, OldWedgeEdgeID);
|
|
}
|
|
Wedge.BorderEdges[j] = CurWedgeEdgeID;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
void FMeshBevel::FixUpUnlinkedBevelEdges(FDynamicMesh3& Mesh)
|
|
{
|
|
// Rewrite vertex IDs in BevelEdge vertex lists to correctly match the vertices in the new unlinked wedges.
|
|
// We did not know these new vertices in UnlinkBevelEdgeInterior() because we didn't unlink the vertices
|
|
// into wedges until afterwards.
|
|
for (FBevelEdge& Edge : Edges)
|
|
{
|
|
// If this is a one-mesh-edge edge, then Edge.NewMeshEdges is incorrect because we could
|
|
// not actually unlink the edge in UnlinkBevelEdgeInterior (since there were no interior vertices).
|
|
// But now that we have unlinked the vertices, the other edge now exists, and we can find it here.
|
|
if (Edge.MeshEdges.Num() == 1)
|
|
{
|
|
int32* FoundOtherEdge = MeshEdgePairs.Find(Edge.MeshEdges[0]);
|
|
MESH_BEVEL_DEBUG_CHECK(FoundOtherEdge != nullptr);
|
|
if (FoundOtherEdge != nullptr)
|
|
{
|
|
Edge.NewMeshEdges[0] = *FoundOtherEdge;
|
|
}
|
|
else
|
|
{
|
|
continue; // something went wrong, loop below will break things
|
|
}
|
|
}
|
|
|
|
// process start and end vertices of the path
|
|
for (int32 j = 0; j < 2; ++j)
|
|
{
|
|
int32 vi = (j == 0) ? 0 : (Edge.MeshVertices.Num()-1);
|
|
int32 ei = (j == 0) ? 0 : (Edge.MeshEdges.Num()-1);
|
|
const FBevelVertex* BevelVertex = GetBevelVertexFromVertexID(Edge.MeshVertices[vi]);
|
|
int32& V0 = Edge.MeshVertices[vi];
|
|
int32& V1 = Edge.NewMeshVertices[vi];
|
|
int32 E0 = Edge.MeshEdges[ei], E1 = Edge.NewMeshEdges[ei];
|
|
bool bFoundV0 = false, bFoundV1 = false;
|
|
for (const FOneRingWedge& Wedge : BevelVertex->Wedges)
|
|
{
|
|
for (int32 tid : Wedge.Triangles)
|
|
{
|
|
FIndex3i TriEdges = Mesh.GetTriEdges(tid);
|
|
if (TriEdges.Contains(E0) && bFoundV0 == false)
|
|
{
|
|
MESH_BEVEL_DEBUG_CHECK( Mesh.GetEdgeV(E0).Contains(Wedge.WedgeVertex) );
|
|
V0 = Wedge.WedgeVertex;
|
|
bFoundV0 = true;
|
|
break;
|
|
}
|
|
else if (TriEdges.Contains(E1) && bFoundV1 == false)
|
|
{
|
|
MESH_BEVEL_DEBUG_CHECK( Mesh.GetEdgeV(E1).Contains(Wedge.WedgeVertex) );
|
|
V1 = Wedge.WedgeVertex;
|
|
bFoundV1 = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void FMeshBevel::DisplaceVertices(FDynamicMesh3& Mesh, double Distance)
|
|
{
|
|
// fallback (very bad) technique to compute an inset vertex
|
|
auto GetDisplacedVertexPos = [Distance](const FDynamicMesh3& Mesh, int32 VertexID) -> FVector3d
|
|
{
|
|
FVector3d CurPos = Mesh.GetVertex(VertexID);
|
|
FVector3d Centroid = FMeshWeights::MeanValueCentroid(Mesh, VertexID);
|
|
return CurPos + Distance * Normalized(Centroid - CurPos);
|
|
};
|
|
|
|
// Basically want to inset any beveled edges inwards into the existing poly-faces.
|
|
// To do this we will solve using our standard inset technique, eg similar to FInsetMeshRegion,
|
|
// which involves computing 'inset lines' for each edge and then finding nearest-points between
|
|
// pairs of lines (which will be the intersection point if the face is planar).
|
|
|
|
// Need to keep track of the inset line sets for open paths because at the corner vertices we
|
|
// will need to combine data from multiple path-line-sets (possibly could do this more efficiently
|
|
// as we only ever need the first and last...but small in context)
|
|
struct FEdgePathInsetLines
|
|
{
|
|
TArray<FLine3d> InsetLines0;
|
|
TArray<FLine3d> InsetLines1;
|
|
};
|
|
TArray<FEdgePathInsetLines> AllInsetLines;
|
|
AllInsetLines.SetNum(Edges.Num());
|
|
|
|
// solve open paths
|
|
for ( int32 k = 0; k < Edges.Num(); ++k)
|
|
{
|
|
FBevelEdge& Edge = Edges[k];
|
|
UE::Geometry::ComputeInsetLineSegmentsFromEdges(Mesh, Edge.MeshEdges, InsetDistance, AllInsetLines[k].InsetLines0);
|
|
UE::Geometry::SolveInsetVertexPositionsFromInsetLines(Mesh, AllInsetLines[k].InsetLines0, Edge.MeshVertices, Edge.NewPositions0, false);
|
|
|
|
UE::Geometry::ComputeInsetLineSegmentsFromEdges(Mesh, Edge.NewMeshEdges, InsetDistance, AllInsetLines[k].InsetLines1);
|
|
UE::Geometry::SolveInsetVertexPositionsFromInsetLines(Mesh, AllInsetLines[k].InsetLines1, Edge.NewMeshVertices, Edge.NewPositions1, false);
|
|
}
|
|
|
|
// solve loops
|
|
for (FBevelLoop& Loop : Loops)
|
|
{
|
|
TArray<FLine3d> InsetLines;
|
|
UE::Geometry::ComputeInsetLineSegmentsFromEdges(Mesh, Loop.MeshEdges, InsetDistance, InsetLines);
|
|
UE::Geometry::SolveInsetVertexPositionsFromInsetLines(Mesh, InsetLines, Loop.MeshVertices, Loop.NewPositions0, true);
|
|
|
|
UE::Geometry::ComputeInsetLineSegmentsFromEdges(Mesh, Loop.NewMeshEdges, InsetDistance, InsetLines);
|
|
UE::Geometry::SolveInsetVertexPositionsFromInsetLines(Mesh, InsetLines, Loop.NewMeshVertices, Loop.NewPositions1, true);
|
|
}
|
|
|
|
|
|
// Now solve corners. For corners, we want to find the 1 or 2 inset-lines corresponding
|
|
// to the outgoing bevel-edges at each bevel-vertex-wedge. Unfortunately we do not have
|
|
// a precomputed mapping for this so we currently linear-search over the full edge set for
|
|
// each wedge. Could do in parallel (eg make list of valid wedges first)
|
|
for (FBevelVertex& Vertex : Vertices)
|
|
{
|
|
if ( (Vertex.VertexType == EBevelVertexType::JunctionVertex)
|
|
|| (Vertex.VertexType == EBevelVertexType::TerminatorVertex) )
|
|
{
|
|
int32 NumWedges = Vertex.Wedges.Num();
|
|
for (int32 k = 0; k < NumWedges; ++k)
|
|
{
|
|
FOneRingWedge& Wedge = Vertex.Wedges[k];
|
|
|
|
// collect up set of inset lines relevant to this vertex
|
|
TArray<FLine3d> SolveLines;
|
|
for (int32 j = 0; j < Edges.Num(); ++j)
|
|
{
|
|
if (Edges[j].MeshVertices[0] == Wedge.WedgeVertex)
|
|
{
|
|
SolveLines.Add(AllInsetLines[j].InsetLines0[0]);
|
|
}
|
|
else if (Edges[j].MeshVertices.Last() == Wedge.WedgeVertex)
|
|
{
|
|
SolveLines.Add(AllInsetLines[j].InsetLines0.Last());
|
|
}
|
|
else if (Edges[j].NewMeshVertices[0] == Wedge.WedgeVertex)
|
|
{
|
|
SolveLines.Add(AllInsetLines[j].InsetLines1[0]);
|
|
}
|
|
else if (Edges[j].NewMeshVertices.Last() == Wedge.WedgeVertex)
|
|
{
|
|
SolveLines.Add(AllInsetLines[j].InsetLines1.Last());
|
|
}
|
|
}
|
|
|
|
// now that we have our line set, use it to solve inset position
|
|
FVector3d CurPos = Mesh.GetVertex(Wedge.WedgeVertex);
|
|
if (SolveLines.Num() == 1)
|
|
{
|
|
Wedge.NewPosition = SolveLines[0].NearestPoint(CurPos);
|
|
}
|
|
else if (SolveLines.Num() == 2)
|
|
{
|
|
Wedge.NewPosition = UE::Geometry::SolveInsetVertexPositionFromLinePair(CurPos, SolveLines[0], SolveLines[1]);
|
|
}
|
|
else
|
|
{
|
|
// Is this even possible? #SolveLines should equal #BoundaryEdges, how can we have more than 2 at a vertex??
|
|
// fall back to not-very-good inset technique
|
|
Wedge.NewPosition = GetDisplacedVertexPos(Mesh, Wedge.WedgeVertex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
auto SetDisplacedPositions = [&GetDisplacedVertexPos](FDynamicMesh3& Mesh, TArray<int32>& VerticesIn, TArray<FVector3d>& PositionsIn, int32 InsetStart, int32 InsetEnd)
|
|
{
|
|
int32 NumVertices = VerticesIn.Num();
|
|
if (PositionsIn.Num() == NumVertices)
|
|
{
|
|
int32 Stop = NumVertices - InsetEnd;
|
|
for (int32 k = InsetStart; k < Stop; ++k)
|
|
{
|
|
Mesh.SetVertex(VerticesIn[k], PositionsIn[k]);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
// now bake in new positions
|
|
for (FBevelEdge& Edge : Edges)
|
|
{
|
|
SetDisplacedPositions(Mesh, Edge.MeshVertices, Edge.NewPositions0, Edge.bEndpointBoundaryFlag[0]?0:1, Edge.bEndpointBoundaryFlag[1]?0:1);
|
|
SetDisplacedPositions(Mesh, Edge.NewMeshVertices, Edge.NewPositions1, Edge.bEndpointBoundaryFlag[0]?0:1, Edge.bEndpointBoundaryFlag[1]?0:1);
|
|
}
|
|
for (FBevelLoop& Loop : Loops)
|
|
{
|
|
SetDisplacedPositions(Mesh, Loop.MeshVertices, Loop.NewPositions0, 0, 0);
|
|
SetDisplacedPositions(Mesh, Loop.NewMeshVertices, Loop.NewPositions1, 0, 0);
|
|
}
|
|
for (FBevelVertex& Vertex : Vertices)
|
|
{
|
|
if ( (Vertex.VertexType == EBevelVertexType::JunctionVertex)
|
|
|| (Vertex.VertexType == EBevelVertexType::TerminatorVertex) )
|
|
{
|
|
for (FOneRingWedge& Wedge : Vertex.Wedges)
|
|
{
|
|
Mesh.SetVertex(Wedge.WedgeVertex, Wedge.NewPosition);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void FMeshBevel::AppendJunctionVertexPolygon(FDynamicMesh3& Mesh, FBevelVertex& Vertex)
|
|
{
|
|
MESH_BEVEL_DEBUG_CHECK(Vertex.VertexType == EBevelVertexType::JunctionVertex);
|
|
|
|
// UnlinkJunctionVertex() split the terminator vertex into N vertices, one for each
|
|
// (now disconnected) triangle-wedge. The wedges are ordered such that their wedge-vertices
|
|
// define a polygon with correct winding, so we can just mesh it and append the triangles
|
|
|
|
TArray<FVector3d> PolygonPoints;
|
|
for (FOneRingWedge& Wedge : Vertex.Wedges)
|
|
{
|
|
PolygonPoints.Add(Mesh.GetVertex(Wedge.WedgeVertex));
|
|
}
|
|
|
|
TArray<FIndex3i> Triangles;
|
|
PolygonTriangulation::TriangulateSimplePolygon<double>(PolygonPoints, Triangles);
|
|
Vertex.NewGroupID = Mesh.AllocateTriangleGroup();
|
|
for (FIndex3i Tri : Triangles)
|
|
{
|
|
int32 A = Vertex.Wedges[Tri.A].WedgeVertex;
|
|
int32 B = Vertex.Wedges[Tri.B].WedgeVertex;
|
|
int32 C = Vertex.Wedges[Tri.C].WedgeVertex;
|
|
int32 tid = Mesh.AppendTriangle(A, B, C, Vertex.NewGroupID);
|
|
if (Mesh.IsTriangle(tid))
|
|
{
|
|
Vertex.NewTriangles.Add(tid);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMeshBevel::AppendTerminatorVertexTriangle(FDynamicMesh3& Mesh, FBevelVertex& Vertex)
|
|
{
|
|
MESH_BEVEL_DEBUG_CHECK(Vertex.VertexType == EBevelVertexType::TerminatorVertex);
|
|
|
|
// UnlinkTerminatorVertex() opened up a triangle-shaped hole adjacent to the incoming edge quad-strip
|
|
// at the terminator vertex. The Wedges of the terminator vertex contain the vertex IDs of the two
|
|
// verts on the quad-strip edge. We need the third vertex. We stored [SplitEdge, FarVertexID] in
|
|
// .TerminatorInfo, however FarVertexID may have become a different vertex when we unlinked other
|
|
// vertices. So, we will try to use SplitEdge to find it.
|
|
// If this turns out to have problems, basically the QuadEdgeID is on the boundary of a 3-edge hole,
|
|
// and so it should be straightforward to find the two other boundary edges and that gives the vertex.
|
|
int32 RingSplitEdgeID = Vertex.TerminatorInfo.A;
|
|
if (Mesh.IsEdge(RingSplitEdgeID))
|
|
{
|
|
FIndex2i SplitEdgeV = Mesh.GetEdgeV(RingSplitEdgeID);
|
|
int32 FarVertexID = SplitEdgeV.OtherElement(Vertex.VertexID);
|
|
|
|
int32 QuadEdgeID = Mesh.FindEdge(Vertex.Wedges[0].WedgeVertex, Vertex.Wedges[1].WedgeVertex);
|
|
if (Mesh.IsEdge(QuadEdgeID))
|
|
{
|
|
FIndex2i QuadEdgeV = Mesh.GetOrientedBoundaryEdgeV(QuadEdgeID);
|
|
// should have computed this GroupID in initial setup
|
|
int32 UseGroupID = (Vertex.NewGroupID >= 0) ? Vertex.NewGroupID : Mesh.AllocateTriangleGroup();
|
|
int32 tid = Mesh.AppendTriangle(QuadEdgeV.B, QuadEdgeV.A, FarVertexID, UseGroupID);
|
|
if (Mesh.IsTriangle(tid))
|
|
{
|
|
Vertex.NewTriangles.Add(tid);
|
|
}
|
|
else
|
|
{
|
|
MESH_BEVEL_DEBUG_CHECK(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MESH_BEVEL_DEBUG_CHECK(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void FMeshBevel::AppendTerminatorVertexPairQuad(FDynamicMesh3& Mesh, FBevelVertex& Vertex0, FBevelVertex& Vertex1)
|
|
{
|
|
MESH_BEVEL_DEBUG_CHECK(Vertex0.VertexType == EBevelVertexType::TerminatorVertex);
|
|
MESH_BEVEL_DEBUG_CHECK(Vertex1.VertexType == EBevelVertexType::TerminatorVertex);
|
|
|
|
// This is a variant of AppendTerminatorVertexTriangle that handles the case where basically two
|
|
// Terminator Vertices are directly connected by a non-beveled mesh edge that was used as the ring-split-edge.
|
|
// Since both sides were opened, we have a quad-shaped hole instead of a triangle-shaped hole, with a quad-edge
|
|
// at each end. The quad can be filled directly, we just need to sort out ordering/etc
|
|
|
|
// does not seem like we need to do anything w/ the TerminatorInfo here, we can get everything from wedges
|
|
int32 QuadEdgeID0 = Mesh.FindEdge(Vertex0.Wedges[0].WedgeVertex, Vertex0.Wedges[1].WedgeVertex);
|
|
int32 QuadEdgeID1 = Mesh.FindEdge(Vertex1.Wedges[0].WedgeVertex, Vertex1.Wedges[1].WedgeVertex);
|
|
MESH_BEVEL_DEBUG_CHECK(Mesh.IsEdge(QuadEdgeID0) && Mesh.IsEdge(QuadEdgeID1));
|
|
FIndex2i QuadEdgeV0 = Mesh.GetOrientedBoundaryEdgeV(QuadEdgeID0);
|
|
FIndex2i QuadEdgeV1 = Mesh.GetOrientedBoundaryEdgeV(QuadEdgeID1);
|
|
|
|
// make sure that the two opposing/connecting edges exist
|
|
MESH_BEVEL_DEBUG_CHECK(
|
|
Mesh.FindEdge(QuadEdgeV0.A, QuadEdgeV1.B) != IndexConstants::InvalidID &&
|
|
Mesh.FindEdge(QuadEdgeV0.B, QuadEdgeV1.A) != IndexConstants::InvalidID);
|
|
|
|
// BIASED? should have computed this GroupID in initial setup
|
|
int32 UseGroupID = (Vertex0.NewGroupID >= 0) ? Vertex0.NewGroupID : Mesh.AllocateTriangleGroup();
|
|
|
|
// quad order is V0.B, V0.A, V1.B, V1.A
|
|
int32 tid0 = Mesh.AppendTriangle(QuadEdgeV0.B, QuadEdgeV0.A, QuadEdgeV1.B, UseGroupID);
|
|
MESH_BEVEL_DEBUG_CHECK(tid0 >= 0);
|
|
if (Mesh.IsTriangle(tid0))
|
|
{
|
|
Vertex0.NewTriangles.Add(tid0);
|
|
}
|
|
int32 tid1 = Mesh.AppendTriangle(QuadEdgeV0.B, QuadEdgeV1.B, QuadEdgeV1.A, UseGroupID);
|
|
MESH_BEVEL_DEBUG_CHECK(tid1 >= 0);
|
|
if (Mesh.IsTriangle(tid1))
|
|
{
|
|
Vertex1.NewTriangles.Add(tid1);
|
|
}
|
|
}
|
|
|
|
|
|
void FMeshBevel::AppendEdgeQuads(FDynamicMesh3& Mesh, FBevelEdge& Edge)
|
|
{
|
|
int32 NumEdges = Edge.MeshEdges.Num();
|
|
if (NumEdges != Edge.NewMeshEdges.Num())
|
|
{
|
|
return;
|
|
}
|
|
|
|
Edge.NewGroupID = Mesh.AllocateTriangleGroup();
|
|
|
|
// At this point each edge-span should be fully disconnected into a set of paired edges,
|
|
// so we can trivially join each edge pair with a quad.
|
|
for (int32 k = 0; k < NumEdges; ++k)
|
|
{
|
|
int32 EdgeID0 = Edge.MeshEdges[k];
|
|
int32 EdgeID1 = Edge.NewMeshEdges[k];
|
|
|
|
// in certain cases, like bevel topo-edges with a single mesh-edge, we would not
|
|
// have been able to construct the "other" mesh edge when processing the topo-edge
|
|
// (where .NewMeshEdges is computed), it would only have been created when processing the
|
|
// junction vertex. Currently we do not go back and update .NewMeshEdges in that case, but
|
|
// we do store the edge-pair-correspondence in the MeshEdgePairs map.
|
|
if (EdgeID0 == EdgeID1)
|
|
{
|
|
int32* FoundEdgeID1 = MeshEdgePairs.Find(EdgeID0);
|
|
if (FoundEdgeID1 != nullptr)
|
|
{
|
|
EdgeID1 = *FoundEdgeID1;
|
|
}
|
|
}
|
|
|
|
FIndex2i QuadTris(IndexConstants::InvalidID, IndexConstants::InvalidID);
|
|
if (EdgeID0 != EdgeID1 && Mesh.IsEdge(EdgeID1) )
|
|
{
|
|
FIndex2i EdgeV0 = Mesh.GetOrientedBoundaryEdgeV(EdgeID0);
|
|
FIndex2i EdgeV1 = Mesh.GetOrientedBoundaryEdgeV(EdgeID1);
|
|
if (EdgeV0.Contains(EdgeV1.A) || EdgeV0.Contains(EdgeV1.B))
|
|
{
|
|
// If we hit this case, it means that Edge0 and Edge1 are still connected at one end, and
|
|
// so cannot be connected by a Quad, they can only be connected by a single triangle.
|
|
// It is unclear how we end up in this situation, it does occur somewhat regularly in complex
|
|
// geometry scripts though (eg see UE-157531 for a potential repro).
|
|
int32 OtherV = EdgeV0.Contains(EdgeV1.A) ? EdgeV1.B : EdgeV1.A;
|
|
QuadTris.A = Mesh.AppendTriangle(EdgeV0.B, EdgeV0.A, OtherV, Edge.NewGroupID);
|
|
QuadTris.B = IndexConstants::InvalidID;
|
|
}
|
|
else
|
|
{
|
|
QuadTris.A = Mesh.AppendTriangle(EdgeV0.B, EdgeV0.A, EdgeV1.B, Edge.NewGroupID);
|
|
QuadTris.B = Mesh.AppendTriangle(EdgeV1.B, EdgeV1.A, EdgeV0.B, Edge.NewGroupID);
|
|
}
|
|
}
|
|
Edge.StripQuads.Add(QuadTris);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void FMeshBevel::AppendLoopQuads(FDynamicMesh3& Mesh, FBevelLoop& Loop)
|
|
{
|
|
int32 NumEdges = Loop.MeshEdges.Num();
|
|
if (NumEdges != Loop.NewMeshEdges.Num())
|
|
{
|
|
return;
|
|
}
|
|
|
|
auto GetGroupKey = [&Mesh, &Loop](int32 k)
|
|
{
|
|
FIndex2i EdgeTris = Loop.MeshEdgeTris[k];
|
|
int32 Group0 = Mesh.GetTriangleGroup(EdgeTris.A);
|
|
int32 Group1 = Mesh.IsTriangle(EdgeTris.B) ? Mesh.GetTriangleGroup(EdgeTris.B) : -1;
|
|
return FIndex2i(FMath::Max(Group0, Group1), FMath::Min(Group0, Group1));
|
|
};
|
|
|
|
TMap<FIndex2i, int> NewGroupIDs;
|
|
for (int32 k = 0; k < NumEdges; ++k)
|
|
{
|
|
FIndex2i GroupKey = GetGroupKey(k);
|
|
if (NewGroupIDs.Contains(GroupKey) == false)
|
|
{
|
|
NewGroupIDs.Add(GroupKey, Mesh.AllocateTriangleGroup());
|
|
Loop.NewGroupIDs.Add(NewGroupIDs[GroupKey]);
|
|
}
|
|
}
|
|
|
|
// At this point each edge-span should be fully disconnected into a set of paired edges,
|
|
// so we can trivially join each edge pair with a quad.
|
|
for (int32 k = 0; k < NumEdges; ++k)
|
|
{
|
|
int32 EdgeID0 = Loop.MeshEdges[k];
|
|
int32 EdgeID1 = Loop.NewMeshEdges[k];
|
|
|
|
// case that happens in AppendEdgeQuads() should never happen for loops...
|
|
|
|
FIndex2i QuadTris(IndexConstants::InvalidID, IndexConstants::InvalidID);
|
|
if (EdgeID0 != EdgeID1 && Mesh.IsEdge(EdgeID1))
|
|
{
|
|
FIndex2i GroupKey = GetGroupKey(k);
|
|
int32 NewGroupID = NewGroupIDs[GroupKey];
|
|
|
|
FIndex2i EdgeV0 = Mesh.GetOrientedBoundaryEdgeV(EdgeID0);
|
|
FIndex2i EdgeV1 = Mesh.GetOrientedBoundaryEdgeV(EdgeID1);
|
|
QuadTris.A = Mesh.AppendTriangle(EdgeV0.B, EdgeV0.A, EdgeV1.B, NewGroupID);
|
|
QuadTris.B = Mesh.AppendTriangle(EdgeV1.B, EdgeV1.A, EdgeV0.B, NewGroupID);
|
|
}
|
|
Loop.StripQuads.Add(QuadTris);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void FMeshBevel::CreateBevelMeshing(FDynamicMesh3& Mesh)
|
|
{
|
|
for (FBevelVertex& Vertex : Vertices)
|
|
{
|
|
if (Vertex.VertexType == EBevelVertexType::JunctionVertex)
|
|
{
|
|
if (Vertex.Wedges.Num() > 2)
|
|
{
|
|
AppendJunctionVertexPolygon(Mesh, Vertex);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (FBevelEdge& Edge : Edges)
|
|
{
|
|
AppendEdgeQuads(Mesh, Edge);
|
|
}
|
|
for (FBevelLoop& Loop : Loops)
|
|
{
|
|
AppendLoopQuads(Mesh, Loop);
|
|
}
|
|
|
|
// easier to do terminators last so that we can use quad edge to orient the triangle
|
|
TSet<FIndex2i> HandledQuadVtxPairs;
|
|
for (FBevelVertex& Vertex : Vertices)
|
|
{
|
|
if (Vertex.VertexType == EBevelVertexType::TerminatorVertex)
|
|
{
|
|
if (Vertex.ConnectedBevelVertex >= 0)
|
|
{
|
|
FBevelVertex& OtherVertex = Vertices[Vertex.ConnectedBevelVertex];
|
|
FIndex2i VtxPair(Vertex.VertexID, OtherVertex.VertexID);
|
|
VtxPair.Sort();
|
|
if (HandledQuadVtxPairs.Contains(VtxPair) == false)
|
|
{
|
|
AppendTerminatorVertexPairQuad(Mesh, Vertex, OtherVertex);
|
|
HandledQuadVtxPairs.Add(VtxPair);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AppendTerminatorVertexTriangle(Mesh, Vertex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void FMeshBevel::ComputeNormals(FDynamicMesh3& Mesh)
|
|
{
|
|
if (Mesh.HasAttributes() == false)
|
|
{
|
|
return;
|
|
}
|
|
FDynamicMeshNormalOverlay* NormalOverlay = Mesh.Attributes()->PrimaryNormals();
|
|
|
|
auto SetNormalsOnTriRegion = [NormalOverlay](const TArray<int32>& Triangles)
|
|
{
|
|
if (Triangles.Num() > 0)
|
|
{
|
|
FMeshNormals::InitializeOverlayRegionToPerVertexNormals(NormalOverlay, Triangles);
|
|
}
|
|
};
|
|
|
|
for (FBevelVertex& Vertex : Vertices)
|
|
{
|
|
SetNormalsOnTriRegion(Vertex.NewTriangles);
|
|
}
|
|
|
|
TArray<int32> TriList;
|
|
for (FBevelEdge& Edge : Edges)
|
|
{
|
|
UELocal::QuadsToTris(Mesh, Edge.StripQuads, TriList, true);
|
|
SetNormalsOnTriRegion(TriList);
|
|
}
|
|
for (FBevelLoop& Loop : Loops)
|
|
{
|
|
UELocal::QuadsToTris(Mesh, Loop.StripQuads, TriList, true);
|
|
SetNormalsOnTriRegion(TriList);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void FMeshBevel::ComputeUVs(FDynamicMesh3& Mesh)
|
|
{
|
|
if (Mesh.HasAttributes() == false)
|
|
{
|
|
return;
|
|
}
|
|
FDynamicMeshUVOverlay* UVOverlay = Mesh.Attributes()->PrimaryUV();
|
|
|
|
auto SetUVsOnTriRegion = [&Mesh, UVOverlay](const TArray<int32>& Triangles)
|
|
{
|
|
if (Triangles.Num() > 0)
|
|
{
|
|
UE::Geometry::ComputeArbitraryTrianglePatchUVs(Mesh, *UVOverlay, Triangles);
|
|
}
|
|
};
|
|
|
|
|
|
TArray<int32> TriList;
|
|
for (FBevelEdge& Edge : Edges)
|
|
{
|
|
UELocal::QuadsToTris(Mesh, Edge.StripQuads, TriList, true);
|
|
SetUVsOnTriRegion(TriList);
|
|
}
|
|
for (FBevelLoop& Loop : Loops)
|
|
{
|
|
UELocal::QuadsToTris(Mesh, Loop.StripQuads, TriList, true);
|
|
SetUVsOnTriRegion(TriList);
|
|
}
|
|
|
|
// do vertices last because until edges have UVs, the vertex polygons have no neighbour UVs islands
|
|
for (FBevelVertex& Vertex : Vertices)
|
|
{
|
|
SetUVsOnTriRegion(Vertex.NewTriangles);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void FMeshBevel::ComputeMaterialIDs(FDynamicMesh3& Mesh)
|
|
{
|
|
if (Mesh.HasAttributes() == false || Mesh.Attributes()->HasMaterialID() == false)
|
|
{
|
|
return;
|
|
}
|
|
FDynamicMeshMaterialAttribute* MaterialIDs = Mesh.Attributes()->GetMaterialID();
|
|
|
|
if (MaterialIDMode == EMaterialIDMode::ConstantMaterialID)
|
|
{
|
|
for (int32 tid : NewTriangles)
|
|
{
|
|
MaterialIDs->SetValue(tid, SetConstantMaterialID);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto SetQuadMaterial = [&Mesh, MaterialIDs](const FIndex2i& Quad, int32 MaterialID)
|
|
{
|
|
if (Quad.A >= 0)
|
|
{
|
|
MaterialIDs->SetValue(Quad.A, MaterialID);
|
|
}
|
|
if (Quad.B >= 0)
|
|
{
|
|
MaterialIDs->SetValue(Quad.B, MaterialID);
|
|
}
|
|
};
|
|
|
|
// Try to set materials on the new triangles along a beveled edge based on the adjacent pre-bevel triangles.
|
|
// Could be improved to at least be more consistent in ambiguous cases
|
|
auto SetEdgeMaterials = [&Mesh, MaterialIDs, this, SetQuadMaterial](const TArray<FIndex2i>& StripQuads, const TArray<FIndex2i>& EdgeTris)
|
|
{
|
|
int32 NumEdges = EdgeTris.Num();
|
|
if (StripQuads.Num() == NumEdges)
|
|
{
|
|
TArray<int32> SawMaterialIDs;
|
|
TArray<int32> AmbiguousEdges;
|
|
for (int32 k = 0; k < StripQuads.Num(); ++k)
|
|
{
|
|
FIndex2i NbrTris = EdgeTris[k];
|
|
int MatIDA = MaterialIDs->GetValue(NbrTris.A);
|
|
int MatIDB = (NbrTris.A >= 0) ? MaterialIDs->GetValue(NbrTris.B) : MatIDA;
|
|
SawMaterialIDs.AddUnique(MatIDA);
|
|
SawMaterialIDs.AddUnique(MatIDB);
|
|
int SetMaterialID = (MatIDA == MatIDB) ? MatIDA : SetConstantMaterialID;
|
|
if (MatIDA != MatIDB)
|
|
{
|
|
if (MaterialIDMode == EMaterialIDMode::InferMaterialID_ConstantIfAmbiguous)
|
|
{
|
|
SetMaterialID = SetConstantMaterialID;
|
|
}
|
|
else
|
|
{
|
|
AmbiguousEdges.Add(k);
|
|
}
|
|
}
|
|
SetQuadMaterial(StripQuads[k], SetMaterialID);
|
|
}
|
|
|
|
if (AmbiguousEdges.Num() > 0)
|
|
{
|
|
SawMaterialIDs.Sort();
|
|
if (AmbiguousEdges.Num() == NumEdges) // if all ambigous, just pick one
|
|
{
|
|
for (int32 k : AmbiguousEdges)
|
|
{
|
|
SetQuadMaterial(StripQuads[k], SawMaterialIDs[0]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO: what we probably want to do here is "infill" from known neighbours.
|
|
// for now we will just punt and pick one
|
|
for (int32 k : AmbiguousEdges)
|
|
{
|
|
SetQuadMaterial(StripQuads[k], SawMaterialIDs[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
for (const FIndex2i& Quad : StripQuads)
|
|
{
|
|
SetQuadMaterial(Quad, SetConstantMaterialID);
|
|
}
|
|
}
|
|
};
|
|
|
|
for (FBevelEdge& Edge : Edges)
|
|
{
|
|
SetEdgeMaterials(Edge.StripQuads, Edge.MeshEdgeTris);
|
|
}
|
|
for (FBevelLoop& Loop : Loops)
|
|
{
|
|
SetEdgeMaterials(Loop.StripQuads, Loop.MeshEdgeTris);
|
|
}
|
|
|
|
|
|
// find all the unique material IDs of neighbours of the Triangles list (that are not in Triangles list) and
|
|
// return (MaterialID, NbrTriCount) tuples as a pair of lists
|
|
auto CountUniqueBorderMaterialIDs = [&](const FDynamicMesh3& Mesh, const FDynamicMeshMaterialAttribute& MaterialAttrib, const TArray<int32>& Triangles, TArray<int32>& MaterialIDs, TArray<int32>& Counts)
|
|
{
|
|
MaterialIDs.Reset(); Counts.Reset();
|
|
for (int32 tid : Triangles)
|
|
{
|
|
FIndex3i TriNbrs = Mesh.GetTriNeighbourTris(tid);
|
|
for (int32 j = 0; j < 3; ++j)
|
|
{
|
|
int32 NbrTriangleID = TriNbrs[j];
|
|
if ( Mesh.IsTriangle(NbrTriangleID) == false || Triangles.Contains(NbrTriangleID) )
|
|
{
|
|
continue;
|
|
}
|
|
int MatID = MaterialAttrib.GetValue(NbrTriangleID);
|
|
int32 Index = MaterialIDs.AddUnique(MatID);
|
|
if (Counts.Num() != MaterialIDs.Num())
|
|
{
|
|
Counts.Add(0);
|
|
Counts[Index]++;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// For each bevel-vertex-polygon, pick the nbr material ID that was most frequent.
|
|
// Terminator vertices are also handled this way, which is not ideal, should probably
|
|
// ignore the new 'edge' faces for the terminator vertex
|
|
for (FBevelVertex& Vertex : Vertices)
|
|
{
|
|
TArray<int32> NbrMaterialIDs, NbrMaterialIDCounts;
|
|
CountUniqueBorderMaterialIDs(Mesh, *MaterialIDs, Vertex.NewTriangles, NbrMaterialIDs, NbrMaterialIDCounts);
|
|
int32 SetMaterialID = SetConstantMaterialID;
|
|
if (NbrMaterialIDs.Num() > 0)
|
|
{
|
|
int32 MinIndex = 0;
|
|
for (int32 k = 1; k < NbrMaterialIDs.Num(); ++k)
|
|
{
|
|
if (NbrMaterialIDCounts[k] < NbrMaterialIDCounts[MinIndex])
|
|
{
|
|
MinIndex = k;
|
|
}
|
|
}
|
|
SetMaterialID = NbrMaterialIDs[MinIndex];
|
|
}
|
|
for (int32 tid : Vertex.NewTriangles)
|
|
{
|
|
MaterialIDs->SetValue(tid, SetMaterialID);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
}
|