Files
UnrealEngine/Engine/Plugins/Runtime/GeometryProcessing/Source/DynamicMesh/Public/SubRegionRemesher.h
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

579 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Remesher.h"
#include "Util/BufferUtil.h"
#include "Util/UniqueIndexSet.h"
#include "DynamicMesh/DynamicMeshChangeTracker.h"
#include "MeshWeights.h"
#include "Async/ParallelFor.h"
namespace UE
{
namespace Geometry
{
/**
* FSubRegionRemesher is an extension of FRemesher that allows for constraining remeshing to
* a localized region of a mesh. Currently this is initialized from a Vertex ROI.
*
* @warning Currently "boundary" edges of the ROI that are split will result in the ROI
* growing to include both new edges created by the split.
*
*/
class FSubRegionRemesher : public FRemesher
{
protected:
/**
* Set of vertices in ROI. You add vertices here initially, then
* we will update the list during each Remesh pass
*/
TSet<int> VertexROI;
// active set of edges we will consider for remeshing. This set is updated on
// each edge flip/split/collapse, but is not used during the pass.
TSet<int> EdgeROI;
// Active set of triangles. This is conservative, ideally contains one-rings of all
// edge-vertices of EdgeROI set, but may include additional triangles accumulated over time
// (should *not* contain any triangles that no longer exist)
TSet<int> TriangleROI;
// static list of edges to consider during a pass (set in UpdateROI which must be called each pass)
TArray<int> Edges;
// index of current edge in .Edges we are at in StartEdges/GetNextEdge modulo-iteration
int CurEdge = -1;
// set of triangles removed in last pass. Enable this by calling BeginTrackRemovedTrisInPass()
TSet<int> RemovedLastPassTris;
// When deleted, remove PRAGMA_DISABLE_DEPRECATION_WARNINGS / PRAGMA_ENABLE_DEPRECATION_WARNINGS from ~FSubRegionRemesher()
UE_DEPRECATED(5.6, "This member is no longer used")
FUniqueIndexSet InitialROIEdgeVertices;
// temporary data structure used to quickly convert vertex set to triangle set. Currenlty it is faster
// to construct this and then copy to a properly-sized TSet, than to incrementally build the TSet
FUniqueIndexSet InitialROITriangles;
// controls whether RemovedLastPass will be populated
bool bTrackRemoved = false;
// counters for making sure that UpdateROI has been called
uint32 LastUpdateROICounter = 0;
uint32 LastRemeshPassCounter = 0;
public:
FSubRegionRemesher(FDynamicMesh3* Mesh) : FRemesher(Mesh)
{
VertexControlF = [this](int vid) {
return this->VertexFilter(vid);
};
}
PRAGMA_DISABLE_DEPRECATION_WARNINGS
virtual ~FSubRegionRemesher() {}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
void Reset()
{
VertexROI.Reset();
EdgeROI.Reset();
TriangleROI.Reset();
Edges.Reset();
RemovedLastPassTris.Reset();
CurEdge = -1;
LastUpdateROICounter = LastRemeshPassCounter = 0;
}
void SetInitialVertexROI(const TSet<int>& Vertices)
{
VertexROI = Vertices;
}
void SetInitialVertexROI(TSet<int>&& Vertices)
{
VertexROI = MoveTemp(Vertices);
}
void SetInitialVertexROI(const TArray<int>& Vertices)
{
VertexROI.Reset();
VertexROI.Reserve(Vertices.Num());
for (int32 tid : Vertices)
{
VertexROI.Add(tid);
}
}
/**
* Initialize edge-subregion ROI from the VertexROI member that has been externally initialized.
* Edge ROI is initialized to fully-contained edges (i.e. both endpoints are in VertexROI).
*/
void InitializeFromVertexROI()
{
const FDynamicMesh3* UseMesh = GetMesh();
{
TRACE_CPUPROFILER_EVENT_SCOPE(InitializeFromVertexROI_1Init);
EdgeROI.Reset();
TriangleROI.Reset();
// Convert vertex ROI to set of contained edges.
for (int VertIdx : VertexROI)
{
UseMesh->EnumerateVertexEdges(VertIdx, [this, UseMesh, VertIdx](int32 eid)
{
FIndex2i EdgeV = UseMesh->GetEdgeV(eid);
int32 OtherVid = IndexUtil::FindEdgeOtherVertex(EdgeV, VertIdx);
if (VertexROI.Contains(OtherVid))
{
EdgeROI.Add(eid);
}
});
}
UpdateROI();
}
{
TRACE_CPUPROFILER_EVENT_SCOPE(InitializeFromVertexROI_4Triangles);
// Convert new vertex ROI built above into new one-ring triangle ROI.
// This conservatively guarantees that any triangle affected by a mesh edge op
// (eg split/collapse/flip) is contained within this triangle set
InitialROITriangles.Initialize(UseMesh->MaxTriangleID());
for (int vid : VertexROI)
{
UseMesh->EnumerateVertexEdges(vid, [this](int32 eid)
{
FIndex2i EdgeT = Mesh->GetEdgeT(eid);
InitialROITriangles.Add(EdgeT.A);
if (EdgeT.B != IndexConstants::InvalidID) InitialROITriangles.Add(EdgeT.B);
});
}
InitialROITriangles.Collect(TriangleROI);
}
}
void InitializeFromTriangleROI(const TSet<int>& InTriangleROI)
{
EdgeROI.Reset();
VertexROI.Reset();
TriangleROI = InTriangleROI;
for (int TriangleIndex : TriangleROI)
{
FIndex3i Vertices = Mesh->GetTriangle(TriangleIndex);
VertexROI.Add(Vertices[0]);
VertexROI.Add(Vertices[1]);
VertexROI.Add(Vertices[2]);
FIndex3i TriEdges = Mesh->GetTriEdges(TriangleIndex);
EdgeROI.Add(TriEdges[0]);
EdgeROI.Add(TriEdges[1]);
EdgeROI.Add(TriEdges[2]);
}
}
/**
* Update the internal data structures in preparation for a call to FRemesher::BasicRemeshPass.
* This must be called before each remesh pass!
*/
void UpdateROI()
{
Edges.Reset(EdgeROI.Num());
for (int eid : EdgeROI)
{
Edges.Add(eid);
}
LastUpdateROICounter++;
}
/**
* Call before BasicRemeshPass() to enable tracking of removed triangles
*/
void BeginTrackRemovedTrisInPass()
{
RemovedLastPassTris.Reset();
bTrackRemoved = true;
}
/**
* Call after BasicRemeshPass() to disable and return tracking of removed triangles
* @return set of removed triangles. This set will be cleared on next call to BeginTrackRemovedTrisInPass()
*/
const TSet<int32>& EndTrackRemovedTrisInPass()
{
bTrackRemoved = false;
return RemovedLastPassTris;
}
/**
* forwards to FRemesher::BasicRemeshPass
*/
virtual void BasicRemeshPass() override
{
check(LastRemeshPassCounter != LastUpdateROICounter);
LastRemeshPassCounter = LastUpdateROICounter;
FRemesher::BasicRemeshPass();
}
/**
* Tell a MeshChangeTracker about the set of triangles that we might modify in the next remesh pass.
* This could include one-rings of either side of an edge in the ROI, if we collapse.
*/
void SaveActiveROI(FDynamicMeshChangeTracker* Change)
{
for (int tid : TriangleROI)
{
Change->SaveTriangle(tid, true);
}
}
/**
* @return
*/
const TSet<int>& GetCurrentVertexROI() const
{
return VertexROI;
}
/**
* @return set of triangles that contains edge ROI (note: may also contain additional triangles)
*/
const TSet<int>& GetCurrentTriangleROI() const
{
return TriangleROI;
}
/**
* @return set of edges in current edge ROI
*/
const TSet<int>& GetCurrentEdgeROI() const
{
return EdgeROI;
}
/**
* @return current edge array.
* @warning This is only valid after calling UpdateROI() and before calling BasicRemeshPass().
*/
const TArray<int>& GetCurrentEdgeArray() const
{
return Edges;
}
/**
* Extract final triangle ROI. After calling this function, the ROI will be cleared, and must be re-initialized.
* This can be used to avoid having to copy the TSet.
* @return set of triangles that contains edge ROI (note: may also contain additional triangles)
*/
TSet<int>&& ExtractFinalTriangleROI()
{
EdgeROI.Reset();
VertexROI.Reset();
return MoveTemp(TriangleROI);
}
//
// specialization of Remesher functionality
//
protected:
virtual int StartEdges() override
{
CurEdge = 0;
return (Edges.Num() > 0) ? Edges[CurEdge] : IndexConstants::InvalidID;
}
virtual int GetNextEdge(int CurEdgeID, bool& bDone) override
{
CurEdge++;
while ((CurEdge < Edges.Num()) && (!EdgeROI.Contains(Edges[CurEdge]))) // EdgeROI may have lost some edges due to earlier operations
{
++CurEdge;
}
if (CurEdge >= Edges.Num())
{
bDone = true;
return -1;
}
else
{
bDone = false;
return Edges[CurEdge];
}
}
virtual void OnEdgeSplit(int EdgeID, int VertexA, int VertexB, const FDynamicMesh3::FEdgeSplitInfo& SplitInfo) override
{
VertexROI.Add(SplitInfo.NewVertex);
EdgeROI.Add(SplitInfo.NewEdges.A);
// By always adding new edges to ROI, we are potentially 'growing' the ROI here.
// Could filter out these edges by checking if other vtx is in VertexROI?
EdgeROI.Add(SplitInfo.NewEdges.B);
AddEdgeToTriangleROI(SplitInfo.NewEdges.B);
if (SplitInfo.NewEdges.C != FDynamicMesh3::InvalidID)
{
EdgeROI.Add(SplitInfo.NewEdges.C);
AddEdgeToTriangleROI(SplitInfo.NewEdges.C);
}
// these two triangles should be already added by AddEdgeToTriangleROI() calls above...
TriangleROI.Add(SplitInfo.NewTriangles.A);
if (SplitInfo.NewTriangles.B != FDynamicMesh3::InvalidID)
{
TriangleROI.Add(SplitInfo.NewTriangles.B);
}
//AddVertexToTriangleROI(SplitInfo.NewVertex);
//for (int tid : GetMesh()->VtxTrianglesItr(SplitInfo.NewVertex))
// check(TriangleROI.Contains(tid));
}
virtual void OnEdgeCollapse(int EdgeID, int VertexA, int VertexB, const FDynamicMesh3::FEdgeCollapseInfo& CollapseInfo) override
{
// remove triangles from ROI
TriangleROI.Remove(CollapseInfo.RemovedTris.A);
if (bTrackRemoved)
{
RemovedLastPassTris.Add(CollapseInfo.RemovedTris.A);
}
if (CollapseInfo.RemovedTris.B != FDynamicMesh3::InvalidID)
{
TriangleROI.Remove(CollapseInfo.RemovedTris.B);
if (bTrackRemoved)
{
RemovedLastPassTris.Add(CollapseInfo.RemovedTris.B);
}
}
// remove vtx
VertexROI.Remove(CollapseInfo.RemovedVertex);
// remove edges
EdgeROI.Remove(CollapseInfo.CollapsedEdge);
EdgeROI.Remove(CollapseInfo.RemovedEdges.A);
if (CollapseInfo.RemovedEdges.B != FDynamicMesh3::InvalidID)
{
EdgeROI.Remove(CollapseInfo.RemovedEdges.B);
}
// Note: we're not removing CollapsedEdge or RemovedEdges from the Edges array
}
virtual void OnEdgeFlip(int EdgeID, const FDynamicMesh3::FEdgeFlipInfo& FlipInfo)
{
// flipping an edge potentially connects new verts to the ROI
FIndex2i EdgeV = GetMesh()->GetEdgeV(EdgeID);
VertexROI.Add(EdgeV.A);
VertexROI.Add(EdgeV.B);
AddVertexToTriangleROI(EdgeV.A);
AddVertexToTriangleROI(EdgeV.B);
}
void AddVertexToTriangleROI(int VertexID)
{
GetMesh()->EnumerateVertexTriangles(VertexID, [this](int tid)
{
TriangleROI.Add(tid);
});
}
void AddEdgeToTriangleROI(int EdgeID)
{
FIndex2i EdgeV = GetMesh()->GetEdgeV(EdgeID);
AddVertexToTriangleROI(EdgeV.A);
AddVertexToTriangleROI(EdgeV.B);
}
EVertexControl VertexFilter(int VertexID)
{
return (VertexROI.Contains(VertexID) == false) ? EVertexControl::NoMovement : EVertexControl::AllowAll;
}
//
// localized smoothing
//
TArray<int32> SmoothIndices;
TArray<FVector3d> SmoothedPositions;
virtual void FullSmoothPass_Buffer(bool bParallel) override
{
SmoothIndices.Reset();
SmoothIndices.Reserve(VertexROI.Num());
for (int32 vid : VertexROI)
{
SmoothIndices.Add(vid);
}
int32 NumVertices = SmoothIndices.Num();
SmoothedPositions.SetNum(NumVertices, EAllowShrinking::No);
TFunction<FVector3d(const FDynamicMesh3&, int, double)> UseSmoothFunc = GetSmoothFunction();
ParallelFor(NumVertices, [&](int32 i)
{
int32 VertIdx = SmoothIndices[i];
bool bModified = false;
SmoothedPositions[i] = ComputeSmoothedVertexPos(VertIdx, UseSmoothFunc, bModified);
SmoothIndices[i] = (bModified) ? SmoothIndices[i] : -1;
});
ParallelFor(NumVertices, [&](int32 i)
{
if (SmoothIndices[i] >= 0)
{
Mesh->SetVertex(SmoothIndices[i], SmoothedPositions[i], false);
}
});
Mesh->UpdateChangeStamps(true, false);
}
};
/**
* FRestrictedSubRegionRemesher is similar to FSubRegionRemesher but does not allow the ROI to grow outside of the
* original ROI boundary.
*
* It is initialized from a set of Triangles and the initial ROI boundary is inferred from those triangles.
*/
class FRestrictedSubRegionRemesher : public FSubRegionRemesher
{
public:
FRestrictedSubRegionRemesher(FDynamicMesh3* Mesh, const TSet<int>& InTriangleROI) :
FSubRegionRemesher(Mesh)
{
InitializeFromTriangleROI(InTriangleROI);
}
void OnEdgeSplit(int EdgeID, int VertexA, int VertexB, const FDynamicMesh3::FEdgeSplitInfo& SplitInfo) override
{
check(EdgeROI.Contains(EdgeID));
VertexROI.Add(SplitInfo.NewVertex);
EdgeROI.Add(SplitInfo.NewEdges.A);
bool bSplitEdgeIsROIBoundary = EdgeIsROIBoundary(EdgeID);
// Don't grow the ROI here, by filtering out edges whose two vertices are not both in the ROI.
if (EdgeVerticesAreInVertexROI(SplitInfo.NewEdges.B))
{
EdgeROI.Add(SplitInfo.NewEdges.B);
}
if (SplitInfo.NewEdges.C != FDynamicMesh3::InvalidID)
{
if (EdgeVerticesAreInVertexROI(SplitInfo.NewEdges.C))
{
EdgeROI.Add(SplitInfo.NewEdges.C);
}
}
// Add Triangles to TriangleROI if all their edges are in EdgeROI
for (int NewTID : { SplitInfo.NewTriangles.A, SplitInfo.NewTriangles.B })
{
if (Mesh->IsTriangle(NewTID) && TriangleEdgesAreInEdgeROI(NewTID))
{
TriangleROI.Add(NewTID);
}
}
}
void OnEdgeFlip(int EdgeID, const FDynamicMesh3::FEdgeFlipInfo& FlipInfo) override
{
// flipping an edge should not introduce new verts to the Vertex ROI!
check(VertexROI.Contains(GetMesh()->GetEdgeV(EdgeID).A));
check(VertexROI.Contains(GetMesh()->GetEdgeV(EdgeID).B));
check(Mesh->IsEdge(FlipInfo.EdgeID));
EdgeROI.Add(FlipInfo.EdgeID);
check(Mesh->IsTriangle(FlipInfo.Triangles[0]));
check(Mesh->IsTriangle(FlipInfo.Triangles[1]));
TriangleROI.Add(FlipInfo.Triangles[0]);
TriangleROI.Add(FlipInfo.Triangles[1]);
}
private:
bool EdgeVerticesAreInVertexROI(int EdgeID) const
{
FIndex2i EdgeVertices = Mesh->GetEdgeV(EdgeID);
return VertexROI.Contains(EdgeVertices[0]) && VertexROI.Contains(EdgeVertices[1]);
}
bool TriangleEdgesAreInEdgeROI(int TriangleID) const
{
FIndex3i TriEdges = Mesh->GetTriEdges(TriangleID);
return EdgeROI.Contains(TriEdges[0]) && EdgeROI.Contains(TriEdges[1]) && EdgeROI.Contains(TriEdges[2]);
}
bool EdgeIsROIBoundary(int EdgeID) const
{
FIndex2i EdgeTriangles = Mesh->GetEdgeT(EdgeID);
return (TriangleROI.Contains(EdgeTriangles[0]) != TriangleROI.Contains(EdgeTriangles[1]));
}
void InitializeFromTriangleROI(const TSet<int>& InTriangleROI)
{
EdgeROI.Reset();
VertexROI.Reset();
TriangleROI = InTriangleROI;
for (int TriangleIndex : TriangleROI)
{
FIndex3i Vertices = Mesh->GetTriangle(TriangleIndex);
VertexROI.Add(Vertices[0]);
VertexROI.Add(Vertices[1]);
VertexROI.Add(Vertices[2]);
FIndex3i TriEdges = Mesh->GetTriEdges(TriangleIndex);
EdgeROI.Add(TriEdges[0]);
EdgeROI.Add(TriEdges[1]);
EdgeROI.Add(TriEdges[2]);
}
}
};
} // end namespace UE::Geometry
} // end namespace UE