Files
UnrealEngine/Engine/Source/Runtime/MeshConversion/Private/MeshDescriptionUVsToDynamicMesh.cpp
2025-05-18 13:04:45 +08:00

369 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MeshDescriptionUVsToDynamicMesh.h"
#include "DynamicMesh/DynamicMesh3.h"
#include "MeshDescription.h"
#include "StaticMeshAttributes.h"
using namespace UE::Geometry;
// TODO: Need to test both conversions some more with meshes that only have instanced UVs.
// Also, we should probably keep maps for elements we split due to manifoldness issues, etc,
// rather than using all those heuristics to try to weld things back together on the conversion
// back.
namespace MeshDescriptionUVsToDynamicMeshLocals
{
bool ShouldConversionUseSharedUVs(const FMeshDescription* MeshDescription, int32 LayerIndex)
{
const int32 NumSharedUVLayers = MeshDescription->GetNumUVElementChannels();
return NumSharedUVLayers > LayerIndex && MeshDescription->UVs(LayerIndex).GetArraySize() != 0;
}
// These determine the mapping between UV values and the resulting mesh vertex positions. If we're
// looking down on the unwrapped mesh, with the Z axis towards us, we want U's to be right, and
// V's to be up. In Unreal's left-handed coordinate system, this means that we map U's to world Y
// and V's to world X.
// Also, Unreal changes the V coordinates of imported meshes to 1-V internally, and we undo this
// while displaying the UV's because the users likely expect to see the original UV's (it would
// be particularly confusing for users working with UDIM assets, where internally stored V's
// frequently end up negative).
// The ScaleFactor just scales the mesh up. Scaling the mesh up makes it easier to zoom in
// further into the display before getting issues with the camera near plane distance.
FORCEINLINE FVector3d UVToVertPosition(const FVector2f& UV, double ScaleFactor)
{
return FVector3d((1 - UV.Y) * ScaleFactor, UV.X * ScaleFactor, 0);
}
FORCEINLINE FVector2f VertPositionToUV(const FVector3d& VertPosition, double ScaleFactor)
{
return FVector2f(VertPosition.Y / ScaleFactor, 1 - (VertPosition.X / ScaleFactor));
}
// This class is copied from MeshDescriptionToDynamicMesh.cpp. We could consider putting it
// somewhere common.
struct FVertexUV
{
int vid;
float x;
float y;
bool operator==(const FVertexUV& o) const
{
return vid == o.vid && x == o.x && y == o.y;
}
};
FORCEINLINE uint32 GetTypeHash(const FVertexUV& Vector)
{
// ugh copied from FVector clearly should not be using CRC for hash!!
return FCrc::MemCrc32(&Vector, sizeof(Vector));
}
// This class is modeled on FUVWelder in MeshDescriptionToDynamicMesh.cpp,
// except it adds to the dynamic mesh vertices instead of the mesh UV's.
// Note: we could consider welding UV's differently by sorting all the verts and welding in one pass.
class FUVMeshWelder
{
public:
FUVMeshWelder(FDynamicMesh3* MeshToAddTo, double ScaleFactorIn)
: Mesh(MeshToAddTo)
, ScaleFactor(ScaleFactorIn)
{
check(MeshToAddTo && ScaleFactorIn != 0);
}
int FindOrAddUnique(const FVector2f& UV, int VertexID)
{
FVertexUV VertUV = { VertexID, UV.X, UV.Y };
const int32* FoundIndex = UniqueVertexUVs.Find(VertUV);
if (FoundIndex != nullptr)
{
return *FoundIndex;
}
int32 NewIndex = Mesh->AppendVertex(UVToVertPosition(UV, ScaleFactor));
UniqueVertexUVs.Add(VertUV, NewIndex);
return NewIndex;
}
protected:
FDynamicMesh3* Mesh;
double ScaleFactor;
TMap<FVertexUV, int> UniqueVertexUVs;
};
}
int32 FMeshDescriptionUVsToDynamicMesh::GetNumUVLayers(const FMeshDescription* MeshDescription) const
{
FStaticMeshConstAttributes Attributes(*MeshDescription);
return Attributes.GetVertexInstanceUVs().GetNumChannels();
}
TSharedPtr<FDynamicMesh3> FMeshDescriptionUVsToDynamicMesh::GetUVMesh(const FMeshDescription* MeshDescription)
{
using namespace MeshDescriptionUVsToDynamicMeshLocals;
check(ScaleFactor != 0);
bool bUseSharedUVs = ShouldConversionUseSharedUVs(MeshDescription, UVLayerIndex);
// The output that we'll return
TSharedPtr<FDynamicMesh3> MeshOut = MakeShared<FDynamicMesh3>();
if (bUseSharedUVs)
{
// Create vertices for the UV elements.
const FUVArray& SharedUVs = MeshDescription->UVs(UVLayerIndex);
TUVAttributesRef<const FVector2f> SharedUVCoordinates = SharedUVs.GetAttributes().GetAttributesRef<FVector2f>(MeshAttribute::UV::UVCoordinate);
MeshOut->BeginUnsafeVerticesInsert();
for (FUVID UVID : SharedUVs.GetElementIDs())
{
const FVector2f UV = SharedUVCoordinates[UVID];
MeshOut->InsertVertex(UVID.GetValue(), UVToVertPosition(UV, ScaleFactor), true);
}
MeshOut->EndUnsafeTrianglesInsert();
}
// Instance UV's and a welder are used if bUseSharedUVs is false, or if it is true but some
// verts are not initialized.
FStaticMeshConstAttributes Attributes(*MeshDescription);
TVertexInstanceAttributesConstRef<FVector2f> InstanceUVs = Attributes.GetVertexInstanceUVs();
FUVMeshWelder UVMeshWelder(MeshOut.Get(), ScaleFactor);
// Go through triangles and build our mesh
MeshOut->BeginUnsafeTrianglesInsert();
for (const FTriangleID TriangleID : MeshDescription->Triangles().GetElementIDs())
{
// The Tid must stay the same in the dynamic mesh for us to be able to bake things back
int32 Tid = TriangleID.GetValue();
// Will be populated with Vids in the dynamic mesh
FIndex3i TriangleToInsert;
// Used on instance UVs to map them to shared UV elements (vertices) in the dynamic mesh.
auto MakeWeldedUVsForTri = [this, MeshDescription, &UVMeshWelder, &InstanceUVs](const FTriangleID& TriangleIDIn, FIndex3i& TriangleToInsertOut) {
TArrayView<const FVertexID> SourceTriangleVids = MeshDescription->GetTriangleVertices(TriangleIDIn);
TArrayView<const FVertexInstanceID> SourceTriangleInstanceIds = MeshDescription->GetTriangleVertexInstances(TriangleIDIn);
for (int32 i = 0; i < 3; ++i)
{
FVector2f UV = InstanceUVs.Get(SourceTriangleInstanceIds[i], UVLayerIndex);
TriangleToInsertOut[i] = UVMeshWelder.FindOrAddUnique(UV, SourceTriangleVids[i].GetValue());
}
};
if (!bUseSharedUVs)
{
// If we're not using shared UV's, each vertex instance has its own UV element. We weld them per vertex
// if the UV's match.
MakeWeldedUVsForTri(TriangleID, TriangleToInsert);
}
else
{
TArrayView<FUVID> TriUVIndices = MeshDescription->GetTriangleUVIndices(TriangleID, UVLayerIndex);
// If some of the mesh description triangles were missing shared UVs,
// use the per-vertex UVs on the mesh description and weld these.
// NB: This can happen when multiple meshes with different UV layer count are "combined" during import
// NB: these mesh description per-vertex UVs always exist, but may default to zero
FUVID InvalidUVID(INDEX_NONE);
if (TriUVIndices[0] == InvalidUVID || TriUVIndices[1] == InvalidUVID || TriUVIndices[2] == InvalidUVID)
{
MakeWeldedUVsForTri(TriangleID, TriangleToInsert);
}
else
{
// Triangle seems ok so far. We already added vertices for it previously, so set up the tri to add.
// We'll be doing more checks for degeneracy and such before inserting.
for (int32 i = 0; i < 3; ++i)
{
TriangleToInsert[i] = TriUVIndices[i].GetValue();
}
}
}
// Our splitter, returns the new vertex id
auto SplitDynamicMeshVertex = [MeshOut](int32 Vid) {
check(MeshOut->IsVertex(Vid));
const FVector3d Position = MeshOut->GetVertex(Vid);
return MeshOut->AppendVertex(Position);
};
// Split vertices if our triangle turned out to be degenerate.
if (TriangleToInsert[0] == TriangleToInsert[1] || TriangleToInsert[0] == TriangleToInsert[2])
{
TriangleToInsert[0] = SplitDynamicMeshVertex(TriangleToInsert[0]);
}
if (TriangleToInsert[1] == TriangleToInsert[2])
{
TriangleToInsert[1] = SplitDynamicMeshVertex(TriangleToInsert[1]);
}
// Attempt triangle insertion
EMeshResult Result = MeshOut->InsertTriangle(Tid, TriangleToInsert, 0, true);
// Duplicate vertices if insertion failed due to a non-manifold edge
if (Result == EMeshResult::Failed_WouldCreateNonmanifoldEdge)
{
bool bDuplicate[3] = { false, false, false };
int e0 = MeshOut->FindEdge(TriangleToInsert[0], TriangleToInsert[1]);
int e1 = MeshOut->FindEdge(TriangleToInsert[1], TriangleToInsert[2]);
int e2 = MeshOut->FindEdge(TriangleToInsert[2], TriangleToInsert[0]);
// determine which verts need to be duplicated by seeing whether existing edges already
// have two triangles.
if (e0 != FDynamicMesh3::InvalidID && MeshOut->IsBoundaryEdge(e0) == false)
{
bDuplicate[0] = true;
bDuplicate[1] = true;
}
if (e1 != FDynamicMesh3::InvalidID && MeshOut->IsBoundaryEdge(e1) == false)
{
bDuplicate[1] = true;
bDuplicate[2] = true;
}
if (e2 != FDynamicMesh3::InvalidID && MeshOut->IsBoundaryEdge(e2) == false)
{
bDuplicate[2] = true;
bDuplicate[0] = true;
}
// Do the actual duplication
for (int32 i = 0; i < 3; ++i)
{
if (bDuplicate[i])
{
TriangleToInsert[i] = SplitDynamicMeshVertex(TriangleToInsert[i]);
}
}
// Reinsert the triangle
Result = MeshOut->InsertTriangle(Tid, TriangleToInsert, 0, true);
}
check(Result == EMeshResult::Ok || Result == EMeshResult::Failed_TriangleAlreadyExists);
}//end iterating through triangles
MeshOut->EndUnsafeTrianglesInsert();
return MeshOut;
}
void FMeshDescriptionUVsToDynamicMesh::BakeBackUVsFromUVMesh(const FDynamicMesh3* DynamicMesh, FMeshDescription* MeshDescription) const
{
using namespace MeshDescriptionUVsToDynamicMeshLocals;
check(ScaleFactor != 0);
check(DynamicMesh->TriangleCount() == MeshDescription->Triangles().Num())
// Regardless of whether we have shared UVs or not, we are supposed to update the instance UVs.
// This is straightforward, since any welding or splitting does not change anything.
FStaticMeshAttributes Attributes(*MeshDescription);
TVertexInstanceAttributesRef<FVector2f> InstanceUVs = Attributes.GetVertexInstanceUVs();
for (int32 Tid : DynamicMesh->TriangleIndicesItr())
{
TArrayView<const FVertexInstanceID> MeshDescriptionTriInstanceIds = MeshDescription->GetTriangleVertexInstances(Tid);
FIndex3i DynamicMeshTriVids = DynamicMesh->GetTriangle(Tid);
for (int i = 0; i < 3; ++i)
{
FVector3d VertPosition = DynamicMesh->GetVertex(DynamicMeshTriVids[i]);
InstanceUVs.Set(MeshDescriptionTriInstanceIds[i], UVLayerIndex, VertPositionToUV(VertPosition, ScaleFactor));
}
}
if (ShouldConversionUseSharedUVs(MeshDescription, UVLayerIndex))
{
// For shared UVs we may have to deal with splitting and welding, which makes things a bit more
// complicated.
// VidToUVElement is able to map vids back to newly created or merged UV elements. It
// also lets us know if we've dealt with a specific vid before.
TArray<int32> VidToUVElement;
VidToUVElement.SetNum(DynamicMesh->MaxVertexID());
for (int32& Value : VidToUVElement)
{
Value = INDEX_NONE;
}
FUVArray& SharedUvs = MeshDescription->UVs(UVLayerIndex);
TUVAttributesRef<FVector2f> SharedUVCoordinates = SharedUvs.GetAttributes().GetAttributesRef<FVector2f>(MeshAttribute::UV::UVCoordinate);
MeshDescription->SuspendUVIndexing();
for (int32 Tid : DynamicMesh->TriangleIndicesItr())
{
// Look at the UV ID's this triangle had, and the vertex ID's it now has. Vertex ID's correspond
// to UV element ID's except in cases where a new UV element needs to be created.
TArrayView<FUVID> TriUVIDs = MeshDescription->GetTriangleUVIndices(FTriangleID(Tid), UVLayerIndex);
FIndex3i TriVids = DynamicMesh->GetTriangle(Tid);
bool bTriUVIDsChanged = false;
// Examine the triangle vertices
for (int i = 0; i < 3; ++i)
{
// By examining the current and expected Vids, we can tell whether the UV connectivity changed
int32 Vid = TriVids[i];
int32 ExpectedVid = TriUVIDs[i].GetValue();
// First check whether we've dealt with this vert before
if (VidToUVElement[Vid] != INDEX_NONE)
{
// We still need to update triangle connectivity unless it corresponds to the existing expected vert
// (this includes verts that we split for conversion only, since those will map to the expected vert)
if (VidToUVElement[Vid] != ExpectedVid)
{
TriUVIDs[i] = VidToUVElement[Vid];
bTriUVIDsChanged = true;
}
continue;
}
// If we haven't dealt with this vert, we will need to update its entry in SharedUVCoordinates.
FVector3d VertPosition = DynamicMesh->GetVertex(Vid);
FVector2f NewUVValue = VertPositionToUV(VertPosition, ScaleFactor);
// See if the vert is the same one that we were expecting (including one that we may have split off just for
// the conversion, in which case the location will match).
if (Vid == ExpectedVid ||
(DynamicMesh->IsVertex(ExpectedVid) && DynamicMesh->GetVertex(ExpectedVid) == VertPosition))
{
VidToUVElement[Vid] = ExpectedVid;
SharedUVCoordinates.Set(TriUVIDs[i], NewUVValue);
// No need to update TriUVIDs
continue;
}
// See if the vert corresponds to an element that exists in SharedUVCoordinates
if (MeshDescription->IsUVValid(FUVID(Vid), UVLayerIndex))
{
VidToUVElement[Vid] = Vid;
SharedUVCoordinates.Set(FUVID(Vid), NewUVValue);
TriUVIDs[i] = FUVID(Vid);
bTriUVIDsChanged = true;
continue;
}
// Otherwise, this is a new element we need to create, and we need to update TriUVIDs
TriUVIDs[i] = MeshDescription->CreateUV(UVLayerIndex);
VidToUVElement[Vid] = TriUVIDs[i].GetValue();
SharedUVCoordinates.Set(TriUVIDs[i], NewUVValue);
bTriUVIDsChanged = true;
}//end dealing with tri verts
if (bTriUVIDsChanged)
{
MeshDescription->SetTriangleUVIndices(Tid, TriUVIDs, UVLayerIndex);
}
}//end iterating through triangles
MeshDescription->ResumeUVIndexing();
}
}