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

1037 lines
34 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MeshDescriptionToDynamicMesh.h"
#include "DynamicMesh/DynamicMeshAttributeSet.h"
#include "DynamicMesh/DynamicMeshOverlay.h"
#include "DynamicMesh/DynamicVertexAttribute.h"
#include "DynamicMesh/DynamicVertexSkinWeightsAttribute.h"
#include "DynamicMesh/DynamicBoneAttribute.h"
#include "DynamicMesh/MeshTangents.h"
#include "DynamicMesh/NonManifoldMappingSupport.h"
#include "MeshDescriptionBuilder.h"
#include "StaticMeshAttributes.h"
#include "SkeletalMeshAttributes.h"
#include "Tasks/Task.h"
#include "Util/ColorConstants.h"
using namespace UE::Geometry;
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));
}
class FUVWelder
{
public:
TMap<FVertexUV, int> UniqueVertexUVs;
FDynamicMeshUVOverlay* UVOverlay;
FUVWelder() : UVOverlay(nullptr)
{
}
FUVWelder(FDynamicMeshUVOverlay* UVOverlayIn)
{
check(UVOverlayIn);
UVOverlay = UVOverlayIn;
}
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 = UVOverlay->AppendElement(FVector2f(UV));
UniqueVertexUVs.Add(VertUV, NewIndex);
return NewIndex;
}
};
struct FVertexNormal
{
int vid;
float x;
float y;
float z;
bool operator==(const FVertexNormal & o) const
{
return vid == o.vid && x == o.x && y == o.y && z == o.z;
}
};
FORCEINLINE uint32 GetTypeHash(const FVertexNormal& Vector)
{
// ugh copied from FVector clearly should not be using CRC for hash!!
return FCrc::MemCrc32(&Vector, sizeof(Vector));
}
class FNormalWelder
{
public:
TMap<FVertexNormal, int> UniqueVertexNormals;
FDynamicMeshNormalOverlay* NormalOverlay;
FNormalWelder() : NormalOverlay(nullptr)
{
}
FNormalWelder(FDynamicMeshNormalOverlay* NormalOverlayIn)
{
check(NormalOverlayIn);
NormalOverlay = NormalOverlayIn;
}
int FindOrAddUnique(const FVector3f & Normal, int VertexID)
{
FVertexNormal VertNormal = { VertexID, Normal.X, Normal.Y, Normal.Z };
const int32* FoundIndex = UniqueVertexNormals.Find(VertNormal);
if (FoundIndex != nullptr)
{
return *FoundIndex;
}
int32 NewIndex = NormalOverlay->AppendElement( &Normal.X );
UniqueVertexNormals.Add(VertNormal, NewIndex);
return NewIndex;
}
};
struct FVertexColor
{
int vid;
FVector4f Color;
bool operator==(const FVertexColor& o) const
{
return vid == o.vid && Color == o.Color;
}
};
FORCEINLINE uint32 GetTypeHash(const FVertexColor& Vector)
{
return FCrc::MemCrc32(&Vector, sizeof(Vector));
}
class FColorWelder
{
public:
TMap<FVertexColor, int> UniqueVertexColors;
FDynamicMeshColorOverlay* ColorOverlay;
FColorWelder() : ColorOverlay(nullptr)
{
}
FColorWelder(FDynamicMeshColorOverlay* ColorOverlayIn)
{
check(ColorOverlayIn);
ColorOverlay = ColorOverlayIn;
}
int FindOrAddUnique(const FVector4f& Color, int VertexID)
{
FVertexColor VertColor = { VertexID, Color };
const int32* FoundIndex = UniqueVertexColors.Find(VertColor);
if (FoundIndex != nullptr)
{
return *FoundIndex;
}
int32 NewIndex = ColorOverlay->AppendElement(&Color.X);
UniqueVertexColors.Add(VertColor, NewIndex);
return NewIndex;
}
};
struct FSkinWeightsAttribCopyInfo
{
FSkinWeightsVertexAttributesConstRef MeshDescAttribRef;
FDynamicMeshVertexSkinWeightsAttribute* DynaMeshAttribRef = nullptr;
};
void FMeshDescriptionToDynamicMesh::Convert(const FMeshDescription* MeshIn, FDynamicMesh3& MeshOut, bool bCopyTangents )
{
TRACE_CPUPROFILER_EVENT_SCOPE(MeshDescriptionToDynamicMesh);
TriIDMap.Reset();
VertIDMap.Reset();
MeshOut.Clear();
if (!ensure(MeshIn != nullptr))
{
return; // nothing to convert
}
// allocate the VertIDMap. Unfortunately the array will need to grow more if MeshIn has non-manifold edges that need to be split
VertIDMap.SetNumUninitialized(MeshIn->Vertices().Num());
// look up vertex positions
TVertexAttributesConstRef<FVector3f> VertexPositions = MeshIn->GetVertexPositions();
// copy vertex positions. Later we may have to append duplicate vertices to resolve non-manifold structures.
for (const FVertexID VertexID : MeshIn->Vertices().GetElementIDs())
{
const FVector3f Position = VertexPositions.Get(VertexID);
int NewVertIdx = MeshOut.AppendVertex( (FVector3d)Position);
VertIDMap[NewVertIdx] = VertexID;
}
// look up vertex-instance UVs and normals
// @todo: does the MeshDescription always have UVs and Normals?
FSkeletalMeshConstAttributes Attributes(*MeshIn);
TVertexInstanceAttributesConstRef<FVector2f> InstanceUVs = Attributes.GetVertexInstanceUVs();
TVertexInstanceAttributesConstRef<FVector3f> InstanceNormals = Attributes.GetVertexInstanceNormals();
TVertexInstanceAttributesConstRef<FVector3f> InstanceTangents = Attributes.GetVertexInstanceTangents();
TVertexInstanceAttributesConstRef<float> InstanceBiTangentSign = Attributes.GetVertexInstanceBinormalSigns();
TVertexInstanceAttributesConstRef<FVector4f> InstanceColors = Attributes.GetVertexInstanceColors();
TPolygonAttributesConstRef<int> PolyGroups =
MeshIn->PolygonAttributes().GetAttributesRef<int>(ExtendedMeshAttribute::PolyTriGroups);
if (bEnableOutputGroups)
{
MeshOut.EnableTriangleGroups(0);
}
bCopyTangents = bCopyTangents && InstanceTangents.IsValid() && InstanceBiTangentSign.IsValid();
// base triangle groups will track polygons
int NumVertices = MeshIn->Vertices().Num();
int NumPolygons = MeshIn->Polygons().Num();
int NumVtxInstances = MeshIn->VertexInstances().Num();
if (bPrintDebugMessages)
{
UE_LOG(LogTemp, Warning, TEXT("FMeshDescriptionToDynamicMesh: MeshDescription verts %d polys %d instances %d"), NumVertices, NumPolygons, NumVtxInstances);
}
FDateTime Time_AfterVertices = FDateTime::Now();
// Although it is slightly redundant, we will build up this list of MeshDescription data
// so that it is easier to index into it below (profile whether extra memory here hurts us?)
struct FTriData
{
FPolygonID PolygonID;
int32 PolygonGroupID;
FVertexInstanceID TriInstances[3];
};
TArray<FTriData> AddedTriangles;
AddedTriangles.SetNum(MeshIn->Triangles().Num());
// allocate the TriIDMap (maps from DynamicMesh triangle ID to MeshDescription FTriangleID)
TriIDMap.AddUninitialized(MeshIn->Triangles().Num());
bool bSrcIsManifold = true;
// Iterate over triangles in the Mesh Description
// NOTE: If you change the iteration order here, please update the corresponding iteration in FDynamicMeshToMeshDescription::UpdateAttributes,
// which assumes the iteration order here is the same as here to correspond the triangles when writing updated attributes back!
for (const FTriangleID TriangleID : MeshIn->Triangles().GetElementIDs())
{
// Get the PolygonID, PolygonGroupID and general GroupID
//---
FPolygonID PolygonID = MeshIn->GetTrianglePolygon(TriangleID);
FPolygonGroupID PolygonGroupID = MeshIn->GetTrianglePolygonGroup(TriangleID);
int GroupID = 0;
switch (GroupMode)
{
case EPrimaryGroupMode::SetToPolyGroup:
if (PolyGroups.IsValid())
{
GroupID = PolyGroups.Get(PolygonID, 0);
}
break;
case EPrimaryGroupMode::SetToPolygonID:
GroupID = PolygonID.GetValue() + 1; // Shift IDs up by 1 to leave ID 0 as a default/unassigned group
break;
case EPrimaryGroupMode::SetToPolygonGroupID:
GroupID = PolygonGroupID.GetValue() + 1; // Shift IDs up by 1 to leave ID 0 as a default/unassigned group
break;
case EPrimaryGroupMode::SetToZero:
break; // keep at 0
}
FTriData TriData;
TriData.PolygonID = PolygonID;
TriData.PolygonGroupID = PolygonGroupID;
// stash the vertex instance IDs for this triangle. potentially needed for per-instance attribute welding
TArrayView<const FVertexInstanceID> InstanceTri = MeshIn->GetTriangleVertexInstances(TriangleID);
TriData.TriInstances[0] = InstanceTri[0];
TriData.TriInstances[1] = InstanceTri[1];
TriData.TriInstances[2] = InstanceTri[2];
//---
// Get the vertex IDs for this triangle.
TArrayView<const FVertexID> TriangleVertexIDs = MeshIn->GetTriangleVertices(TriangleID);
// sanity
checkSlow(TriangleVertexIDs.Num() == 3);
FIndex3i VertexIDs;
VertexIDs[0] = TriangleVertexIDs[0].GetValue();
VertexIDs[1] = TriangleVertexIDs[1].GetValue();
VertexIDs[2] = TriangleVertexIDs[2].GetValue();
int NewTriangleID = MeshOut.AppendTriangle(VertexIDs, GroupID);
// Deal with potential failure cases
//-- already seen this triangle for some reason.. or the MeshDecription had a degenerate tri
if (NewTriangleID == FDynamicMesh3::DuplicateTriangleID || NewTriangleID == FDynamicMesh3::InvalidID)
{
continue;
}
//-- non manifold
// if append failed due to non-manifold, duplicate verts
if (NewTriangleID == FDynamicMesh3::NonManifoldID)
{
bSrcIsManifold = false;
int e0 = MeshOut.FindEdge(VertexIDs[0], VertexIDs[1]);
int e1 = MeshOut.FindEdge(VertexIDs[1], VertexIDs[2]);
int e2 = MeshOut.FindEdge(VertexIDs[2], VertexIDs[0]);
// determine which verts need to be duplicated
bool bDuplicate[3] = { false, false, false };
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;
}
for (int32 i = 0; i < 3; ++i)
{
if (bDuplicate[i])
{
const FVertexID& TriangleVertID = TriangleVertexIDs[i];
const FVector3f Position = VertexPositions[TriangleVertID];
const int32 NewVertIdx = MeshOut.AppendVertex( (FVector3d)Position );
VertexIDs[i] = NewVertIdx;
VertIDMap.SetNumUninitialized(NewVertIdx + 1);
VertIDMap[NewVertIdx] = TriangleVertID;
}
}
NewTriangleID = MeshOut.AppendTriangle(VertexIDs, GroupID);
checkSlow(NewTriangleID != FDynamicMesh3::NonManifoldID);
}
checkSlow(NewTriangleID >= 0);
AddedTriangles[NewTriangleID] = TriData;
TriIDMap[NewTriangleID] = TriangleID;
}
int32 MaxTriID = MeshOut.MaxTriangleID(); // really MaxTriID+1
// if the source mesh had duplicates then TriIDMap will be too long, this will trim excess
TriIDMap.SetNum(MaxTriID);
FDateTime Time_AfterTriangles = FDateTime::Now();
//
// Enable relevant attributes and initialize UV/Normal welders
//
// the shared UV representation.
const int32 NumUVElementChannels = MeshIn->GetNumUVElementChannels();
// the instanced UV representation.
const int NumUVLayers = InstanceUVs.IsValid() ? InstanceUVs.GetNumChannels() : 0;
// determine if we really have shared UVS. Legacy geo might not have them
// - at the time of this writing MeshDescription has not been updated
// to always populated the shared UVs during load time for legacy geometry that only has per-instance UVs.
// but that is a promised improvement.
bool bUseSharedUVs = (NumUVLayers == NumUVElementChannels);
if (bUseSharedUVs)
{
for (int UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex)
{
const int32 NumSharedUVs = MeshIn->UVs(UVLayerIndex).GetArraySize();
bUseSharedUVs = bUseSharedUVs && (NumSharedUVs != 0);
}
}
FDynamicMeshColorOverlay* ColorOverlay = nullptr;
FDynamicMeshNormalOverlay* NormalOverlay = nullptr;
FDynamicMeshNormalOverlay* TangentOverlay = nullptr;
FDynamicMeshNormalOverlay* BiTangentOverlay = nullptr;
FDynamicMeshMaterialAttribute* MaterialIDAttrib = nullptr;
FDynamicMeshBoneNameAttribute* BoneNameAttrib = nullptr;
FDynamicMeshBoneParentIndexAttribute* BoneParentIndexAttrib = nullptr;
FDynamicMeshBonePoseAttribute* BonePoseAttrib = nullptr;
FDynamicMeshBoneColorAttribute* BoneColorAttrib = nullptr;
TArray<FSkinWeightsAttribCopyInfo> SkinWeightAttribs;
if (!bDisableAttributes)
{
MeshOut.EnableAttributes(); // by default 1-UV layer and 1-normal layer
// Normals
// set up for the tangent plane vectors if required
int32 NumRequiredNormalLayers = (bCopyTangents) ? 3 : 1;
if (MeshOut.Attributes()->NumNormalLayers() < NumRequiredNormalLayers)
{
// add additional normal layers and reserve space in them
MeshOut.Attributes()->SetNumNormalLayers(NumRequiredNormalLayers);
}
for (int32 i = 0; i < NumRequiredNormalLayers; ++i)
{
MeshOut.Attributes()->GetNormalLayer(i)->InitializeTriangles(MeshOut.MaxTriangleID());
}
NormalOverlay = MeshOut.Attributes()->PrimaryNormals();
if (bCopyTangents)
{
TangentOverlay = MeshOut.Attributes()->PrimaryTangents();
BiTangentOverlay = MeshOut.Attributes()->PrimaryBiTangents();
}
// UVs
MeshOut.Attributes()->SetNumUVLayers(NumUVLayers);
// reserve space in any new UV layers.
for (int32 i = 1; i < NumUVLayers; ++i)
{
MeshOut.Attributes()->GetUVLayer(i)->InitializeTriangles(MeshOut.MaxTriangleID());
}
if (InstanceColors.IsValid())
{
MeshOut.Attributes()->EnablePrimaryColors();
ColorOverlay = MeshOut.Attributes()->PrimaryColors();
}
// always enable Material ID if there are any attributes
MeshOut.Attributes()->EnableMaterialID();
MaterialIDAttrib = MeshOut.Attributes()->GetMaterialID();
// Copy all skin weight attributes, if they exist.
MeshIn->VertexAttributes().ForEach([&](const FName InAttributeName, auto InAttributesRef)
{
if (FSkeletalMeshAttributes::IsSkinWeightAttribute(InAttributeName))
{
const FName ProfileName = FSkeletalMeshAttributes::GetProfileNameFromAttribute(InAttributeName);
FDynamicMeshVertexSkinWeightsAttribute *VertexSkinWeightsAttrib = new FDynamicMeshVertexSkinWeightsAttribute(&MeshOut);
SkinWeightAttribs.Add({
Attributes.GetVertexSkinWeightsFromAttributeName(InAttributeName),
VertexSkinWeightsAttrib
});
MeshOut.Attributes()->AttachSkinWeightsAttribute(ProfileName, VertexSkinWeightsAttrib);
}
});
if (Attributes.HasBones())
{
MeshOut.Attributes()->EnableBones(Attributes.GetNumBones());
if (Attributes.HasBoneNameAttribute())
{
BoneNameAttrib = MeshOut.Attributes()->GetBoneNames();
}
if (Attributes.HasBoneParentIndexAttribute())
{
BoneParentIndexAttrib = MeshOut.Attributes()->GetBoneParentIndices();
}
if (Attributes.HasBonePoseAttribute())
{
BonePoseAttrib = MeshOut.Attributes()->GetBonePoses();
}
if (Attributes.HasBoneColorAttribute())
{
BoneColorAttrib = MeshOut.Attributes()->GetBoneColors();
}
}
}
// find polygroup attribs
const TAttributesSet<FTriangleID>& TriAttribsSet = MeshIn->TriangleAttributes();
TArray<TTriangleAttributesConstRef<int32>> PolygroupAttribs;
TArray<FName> PolygroupAttribNames;
TriAttribsSet.ForEach([&](const FName AttributeName, auto AttributesRef)
{
if (FSkeletalMeshAttributes::IsReservedAttributeName(AttributeName)) return;
if (TriAttribsSet.template HasAttributeOfType<int32>(AttributeName))
{
PolygroupAttribs.Add(TriAttribsSet.GetAttributesRef<int32>(AttributeName));
PolygroupAttribNames.Add(AttributeName);
}
});
// find weight attribs
const TAttributesSet<FVertexID>& VertAttribsSet = MeshIn->VertexAttributes();
TArray<TVertexAttributesConstRef<float>> WeightAttribs;
TArray<FName> WeightAttribNames;
VertAttribsSet.ForEach([&](const FName AttributeName, auto AttributesRef)
{
if (FSkeletalMeshAttributes::IsReservedAttributeName(AttributeName)) return;
if (FSkeletalMeshAttributes::IsSkinWeightAttribute(AttributeName)) return;
if (VertAttribsSet.template HasAttributeOfType<float>(AttributeName))
{
WeightAttribs.Add(VertAttribsSet.GetAttributesRef<float>(AttributeName));
WeightAttribNames.Add(AttributeName);
}
});
if (!bDisableAttributes)
{
bool bFoundNonDefaultVertexInstanceColor = false;
// we will weld/populate all the attributes simultaneously, hold on to futures in this array and then Wait for them at the end
TArray<UE::Tasks::FTask> Pending;
if (PolygroupAttribs.Num() > 0)
{
MeshOut.Attributes()->SetNumPolygroupLayers(PolygroupAttribs.Num());
}
MeshOut.Attributes()->SetNumWeightLayers(WeightAttribs.Num());
for (int UVLayerIndex = 0; UVLayerIndex < NumUVLayers; UVLayerIndex++)
{
UE::Tasks::FTask UVTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [&, UVLayerIndex, bUseSharedUVs]() // must copy UVLayerIndex here!
{
// the overlay to fill.
FDynamicMeshUVOverlay* UVOverlay = MeshOut.Attributes()->GetUVLayer(UVLayerIndex);
if (UVOverlay == nullptr) return;
if (!bUseSharedUVs) // have to rely on welding the per-instance uvs
{
FUVWelder UVWelder;
UVWelder.UVOverlay = UVOverlay;
for (int32 TriangleID : MeshOut.TriangleIndicesItr())
{
FIndex3i Tri = MeshOut.GetTriangle(TriangleID);
const FTriData& TriData = AddedTriangles[TriangleID];
FIndex3i TriUV;
for (int j = 0; j < 3; ++j)
{
FVector2f UV = InstanceUVs.Get(TriData.TriInstances[j], UVLayerIndex);
TriUV[j] = UVWelder.FindOrAddUnique(UV, Tri[j]);
}
UVOverlay->SetTriangle(TriangleID, TriUV);
}
}
else
{
// copy uv "vertex buffer"
const FUVArray& UVs = MeshIn->UVs(UVLayerIndex);
TUVAttributesRef<const FVector2f> UVCoordinates = UVs.GetAttributes().GetAttributesRef<FVector2f>(MeshAttribute::UV::UVCoordinate);
// map to translate UVIds from FUVID::int32() to DynamicOverlay Index.
TArray<int32> UVIndexMap;
{
// pre-compute max UVID in the mesh description
int32 MaxUVElID = -1;
for (FUVID UVID : UVs.GetElementIDs())
{
MaxUVElID = FMath::Max(MaxUVElID, UVID.GetValue());
}
UVIndexMap.AddUninitialized(MaxUVElID+1);
}
for (FUVID UVID : UVs.GetElementIDs())
{
const FVector2f UVvalue = UVCoordinates[UVID];
int32 NewIndex = UVOverlay->AppendElement(FVector2f(UVvalue));
UVIndexMap[UVID.GetValue()] = NewIndex;
}
// copy uv "index buffer"
FUVID InvalidUVID(INDEX_NONE);
TArray<int32> TrisMissingUVs;
TMap<FIndex2i, int> SplitUVMapping; // mapping from (ParentVID, OrigElID) -> SplitElID, for UV ElIDs that need to be split to multiple vertices
for (int32 TriID : MeshOut.TriangleIndicesItr())
{
FTriangleID TriangleID = TriIDMap[TriID];
// NB: the mesh description lacks a const method variant of this function, hence the const_cast.
// Don't change the UVIndices values!
TArrayView<FUVID> UVIndices = const_cast<FMeshDescription*>(MeshIn)->GetTriangleUVIndices(TriangleID, UVLayerIndex);
if (UVIndices[0] == InvalidUVID || UVIndices[1] == InvalidUVID || UVIndices[2] == InvalidUVID)
{
// keep track of the tris that don't have UVs in the mesh description shared UV channel.
TrisMissingUVs.Add(TriID);
continue;
}
// translate to Overlay indicies
FIndex3i TriUV( UVIndexMap[UVIndices[0].GetValue()],
UVIndexMap[UVIndices[1].GetValue()],
UVIndexMap[UVIndices[2].GetValue()] );
///-- We have to do some clean-up on the shared UVs that come from MeshDecription --///
// This clean up should go away if MeshDescription can solve these problems during import from the source fbx files
{
// Helper to split UVs out on a triangle while keeping UVs with the same source ID and parent vertex the same element in the overlay
auto SplitVertexUV = [&SplitUVMapping, &UVCoordinates, &UVIndices, &UVOverlay](int ParentVID, FIndex3i& TriUV, int Idx)
{
FIndex2i SplitUVID(ParentVID, TriUV[Idx]);
int* FoundUVEl = SplitUVMapping.Find(SplitUVID);
if (FoundUVEl)
{
TriUV[Idx] = *FoundUVEl;
}
else
{
const FVector2f UVvalue = UVCoordinates[UVIndices[Idx]];
int NewUVEl = UVOverlay->AppendElement(FVector2f(UVvalue));
TriUV[Idx] = NewUVEl;
SplitUVMapping.Add(SplitUVID, NewUVEl);
}
};
// MeshDescription can attach multiple vertices to the same UV element. DynamicMesh does not.
// if we have already used this element for a different mesh vertex, split it.
const FIndex3i ParentTriangle = MeshOut.GetTriangle(TriID);
for (int i = 0; i < 3; ++i)
{
int32 ParentVID = UVOverlay->GetParentVertex(TriUV[i]);
if (ParentVID != FDynamicMesh3::InvalidID && ParentVID != ParentTriangle[i])
{
SplitVertexUV(ParentTriangle[i], TriUV, i);
}
}
// MeshDescription allows for degenerate UV tris. Dynamic Mesh does not.
// if the UV tri is degenerate we split the degenerate UV edge by adding a new UV
// in its place, or if it is totally degenerate we add 2 new UVs
if (TriUV[0] == TriUV[1] && TriUV[0] == TriUV[2])
{
const FVector2f UVvalue = UVCoordinates[UVIndices[1]];
SplitVertexUV(ParentTriangle[1], TriUV, 1);
SplitVertexUV(ParentTriangle[2], TriUV, 2);
}
else
{
int SplitWhich = -1;
if (TriUV[0] == TriUV[1])
{
SplitWhich = 1;
}
else if (TriUV[0] == TriUV[2] || TriUV[1] == TriUV[2])
{
SplitWhich = 2;
}
if (SplitWhich > -1)
{
SplitVertexUV(ParentTriangle[SplitWhich], TriUV, SplitWhich);
}
}
}
// set the triangle in the overlay
UVOverlay->SetTriangle(TriID, TriUV);
}
// 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
// we may want to revisit this in the future and not set UVs in the Overlay for these triangles,
// but currently some of our tools (e.g. inspector) assume the every mesh triangle has an overlay triangle.
if (TrisMissingUVs.Num() != 0)
{
FUVWelder UVWelder;
UVWelder.UVOverlay = UVOverlay;
for (int32 TriangleID : TrisMissingUVs)
{
const FIndex3i Tri = MeshOut.GetTriangle(TriangleID);
const FTriData& TriData = AddedTriangles[TriangleID];
FIndex3i TriUV;
for (int j = 0; j < 3; ++j)
{
const FVector2f UV = InstanceUVs.Get(TriData.TriInstances[j], UVLayerIndex);
TriUV[j] = UVWelder.FindOrAddUnique(UV, Tri[j]);
}
UVOverlay->SetTriangle(TriangleID, TriUV);
}
}
}
});
Pending.Add(MoveTemp(UVTask));
}
if (NormalOverlay != nullptr)
{
UE::Tasks::FTask NormalTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [&]()
{
FNormalWelder NormalWelder;
NormalWelder.NormalOverlay = NormalOverlay;
for (int32 TriangleID : MeshOut.TriangleIndicesItr())
{
const FIndex3i Tri = MeshOut.GetTriangle(TriangleID);
const FTriData& TriData = AddedTriangles[TriangleID];
FIndex3i TriNormals;
for (int j = 0; j < 3; ++j)
{
const FVector3f Normal = InstanceNormals.Get(TriData.TriInstances[j]);
TriNormals[j] = NormalWelder.FindOrAddUnique(Normal, Tri[j]);
}
NormalOverlay->SetTriangle(TriangleID, TriNormals);
}
});
Pending.Add(MoveTemp(NormalTask));
}
if (TangentOverlay != nullptr)
{
auto TangentTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [&]()
{
FNormalWelder TangentWelder;
TangentWelder.NormalOverlay = TangentOverlay;
for (int32 TriangleID : MeshOut.TriangleIndicesItr())
{
const FIndex3i Tri = MeshOut.GetTriangle(TriangleID);
const FTriData& TriData = AddedTriangles[TriangleID];
FIndex3i TriVector;
for (int j = 0; j < 3; ++j)
{
const FVector3f Vector = InstanceTangents.Get(TriData.TriInstances[j]);
TriVector[j] = TangentWelder.FindOrAddUnique(Vector, Tri[j]);
}
TangentOverlay->SetTriangle(TriangleID, TriVector);
}
});
Pending.Add(MoveTemp(TangentTask));
}
if (BiTangentOverlay != nullptr)
{
auto BiTangentTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [&]()
{
FNormalWelder BiTangentWelder;
BiTangentWelder.NormalOverlay = BiTangentOverlay;
for (int32 TriangleID : MeshOut.TriangleIndicesItr())
{
const FIndex3i Tri = MeshOut.GetTriangle(TriangleID);
const FTriData& TriData = AddedTriangles[TriangleID];
FIndex3i TriVector;
for (int j = 0; j < 3; ++j)
{
// compute the bi tangent.
const FVertexInstanceID VertexInstanceID = TriData.TriInstances[j];
const FVector3f NormalVector = InstanceNormals.Get(VertexInstanceID);
const FVector3f TangentVector = InstanceTangents.Get(VertexInstanceID);
const float BiSign = InstanceBiTangentSign.Get(VertexInstanceID);
FVector3f BiTangentVector = BiSign * FVector3f::CrossProduct(NormalVector, TangentVector);
BiTangentVector.Normalize();
TriVector[j] = BiTangentWelder.FindOrAddUnique(BiTangentVector, Tri[j]);
}
BiTangentOverlay->SetTriangle(TriangleID, TriVector);
}
});
Pending.Add(MoveTemp(BiTangentTask));
}
if (ColorOverlay != nullptr)
{
auto ColorTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [&]()
{
const FVector4f DefaultColor4 = InstanceColors.GetDefaultValue();
FColorWelder ColorWelder(ColorOverlay);
for (int32 TriangleID : MeshOut.TriangleIndicesItr())
{
const FIndex3i Tri = MeshOut.GetTriangle(TriangleID);
const FTriData& TriData = AddedTriangles[TriangleID];
FIndex3i TriVector;
for (int j = 0; j < 3; ++j)
{
FVector4f InstanceColor4 = InstanceColors.Get(TriData.TriInstances[j], 0);
ApplyVertexColorTransform(InstanceColor4);
const FVector4f OverlayColor(InstanceColor4.X, InstanceColor4.Y, InstanceColor4.Z, InstanceColor4.W);
TriVector[j] = ColorWelder.FindOrAddUnique(OverlayColor, Tri[j]);
// need to detect if the vertex instance color actually held any real data..
bFoundNonDefaultVertexInstanceColor |= (InstanceColor4 != DefaultColor4);
}
ColorOverlay->SetTriangle(TriangleID, TriVector);
}
});
Pending.Add(MoveTemp(ColorTask));
}
if (MaterialIDAttrib != nullptr)
{
auto MaterialTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [&]()
{
for (int32 TriangleID : MeshOut.TriangleIndicesItr())
{
const FTriData& TriData = AddedTriangles[TriangleID];
int32 MaterialIndex = TriData.PolygonGroupID;
if (PolygonGroupToMaterialIndexMap.IsValidIndex(TriData.PolygonGroupID))
{
MaterialIndex = PolygonGroupToMaterialIndexMap[TriData.PolygonGroupID];
}
MaterialIDAttrib->SetValue(TriangleID, &MaterialIndex);
}
});
Pending.Add(MoveTemp(MaterialTask));
}
for (const FSkinWeightsAttribCopyInfo& SkinWeightAttribInfo: SkinWeightAttribs)
{
auto SkinWeightsTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [&, SkinWeightAttribInfo]() -> void
{
for (int32 VertexID: MeshOut.VertexIndicesItr())
{
SkinWeightAttribInfo.DynaMeshAttribRef->SetValue(VertexID, SkinWeightAttribInfo.MeshDescAttribRef.Get(VertIDMap[VertexID]));
}
});
Pending.Add(MoveTemp(SkinWeightsTask));
}
if (BoneNameAttrib != nullptr)
{
auto BonesTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [&]() -> void
{
const int NumBones = Attributes.GetNumBones();
for (int32 BoneID = 0; BoneID < NumBones; ++BoneID)
{
BoneNameAttrib->SetValue(BoneID, Attributes.GetBoneNames().Get(BoneID));
if (BoneParentIndexAttrib)
{
BoneParentIndexAttrib->SetValue(BoneID, Attributes.GetBoneParentIndices().Get(BoneID));
}
if (BonePoseAttrib)
{
BonePoseAttrib->SetValue(BoneID, Attributes.GetBonePoses().Get(BoneID));
}
if (BoneColorAttrib)
{
BoneColorAttrib->SetValue(BoneID, Attributes.GetBoneColors().Get(BoneID));
}
}
});
Pending.Add(MoveTemp(BonesTask));
}
// initialize polygroup layers
for (int32 GroupLayerIdx = 0; GroupLayerIdx < PolygroupAttribs.Num(); GroupLayerIdx++)
{
auto PolygroupTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [&,Index=GroupLayerIdx]()
{
TTriangleAttributesConstRef<int32> InputGroupSet = PolygroupAttribs[Index];
FDynamicMeshPolygroupAttribute* OutputGroupSet = MeshOut.Attributes()->GetPolygroupLayer(Index);
if (ensure(OutputGroupSet))
{
OutputGroupSet->SetName(PolygroupAttribNames[Index]);
for (int32 tid : MeshOut.TriangleIndicesItr())
{
FTriangleID SourceTriangleID = TriIDMap[tid];
int32 GroupID = InputGroupSet.Get(SourceTriangleID);
OutputGroupSet->SetValue(tid, &GroupID);
}
}
});
Pending.Add(MoveTemp(PolygroupTask));
}
// initialize weight layers
for (int32 WeightLayerIdx = 0; WeightLayerIdx < WeightAttribs.Num(); WeightLayerIdx++)
{
auto WeightTask = UE::Tasks::Launch(UE_SOURCE_LOCATION, [this, &WeightAttribs, &WeightAttribNames, &MeshOut, WeightLayerIdx]()
{
TVertexAttributesConstRef<float> InputWeights = WeightAttribs[WeightLayerIdx];
FDynamicMeshWeightAttribute* OutputWeights = MeshOut.Attributes()->GetWeightLayer(WeightLayerIdx);
if (ensure(OutputWeights))
{
OutputWeights->SetName(WeightAttribNames[WeightLayerIdx]);
for (int32 vid : MeshOut.VertexIndicesItr())
{
FVertexID SourceVertexID = VertIDMap[vid];
float Value = InputWeights.Get(SourceVertexID);
OutputWeights->SetValue(vid, &Value);
}
}
});
Pending.Add(MoveTemp(WeightTask));
}
// wait for all work to be done
UE::Tasks::Wait(Pending);
// add non-manifold mapping information if required.
if (!bSrcIsManifold && bVIDsFromNonManifoldMeshDescriptionAttr)
{
TArray<int32> TmpVIDMap;
TmpVIDMap.SetNumUninitialized(VertIDMap.Num());
for (int32 i = 0, I = VertIDMap.Num(); i < I; ++i)
{
TmpVIDMap[i] = VertIDMap[i].GetValue();
}
FNonManifoldMappingSupport::AttachNonManifoldVertexMappingData(TmpVIDMap, MeshOut);
}
if (!bFoundNonDefaultVertexInstanceColor)
{
MeshOut.Attributes()->DisablePrimaryColors();
}
}
// free maps if no longer needed
if (!bCalculateMaps)
{
TriIDMap.Empty();
VertIDMap.Empty();
}
FDateTime Time_AfterAttribs = FDateTime::Now();
if (bPrintDebugMessages)
{
UE_LOG(LogTemp, Warning, TEXT("FMeshDescriptionToDynamicMesh: Conversion Timing: Triangles %fs Attributbes %fs"),
(Time_AfterTriangles - Time_AfterVertices).GetTotalSeconds(), (Time_AfterAttribs - Time_AfterTriangles).GetTotalSeconds());
int NumUVs = (MeshOut.HasAttributes() && NumUVLayers > 0) ? MeshOut.Attributes()->PrimaryUV()->MaxElementID() : 0;
int NumNormals = (NormalOverlay != nullptr) ? NormalOverlay->MaxElementID() : 0;
UE_LOG(LogTemp, Warning, TEXT("FMeshDescriptionToDynamicMesh: FDynamicMesh verts %d triangles %d (primary) uvs %d normals %d"), MeshOut.MaxVertexID(), MeshOut.MaxTriangleID(), NumUVs, NumNormals);
}
}
template<typename RealType>
static void CopyTangents_Internal(const FMeshDescription* SourceMesh, const FDynamicMesh3* TargetMesh, TMeshTangents<RealType>* TangentsOut, const TArray<FTriangleID>& TriIDMap)
{
FStaticMeshConstAttributes Attributes(*SourceMesh);
TArrayView<const FVector3f> InstanceNormals = Attributes.GetVertexInstanceNormals().GetRawArray();
TArrayView<const FVector3f> InstanceTangents = Attributes.GetVertexInstanceTangents().GetRawArray();
TArrayView<const float> InstanceSigns = Attributes.GetVertexInstanceBinormalSigns().GetRawArray();
if (!ensureMsgf(InstanceNormals.Num() != 0, TEXT("Cannot CopyTangents from MeshDescription with invalid Instance Normals"))) return;
if (!ensureMsgf(InstanceTangents.Num() != 0, TEXT("Cannot CopyTangents from MeshDescription with invalid Instance Tangents"))) return;
if (!ensureMsgf(InstanceSigns.Num() != 0, TEXT("Cannot CopyTangents from MeshDescription with invalid Instance BinormalSigns"))) return;
TangentsOut->SetMesh(TargetMesh);
TangentsOut->InitializeTriVertexTangents(false);
for (int32 TriID : TargetMesh->TriangleIndicesItr())
{
const FTriangleID TriangleID = TriIDMap[TriID];
TArrayView<const FVertexInstanceID> InstanceTri = SourceMesh->GetTriangleVertexInstances(TriangleID);
for (int32 j = 0; j < 3; ++j)
{
FVector Normal(InstanceNormals[InstanceTri[j]]);
FVector Tangent(InstanceTangents[InstanceTri[j]]);
float BitangentSign = InstanceSigns[InstanceTri[j]];
TVector<RealType> Bitangent = VectorUtil::Bitangent((TVector<RealType>)Normal, (TVector<RealType>)Tangent, (RealType)BitangentSign);
Tangent.Normalize();
UE::Geometry::Normalize(Bitangent);
TangentsOut->SetPerTriangleTangent(TriID, j, (TVector<RealType>)Tangent, (TVector<RealType>)Bitangent);
}
}
}
void FMeshDescriptionToDynamicMesh::CopyTangents(const FMeshDescription* SourceMesh, const FDynamicMesh3* TargetMesh, TMeshTangents<float>* TangentsOut)
{
if (!ensureMsgf(bCalculateMaps, TEXT("Cannot CopyTangents unless Maps were calculated"))) return;
int32 TargetTriCount = TargetMesh->TriangleCount();
if (!ensureMsgf(TriIDMap.Num() == TargetTriCount, TEXT("Tried to CopyTangents to mesh with different triangle count"))) return;
CopyTangents_Internal<float>(SourceMesh, TargetMesh, TangentsOut, TriIDMap);
}
void FMeshDescriptionToDynamicMesh::CopyTangents(const FMeshDescription* SourceMesh, const FDynamicMesh3* TargetMesh, TMeshTangents<double>* TangentsOut)
{
if (!ensureMsgf(bCalculateMaps, TEXT("Cannot CopyTangents unless Maps were calculated"))) return;
if (!ensureMsgf(TriIDMap.Num() == TargetMesh->TriangleCount(), TEXT("Tried to CopyTangents to mesh with different triangle count"))) return;
CopyTangents_Internal<double>(SourceMesh, TargetMesh, TangentsOut, TriIDMap);
}
void FMeshDescriptionToDynamicMesh::ApplyVertexColorTransform(FVector4f& Color) const
{
if (bTransformVertexColorsLinearToSRGB)
{
// The corollary to bTransformVertexColorsSRGBToLinear in DynamicMeshToMeshDescription.
// See DynamicMeshToMeshDescription::ApplyVertexColorTransform(..).
//
// StaticMeshes store vertex colors as FColor. The StaticMesh build always encodes
// FColors as SRGB to ensure a good distribution of float values across the 8-bit range.
//
// Since there is no currently defined gamma space convention for vertex colors in
// engine, an option is provided to pre-transform vertex colors (SRGB To Linear) when
// writing out a MeshDescription.
//
// We similarly provide the inverse of that optional pre-transformation to maintain
// color space consistency in our usage.
LinearColors::LinearToSRGB(Color);
}
}