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

3203 lines
124 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "StaticMeshOperations.h"
#include "StaticMeshAttributes.h"
#include "UVMapSettings.h"
#include "Async/ParallelFor.h"
#include "LayoutUV.h"
#include "MeshUtilitiesCommon.h"
#include "Misc/SecureHash.h"
#include "OverlappingCorners.h"
#include "RawMesh.h"
#include "Modules/ModuleManager.h"
#if WITH_EDITOR
#include "IGeometryProcessingInterfacesModule.h"
#include "GeometryProcessingInterfaces/MeshAutoUV.h"
#endif
#if WITH_MIKKTSPACE
#include "mikktspace.h"
#endif //WITH_MIKKTSPACE
DEFINE_LOG_CATEGORY(LogStaticMeshOperations);
#define LOCTEXT_NAMESPACE "StaticMeshOperations"
static bool GetPolygonTangentsAndNormals(FMeshDescription& MeshDescription,
FPolygonID PolygonID,
float ComparisonThreshold,
TVertexAttributesConstRef<const FVector3f> VertexPositions,
TVertexInstanceAttributesConstRef<const FVector2f> VertexUVs,
TPolygonAttributesRef<FVector3f> PolygonNormals,
TPolygonAttributesRef<FVector3f> PolygonTangents,
TPolygonAttributesRef<FVector3f> PolygonBinormals,
TPolygonAttributesRef<FVector3f> PolygonCenters)
{
bool bValidNTBs = true;
// Calculate the tangent basis for the polygon, based on the average of all constituent triangles
FVector3f Normal(FVector3f::ZeroVector);
FVector3f Tangent(FVector3f::ZeroVector);
FVector3f Binormal(FVector3f::ZeroVector);
FVector3f Center(FVector3f::ZeroVector);
// Calculate the center of this polygon
TArray<FVertexInstanceID, TInlineAllocator<4>> VertexInstanceIDs = MeshDescription.GetPolygonVertexInstances<TInlineAllocator<4>>(PolygonID);
for (const FVertexInstanceID& VertexInstanceID : VertexInstanceIDs)
{
Center += VertexPositions[MeshDescription.GetVertexInstanceVertex(VertexInstanceID)];
}
Center /= float(VertexInstanceIDs.Num());
// GetSafeNormal compare the squareSum to the tolerance.
const float SquareComparisonThreshold = FMath::Max(ComparisonThreshold * ComparisonThreshold, MIN_flt);
for (const FTriangleID& TriangleID : MeshDescription.GetPolygonTriangles(PolygonID))
{
TArrayView<const FVertexInstanceID> TriangleVertexInstances = MeshDescription.GetTriangleVertexInstances(TriangleID);
const FVertexID VertexID0 = MeshDescription.GetVertexInstanceVertex(TriangleVertexInstances[0]);
const FVertexID VertexID1 = MeshDescription.GetVertexInstanceVertex(TriangleVertexInstances[1]);
const FVertexID VertexID2 = MeshDescription.GetVertexInstanceVertex(TriangleVertexInstances[2]);
const FVector3f Position0 = VertexPositions[VertexID0];
const FVector3f DPosition1 = VertexPositions[VertexID1] - Position0;
const FVector3f DPosition2 = VertexPositions[VertexID2] - Position0;
const FVector2f UV0 = VertexUVs[TriangleVertexInstances[0]];
const FVector2f DUV1 = VertexUVs[TriangleVertexInstances[1]] - UV0;
const FVector2f DUV2 = VertexUVs[TriangleVertexInstances[2]] - UV0;
// We have a left-handed coordinate system, but a counter-clockwise winding order
// Hence normal calculation has to take the triangle vectors cross product in reverse.
FVector3f TmpNormal = FVector3f::CrossProduct(DPosition2, DPosition1).GetSafeNormal(SquareComparisonThreshold);
if (!TmpNormal.IsNearlyZero(ComparisonThreshold))
{
FMatrix44f ParameterToLocal(
DPosition1,
DPosition2,
Position0,
FVector3f::ZeroVector
);
FMatrix44f ParameterToTexture(
FPlane4f(DUV1.X, DUV1.Y, 0, 0),
FPlane4f(DUV2.X, DUV2.Y, 0, 0),
FPlane4f(UV0.X, UV0.Y, 1, 0),
FPlane4f(0, 0, 0, 1)
);
// Use InverseSlow to catch singular matrices. Inverse can miss this sometimes.
const FMatrix44f TextureToLocal = ParameterToTexture.Inverse() * ParameterToLocal;
FVector3f TmpTangent = TextureToLocal.TransformVector(FVector3f(1, 0, 0)).GetSafeNormal();
FVector3f TmpBinormal = TextureToLocal.TransformVector(FVector3f(0, 1, 0)).GetSafeNormal();
FVector3f::CreateOrthonormalBasis(TmpTangent, TmpBinormal, TmpNormal);
if (TmpTangent.IsNearlyZero() || TmpTangent.ContainsNaN()
|| TmpBinormal.IsNearlyZero() || TmpBinormal.ContainsNaN())
{
TmpTangent = FVector3f::ZeroVector;
TmpBinormal = FVector3f::ZeroVector;
bValidNTBs = false;
}
if (TmpNormal.IsNearlyZero() || TmpNormal.ContainsNaN())
{
TmpNormal = FVector3f::ZeroVector;
bValidNTBs = false;
}
Normal += TmpNormal;
Tangent += TmpTangent;
Binormal += TmpBinormal;
}
else
{
//This will force a recompute of the normals and tangents
Normal = FVector3f::ZeroVector;
Tangent = FVector3f::ZeroVector;
Binormal = FVector3f::ZeroVector;
// The polygon is degenerated
bValidNTBs = false;
}
}
PolygonNormals[PolygonID] = Normal.GetSafeNormal();
PolygonTangents[PolygonID] = Tangent.GetSafeNormal();
PolygonBinormals[PolygonID] = Binormal.GetSafeNormal();
PolygonCenters[PolygonID] = Center;
return bValidNTBs;
}
void FStaticMeshOperations::ComputePolygonTangentsAndNormals(FMeshDescription& MeshDescription, float ComparisonThreshold)
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshOperations::ComputePolygonTangentsAndNormals_Selection);
FStaticMeshAttributes Attributes(MeshDescription);
Attributes.RegisterPolygonNormalAndTangentAttributes();
TArray<FPolygonID> PolygonIDs;
PolygonIDs.Reserve(MeshDescription.Polygons().Num());
for (const FPolygonID Polygon : MeshDescription.Polygons().GetElementIDs())
{
PolygonIDs.Add(Polygon);
}
// Split work in batch to reduce call overhead
const int32 BatchSize = 8 * 1024;
const int32 BatchCount = 1 + PolygonIDs.Num() / BatchSize;
ParallelFor(BatchCount,
[&PolygonIDs, &BatchSize, &ComparisonThreshold, &MeshDescription, &Attributes](int32 BatchIndex)
{
TVertexAttributesConstRef<FVector3f> VertexPositions = Attributes.GetVertexPositions();
TVertexInstanceAttributesConstRef<FVector2f> VertexUVs = Attributes.GetVertexInstanceUVs();
TPolygonAttributesRef<FVector3f> PolygonNormals = Attributes.GetPolygonNormals();
TPolygonAttributesRef<FVector3f> PolygonTangents = Attributes.GetPolygonTangents();
TPolygonAttributesRef<FVector3f> PolygonBinormals = Attributes.GetPolygonBinormals();
TPolygonAttributesRef<FVector3f> PolygonCenters = Attributes.GetPolygonCenters();
FVertexInstanceArray& VertexInstanceArray = MeshDescription.VertexInstances();
FVertexArray& VertexArray = MeshDescription.Vertices();
FPolygonArray& PolygonArray = MeshDescription.Polygons();
int32 Indice = BatchIndex * BatchSize;
int32 LastIndice = FMath::Min(Indice + BatchSize, PolygonIDs.Num());
for (; Indice < LastIndice; ++Indice)
{
const FPolygonID PolygonID = PolygonIDs[Indice];
if (!PolygonNormals[PolygonID].IsNearlyZero())
{
//By pass normal calculation if its already done
continue;
}
GetPolygonTangentsAndNormals(MeshDescription, PolygonID, ComparisonThreshold, VertexPositions, VertexUVs, PolygonNormals, PolygonTangents, PolygonBinormals, PolygonCenters);
}
}
);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
static TTuple<FVector3f, FVector3f, FVector3f> GetTriangleTangentsAndNormalsWithUV(float ComparisonThreshold, TArrayView<const FVector3f> VertexPositions, TArrayView<const FVector2D> VertexUVs)
{
// GetSafeNormal compare the squareSum to the tolerance.
const float SquareComparisonThreshold = FMath::Max(ComparisonThreshold * ComparisonThreshold, MIN_flt);
const FVector3f Position0 = VertexPositions[0];
// If the positions deltas are too small, we get a zero vector out.
const FVector3f DPosition1 = VertexPositions[1] - Position0;
const FVector3f DPosition2 = VertexPositions[2] - Position0;
const FVector2f UV0 = FVector2f(VertexUVs[0]);
const FVector2f DUV1 = FVector2f(VertexUVs[1]) - UV0;
const FVector2f DUV2 = FVector2f(VertexUVs[2]) - UV0;
// We have a left-handed coordinate system, but a counter-clockwise winding order
// Hence normal calculation has to take the triangle vectors cross product in reverse.
// If we got a zero vector out above, then this is also zero
FVector3f Normal = FVector3f::CrossProduct(DPosition2, DPosition1).GetSafeNormal(SquareComparisonThreshold);
if (!Normal.IsNearlyZero(ComparisonThreshold))
{
FMatrix44f ParameterToLocal(
DPosition1,
DPosition2,
Position0,
FVector3f::ZeroVector
);
FMatrix44f ParameterToTexture(
FPlane4f(DUV1.X, DUV1.Y, 0, 0),
FPlane4f(DUV2.X, DUV2.Y, 0, 0),
FPlane4f(UV0.X, UV0.Y, 1, 0),
FPlane4f(0, 0, 0, 1)
);
// Use InverseSlow to catch singular matrices. Inverse can miss this sometimes.
const FMatrix44f TextureToLocal = ParameterToTexture.Inverse() * ParameterToLocal;
FVector3f Tangent = TextureToLocal.TransformVector(FVector3f(1, 0, 0)).GetSafeNormal();
FVector3f Binormal = TextureToLocal.TransformVector(FVector3f(0, 1, 0)).GetSafeNormal();
FVector3f::CreateOrthonormalBasis(Tangent, Binormal, Normal);
if (Tangent.IsNearlyZero() || Tangent.ContainsNaN()
|| Binormal.IsNearlyZero() || Binormal.ContainsNaN())
{
Tangent = FVector3f::ZeroVector;
Binormal = FVector3f::ZeroVector;
}
if (Normal.IsNearlyZero() || Normal.ContainsNaN())
{
Normal = FVector3f::ZeroVector;
}
return MakeTuple(Normal.GetSafeNormal(), Tangent.GetSafeNormal(), Binormal.GetSafeNormal());
}
else
{
// This will force a recompute of the normals and tangents
return MakeTuple(FVector3f::ZeroVector, FVector3f::ZeroVector, FVector3f::ZeroVector);
}
}
// Create a normal using the triangle plane, but use the Duff & Frisvad algorithm (see Duff 2017 in JCGT) to construct a consistent tangent from a
// single vector.
static TTuple<FVector3f, FVector3f, FVector3f> GetTriangleTangentsAndNormalsWithNoUVs(float ComparisonThreshold, TArrayView<const FVector3f> VertexPositions)
{
// GetSafeNormal compare the squareSum to the tolerance.
const float SquareComparisonThreshold = FMath::Max(ComparisonThreshold * ComparisonThreshold, MIN_flt);
const FVector3f Position0 = VertexPositions[0];
// If the positions deltas are too small, we get a zero vector out.
const FVector3f DPosition1 = VertexPositions[1] - Position0;
const FVector3f DPosition2 = VertexPositions[2] - Position0;
// We have a left-handed coordinate system, but a counter-clockwise winding order
// Hence normal calculation has to take the triangle vectors cross product in reverse.
// If we got a zero vector out above, then this is also zero
FVector3f Normal = FVector3f::CrossProduct(DPosition2, DPosition1).GetSafeNormal(SquareComparisonThreshold);
if (!Normal.Normalize(ComparisonThreshold))
{
return MakeTuple(FVector3f::ZeroVector, FVector3f::ZeroVector, FVector3f::ZeroVector);
}
FVector3f Tangent, Binormal;
if (Normal.Z < 0.0f)
{
const float A = 1.0f / (1.0f - Normal.Z);
const float B = Normal.X * Normal.Y * A;
Tangent = FVector3f(1.0f - Normal.X * Normal.X * A, -B, Normal.X);
Binormal = FVector3f(B, Normal.Y * Normal.Y * A - 1.0f, -Normal.Y);
}
else
{
const float A = 1.0f / (1.0f + Normal.Z);
const float B = -Normal.X * Normal.Y * A;
Tangent = FVector3f(1.0f - Normal.X * Normal.X * A, B, -Normal.X);
Binormal = FVector3f(B, 1.0f - Normal.Y * Normal.Y * A, -Normal.Y);
}
// The above algorithm guarantees orthogonality and normalization of the tangent & binormal if the normal vector is already normalized.
return MakeTuple(Normal, Tangent, Binormal);
}
void FStaticMeshOperations::ComputeTriangleTangentsAndNormals(FMeshDescription& MeshDescription, float ComparisonThreshold, const TCHAR* DebugName)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshOperations::ComputeTriangleTangentsAndNormals_Selection);
FStaticMeshAttributes Attributes(MeshDescription);
Attributes.RegisterTriangleNormalAndTangentAttributes();
// Check that the mesh description is compact
const int32 NumTriangles = MeshDescription.Triangles().Num();
if (MeshDescription.NeedsCompact())
{
FElementIDRemappings Remappings;
MeshDescription.Compact(Remappings);
}
// Split work in batch to reduce call overhead
const int32 BatchSize = 8 * 1024;
const int32 BatchCount = (NumTriangles + BatchSize - 1) / BatchSize;
ParallelFor( TEXT("ComputeTriangleTangentsAndNormals.PF"), BatchCount,1,
[BatchSize, ComparisonThreshold, NumTriangles, &Attributes, DebugName](int32 BatchIndex)
{
TArrayView<const FVector3f> VertexPositions = Attributes.GetVertexPositions().GetRawArray();
TArrayView<const FVector2f> VertexUVs;
TArrayView<const FVertexID> TriangleVertexIDs = Attributes.GetTriangleVertexIndices().GetRawArray();
TArrayView<const FVertexInstanceID> TriangleVertexInstanceIDs = Attributes.GetTriangleVertexInstanceIndices().GetRawArray();
TArrayView<FVector3f> TriangleNormals = Attributes.GetTriangleNormals().GetRawArray();
TArrayView<FVector3f> TriangleTangents = Attributes.GetTriangleTangents().GetRawArray();
TArrayView<FVector3f> TriangleBinormals = Attributes.GetTriangleBinormals().GetRawArray();
if (Attributes.GetVertexInstanceUVs().GetNumChannels() > 0)
{
VertexUVs = Attributes.GetVertexInstanceUVs().GetRawArray(0);
}
int32 StartIndex = BatchIndex * BatchSize;
int32 TriIndex = StartIndex * 3;
int32 EndIndex = FMath::Min(StartIndex + BatchSize, NumTriangles);
for (; StartIndex < EndIndex; ++StartIndex, TriIndex += 3)
{
if (!TriangleNormals[StartIndex].IsNearlyZero())
{
// Bypass normal calculation if it's already done
continue;
}
FVector3f TriangleVertexPositions[3] =
{
VertexPositions[TriangleVertexIDs[TriIndex]],
VertexPositions[TriangleVertexIDs[TriIndex + 1]],
VertexPositions[TriangleVertexIDs[TriIndex + 2]]
};
if (TriangleVertexPositions[0].ContainsNaN() ||
TriangleVertexPositions[1].ContainsNaN() ||
TriangleVertexPositions[2].ContainsNaN())
{
UE_CLOG(DebugName != nullptr, LogStaticMeshOperations, Warning, TEXT("Static Mesh %s has NaNs in it's vertex positions! Triangle index %d -- using identity for tangent basis."), DebugName, StartIndex);
TriangleNormals[StartIndex] = FVector3f(1, 0, 0);
TriangleTangents[StartIndex] = FVector3f(0, 1, 0);
TriangleBinormals[StartIndex] = FVector3f(0, 0, 1);
continue;
}
TTuple<FVector3f, FVector3f, FVector3f> Result;
if (!VertexUVs.IsEmpty())
{
FVector2D TriangleUVs[3] =
{
FVector2D(VertexUVs[TriangleVertexInstanceIDs[TriIndex]]),
FVector2D(VertexUVs[TriangleVertexInstanceIDs[TriIndex + 1]]),
FVector2D(VertexUVs[TriangleVertexInstanceIDs[TriIndex + 2]])
};
if (TriangleUVs[0].ContainsNaN() ||
TriangleUVs[1].ContainsNaN() ||
TriangleUVs[2].ContainsNaN())
{
UE_CLOG(DebugName != nullptr, LogStaticMeshOperations, Warning, TEXT("Static Mesh %s has NaNs in it's vertex uvs! Triangle index %d -- using identity for tangent basis."), DebugName, StartIndex);
TriangleNormals[StartIndex] = FVector3f(1, 0, 0);
TriangleTangents[StartIndex] = FVector3f(0, 1, 0);
TriangleBinormals[StartIndex] = FVector3f(0, 0, 1);
continue;
}
Result = GetTriangleTangentsAndNormalsWithUV(ComparisonThreshold, TriangleVertexPositions, TriangleUVs);
}
else
{
Result = GetTriangleTangentsAndNormalsWithNoUVs(ComparisonThreshold, TriangleVertexPositions);
}
TriangleNormals[StartIndex] = Result.Get<0>();
TriangleTangents[StartIndex] = Result.Get<1>();
TriangleBinormals[StartIndex] = Result.Get<2>();
}
}
);
}
void FStaticMeshOperations::DetermineEdgeHardnessesFromVertexInstanceNormals(FMeshDescription& MeshDescription, float Tolerance)
{
FStaticMeshAttributes Attributes(MeshDescription);
TVertexInstanceAttributesRef<const FVector3f> VertexNormals = Attributes.GetVertexInstanceNormals();
TEdgeAttributesRef<bool> EdgeHardnesses = Attributes.GetEdgeHardnesses();
// Holds unique vertex instance IDs for a given edge vertex
TArray<FVertexInstanceID> UniqueVertexInstanceIDs;
for (const FEdgeID EdgeID : MeshDescription.Edges().GetElementIDs())
{
// Get list of polygons connected to this edge
TArray<FPolygonID, TInlineAllocator<2>> ConnectedPolygonIDs = MeshDescription.GetEdgeConnectedPolygons<TInlineAllocator<2>>(EdgeID);
if (ConnectedPolygonIDs.Num() == 0)
{
// What does it mean if an edge has no connected polygons? For now we just skip it
continue;
}
// Assume by default that the edge is soft - but as soon as any vertex instance belonging to a connected polygon
// has a distinct normal from the others (within the given tolerance), we mark it as hard.
// The exception is if an edge has exactly one connected polygon: in this case we automatically deem it a hard edge.
bool bEdgeIsHard = (ConnectedPolygonIDs.Num() == 1);
// Examine vertices on each end of the edge, if we haven't yet identified it as 'hard'
for (int32 VertexIndex = 0; !bEdgeIsHard && VertexIndex < 2; ++VertexIndex)
{
const FVertexID VertexID = MeshDescription.GetEdgeVertex(EdgeID, VertexIndex);
const int32 ReservedElements = 4;
UniqueVertexInstanceIDs.Reset(ReservedElements);
// Get a list of all vertex instances for this vertex which form part of any polygon connected to the edge
for (const FVertexInstanceID& VertexInstanceID : MeshDescription.GetVertexVertexInstanceIDs(VertexID))
{
for (const FPolygonID& PolygonID : MeshDescription.GetVertexInstanceConnectedPolygons<TInlineAllocator<8>>(VertexInstanceID))
{
if (ConnectedPolygonIDs.Contains(PolygonID))
{
UniqueVertexInstanceIDs.AddUnique(VertexInstanceID);
break;
}
}
}
check(UniqueVertexInstanceIDs.Num() > 0);
// First unique vertex instance is used as a reference against which the others are compared.
// (not a perfect approach: really the 'median' should be used as a reference)
const FVector3f ReferenceNormal = VertexNormals[UniqueVertexInstanceIDs[0]];
for (int32 Index = 1; Index < UniqueVertexInstanceIDs.Num(); ++Index)
{
if (!VertexNormals[UniqueVertexInstanceIDs[Index]].Equals(ReferenceNormal, Tolerance))
{
bEdgeIsHard = true;
break;
}
}
}
EdgeHardnesses[EdgeID] = bEdgeIsHard;
}
}
//////////////////
struct FVertexInfo
{
FVertexInfo()
{
TriangleID = INDEX_NONE;
VertexInstanceID = INDEX_NONE;
UVs = FVector2f(0.0f, 0.0f);
}
FTriangleID TriangleID;
FVertexInstanceID VertexInstanceID;
FVector2f UVs;
//Most of the time a edge has two triangles
TArray<FEdgeID, TInlineAllocator<2>> EdgeIDs;
};
/** Helper struct for building acceleration structures. */
namespace MeshDescriptionOperationNamespace
{
struct FIndexAndZ
{
float Z;
int32 Index;
const FVector3f *OriginalVector;
/** Default constructor. */
FIndexAndZ() {}
/** Initialization constructor. */
FIndexAndZ(int32 InIndex, const FVector3f& V)
{
Z = 0.30f * V.X + 0.33f * V.Y + 0.37f * V.Z;
Index = InIndex;
OriginalVector = &V;
}
};
/** Sorting function for vertex Z/index pairs. */
struct FCompareIndexAndZ
{
FORCEINLINE bool operator()(const FIndexAndZ& A, const FIndexAndZ& B) const { return A.Z < B.Z; }
};
}
void FStaticMeshOperations::ConvertHardEdgesToSmoothGroup(const FMeshDescription& SourceMeshDescription, TArray<uint32>& FaceSmoothingMasks)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshOperations::ConvertHardEdgesToSmoothGroup);
TMap<FPolygonID, uint32> PolygonSmoothGroup;
PolygonSmoothGroup.Reserve(SourceMeshDescription.Polygons().GetArraySize());
TArray<bool> ConsumedPolygons;
ConsumedPolygons.AddZeroed(SourceMeshDescription.Polygons().GetArraySize());
TMap < FPolygonID, uint32> PolygonAvoidances;
TEdgeAttributesConstRef<bool> EdgeHardnesses = SourceMeshDescription.EdgeAttributes().GetAttributesRef<bool>(MeshAttribute::Edge::IsHard);
int32 TriangleCount = 0;
TArray<FPolygonID> SoftEdgeNeigbors;
TArray<FEdgeID> PolygonEdges;
TArray<FPolygonID> EdgeConnectedPolygons;
TArray<FPolygonID> ConnectedPolygons;
TArray<FPolygonID> LastConnectedPolygons;
for (const FPolygonID PolygonID : SourceMeshDescription.Polygons().GetElementIDs())
{
TriangleCount += SourceMeshDescription.GetPolygonTriangles(PolygonID).Num();
if (ConsumedPolygons[PolygonID.GetValue()])
{
continue;
}
ConnectedPolygons.Reset();
LastConnectedPolygons.Reset();
ConnectedPolygons.Add(PolygonID);
LastConnectedPolygons.Add(INDEX_NONE);
while (ConnectedPolygons.Num() > 0)
{
check(LastConnectedPolygons.Num() == ConnectedPolygons.Num());
FPolygonID LastPolygonID = LastConnectedPolygons.Pop(EAllowShrinking::No);
FPolygonID CurrentPolygonID = ConnectedPolygons.Pop(EAllowShrinking::No);
if (ConsumedPolygons[CurrentPolygonID.GetValue()])
{
continue;
}
SoftEdgeNeigbors.Reset();
uint32& SmoothGroup = PolygonSmoothGroup.FindOrAdd(CurrentPolygonID);
uint32 AvoidSmoothGroup = 0;
uint32 NeighborSmoothGroup = 0;
const uint32 LastSmoothGroupValue = (LastPolygonID == INDEX_NONE) ? 0 : PolygonSmoothGroup[LastPolygonID];
PolygonEdges.Reset();
SourceMeshDescription.GetPolygonPerimeterEdges(CurrentPolygonID, PolygonEdges);
for (const FEdgeID& EdgeID : PolygonEdges)
{
bool bIsHardEdge = EdgeHardnesses[EdgeID];
EdgeConnectedPolygons.Reset();
SourceMeshDescription.GetEdgeConnectedPolygons(EdgeID, EdgeConnectedPolygons);
for (const FPolygonID& EdgePolygonID : EdgeConnectedPolygons)
{
if (EdgePolygonID == CurrentPolygonID)
{
continue;
}
uint32 SmoothValue = 0;
if (PolygonSmoothGroup.Contains(EdgePolygonID))
{
SmoothValue = PolygonSmoothGroup[EdgePolygonID];
}
if (bIsHardEdge) //Hard Edge
{
AvoidSmoothGroup |= SmoothValue;
}
else
{
NeighborSmoothGroup |= SmoothValue;
//Put all none hard edge polygon in the next iteration
if (!ConsumedPolygons[EdgePolygonID.GetValue()])
{
ConnectedPolygons.Add(EdgePolygonID);
LastConnectedPolygons.Add(CurrentPolygonID);
}
else
{
SoftEdgeNeigbors.Add(EdgePolygonID);
}
}
}
}
if (AvoidSmoothGroup != 0)
{
PolygonAvoidances.FindOrAdd(CurrentPolygonID) = AvoidSmoothGroup;
//find neighbor avoidance
for (FPolygonID& NeighborID : SoftEdgeNeigbors)
{
if (!PolygonAvoidances.Contains(NeighborID))
{
continue;
}
AvoidSmoothGroup |= PolygonAvoidances[NeighborID];
}
uint32 NewSmoothGroup = 1;
while ((NewSmoothGroup & AvoidSmoothGroup) != 0 && NewSmoothGroup < MAX_uint32)
{
//Shift the smooth group
NewSmoothGroup = NewSmoothGroup << 1;
}
SmoothGroup = NewSmoothGroup;
//Apply to all neighboard
for (FPolygonID& NeighborID : SoftEdgeNeigbors)
{
PolygonSmoothGroup[NeighborID] |= NewSmoothGroup;
}
}
else if (NeighborSmoothGroup != 0)
{
SmoothGroup |= LastSmoothGroupValue | NeighborSmoothGroup;
}
else
{
SmoothGroup = 1;
}
ConsumedPolygons[CurrentPolygonID.GetValue()] = true;
}
}
//Set the smooth group in the FaceSmoothingMasks parameter
check(FaceSmoothingMasks.Num() == TriangleCount);
int32 TriangleIndex = 0;
for (const FPolygonID PolygonID : SourceMeshDescription.Polygons().GetElementIDs())
{
const uint32 PolygonSmoothValue = PolygonSmoothGroup[PolygonID];
for (int32 i = 0, Num = SourceMeshDescription.GetPolygonTriangles(PolygonID).Num(); i < Num; ++i)
{
FaceSmoothingMasks[TriangleIndex++] = PolygonSmoothValue;
}
}
}
void FStaticMeshOperations::ConvertSmoothGroupToHardEdges(const TArray<uint32>& FaceSmoothingMasks, FMeshDescription& DestinationMeshDescription)
{
TEdgeAttributesRef<bool> EdgeHardnesses = DestinationMeshDescription.EdgeAttributes().GetAttributesRef<bool>(MeshAttribute::Edge::IsHard);
TArray<bool> ConsumedPolygons;
ConsumedPolygons.AddZeroed(DestinationMeshDescription.Polygons().Num());
for (const FPolygonID PolygonID : DestinationMeshDescription.Polygons().GetElementIDs())
{
if (ConsumedPolygons[PolygonID.GetValue()])
{
continue;
}
TArray<FPolygonID> ConnectedPolygons;
ConnectedPolygons.Add(PolygonID);
while (ConnectedPolygons.Num() > 0)
{
FPolygonID CurrentPolygonID = ConnectedPolygons.Pop(EAllowShrinking::No);
int32 CurrentPolygonIDValue = CurrentPolygonID.GetValue();
check(FaceSmoothingMasks.IsValidIndex(CurrentPolygonIDValue));
const uint32 ReferenceSmoothGroup = FaceSmoothingMasks[CurrentPolygonIDValue];
TArray<FEdgeID> PolygonEdges;
DestinationMeshDescription.GetPolygonPerimeterEdges(CurrentPolygonID, PolygonEdges);
for (const FEdgeID& EdgeID : PolygonEdges)
{
const bool bIsHardEdge = EdgeHardnesses[EdgeID];
if (bIsHardEdge)
{
continue;
}
const TArray<FPolygonID>& EdgeConnectedPolygons = DestinationMeshDescription.GetEdgeConnectedPolygons(EdgeID);
for (const FPolygonID& EdgePolygonID : EdgeConnectedPolygons)
{
int32 EdgePolygonIDValue = EdgePolygonID.GetValue();
if (EdgePolygonID == CurrentPolygonID || ConsumedPolygons[EdgePolygonIDValue])
{
continue;
}
check(FaceSmoothingMasks.IsValidIndex(EdgePolygonIDValue));
const uint32 TestSmoothGroup = FaceSmoothingMasks[EdgePolygonIDValue];
if ((TestSmoothGroup & ReferenceSmoothGroup) == 0)
{
EdgeHardnesses[EdgeID] = true;
break;
}
else
{
ConnectedPolygons.Add(EdgePolygonID);
}
}
}
ConsumedPolygons[CurrentPolygonID.GetValue()] = true;
}
}
}
void FStaticMeshOperations::ConvertToRawMesh(const FMeshDescription& SourceMeshDescription, FRawMesh& DestinationRawMesh, const TMap<FName, int32>& MaterialMap)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshOperations::ConvertToRawMesh);
DestinationRawMesh.Empty();
//Gather all array data
FStaticMeshConstAttributes Attributes(SourceMeshDescription);
TVertexAttributesConstRef<FVector3f> VertexPositions = Attributes.GetVertexPositions();
TVertexInstanceAttributesConstRef<FVector3f> VertexInstanceNormals = Attributes.GetVertexInstanceNormals();
TVertexInstanceAttributesConstRef<FVector3f> VertexInstanceTangents = Attributes.GetVertexInstanceTangents();
TVertexInstanceAttributesConstRef<float> VertexInstanceBinormalSigns = Attributes.GetVertexInstanceBinormalSigns();
TVertexInstanceAttributesConstRef<FVector4f> VertexInstanceColors = Attributes.GetVertexInstanceColors();
TVertexInstanceAttributesConstRef<FVector2f> VertexInstanceUVs = Attributes.GetVertexInstanceUVs();
TPolygonGroupAttributesConstRef<FName> PolygonGroupMaterialSlotName = Attributes.GetPolygonGroupMaterialSlotNames();
DestinationRawMesh.VertexPositions.AddZeroed(SourceMeshDescription.Vertices().Num());
TArray<int32> RemapVerts;
RemapVerts.AddZeroed(SourceMeshDescription.Vertices().GetArraySize());
int32 VertexIndex = 0;
for (const FVertexID VertexID : SourceMeshDescription.Vertices().GetElementIDs())
{
DestinationRawMesh.VertexPositions[VertexIndex] = VertexPositions[VertexID];
RemapVerts[VertexID.GetValue()] = VertexIndex;
++VertexIndex;
}
int32 TriangleNumber = SourceMeshDescription.Triangles().Num();
DestinationRawMesh.FaceMaterialIndices.AddZeroed(TriangleNumber);
DestinationRawMesh.FaceSmoothingMasks.AddZeroed(TriangleNumber);
bool bHasVertexColor = HasVertexColor(SourceMeshDescription);
int32 WedgeIndexNumber = TriangleNumber * 3;
if (bHasVertexColor)
{
DestinationRawMesh.WedgeColors.AddZeroed(WedgeIndexNumber);
}
DestinationRawMesh.WedgeIndices.AddZeroed(WedgeIndexNumber);
DestinationRawMesh.WedgeTangentX.AddZeroed(WedgeIndexNumber);
DestinationRawMesh.WedgeTangentY.AddZeroed(WedgeIndexNumber);
DestinationRawMesh.WedgeTangentZ.AddZeroed(WedgeIndexNumber);
int32 ExistingUVCount = VertexInstanceUVs.GetNumChannels();
for (int32 UVIndex = 0; UVIndex < ExistingUVCount; ++UVIndex)
{
DestinationRawMesh.WedgeTexCoords[UVIndex].AddZeroed(WedgeIndexNumber);
}
int32 TriangleIndex = 0;
int32 WedgeIndex = 0;
for (const FPolygonID PolygonID : SourceMeshDescription.Polygons().GetElementIDs())
{
const FPolygonGroupID& PolygonGroupID = SourceMeshDescription.GetPolygonPolygonGroup(PolygonID);
int32 PolygonIDValue = PolygonID.GetValue();
TArrayView<const FTriangleID> TriangleIDs = SourceMeshDescription.GetPolygonTriangles(PolygonID);
for (const FTriangleID& TriangleID : TriangleIDs)
{
if (MaterialMap.Num() > 0 && MaterialMap.Contains(PolygonGroupMaterialSlotName[PolygonGroupID]))
{
DestinationRawMesh.FaceMaterialIndices[TriangleIndex] = MaterialMap[PolygonGroupMaterialSlotName[PolygonGroupID]];
}
else
{
DestinationRawMesh.FaceMaterialIndices[TriangleIndex] = PolygonGroupID.GetValue();
}
DestinationRawMesh.FaceSmoothingMasks[TriangleIndex] = 0; //Conversion of soft/hard to smooth mask is done after the geometry is converted
for (int32 Corner = 0; Corner < 3; ++Corner)
{
const FVertexInstanceID VertexInstanceID = SourceMeshDescription.GetTriangleVertexInstance(TriangleID, Corner);
if (bHasVertexColor)
{
DestinationRawMesh.WedgeColors[WedgeIndex] = FLinearColor(VertexInstanceColors[VertexInstanceID]).ToFColor(true);
}
DestinationRawMesh.WedgeIndices[WedgeIndex] = RemapVerts[SourceMeshDescription.GetVertexInstanceVertex(VertexInstanceID).GetValue()];
DestinationRawMesh.WedgeTangentX[WedgeIndex] = VertexInstanceTangents[VertexInstanceID];
DestinationRawMesh.WedgeTangentY[WedgeIndex] = FVector3f::CrossProduct(VertexInstanceNormals[VertexInstanceID], VertexInstanceTangents[VertexInstanceID]).GetSafeNormal() * VertexInstanceBinormalSigns[VertexInstanceID];
DestinationRawMesh.WedgeTangentZ[WedgeIndex] = VertexInstanceNormals[VertexInstanceID];
for (int32 UVIndex = 0; UVIndex < ExistingUVCount; ++UVIndex)
{
DestinationRawMesh.WedgeTexCoords[UVIndex][WedgeIndex] = VertexInstanceUVs.Get(VertexInstanceID, UVIndex);
}
++WedgeIndex;
}
++TriangleIndex;
}
}
//Convert the smoothgroup
ConvertHardEdgesToSmoothGroup(SourceMeshDescription, DestinationRawMesh.FaceSmoothingMasks);
}
//We want to fill the FMeshDescription vertex position mesh attribute with the FRawMesh vertex position
//We will also weld the vertex position (old FRawMesh is not always welded) and construct a mapping array to match the FVertexID
void FillMeshDescriptionVertexPositionNoDuplicate(const TArray<FVector3f>& RawMeshVertexPositions, FMeshDescription& DestinationMeshDescription, TArray<FVertexID>& RemapVertexPosition)
{
TVertexAttributesRef<FVector3f> VertexPositions = DestinationMeshDescription.GetVertexPositions();
const int32 NumVertex = RawMeshVertexPositions.Num();
TMap<int32, int32> TempRemapVertexPosition;
TempRemapVertexPosition.Reserve(NumVertex);
// Create a list of vertex Z/index pairs
TArray<MeshDescriptionOperationNamespace::FIndexAndZ> VertIndexAndZ;
VertIndexAndZ.Reserve(NumVertex);
for (int32 VertexIndex = 0; VertexIndex < NumVertex; ++VertexIndex)
{
VertIndexAndZ.Emplace(VertexIndex, RawMeshVertexPositions[VertexIndex]);
}
// Sort the vertices by z value
VertIndexAndZ.Sort(MeshDescriptionOperationNamespace::FCompareIndexAndZ());
int32 VertexCount = 0;
// Search for duplicates, quickly!
for (int32 i = 0; i < VertIndexAndZ.Num(); i++)
{
int32 Index_i = VertIndexAndZ[i].Index;
if (TempRemapVertexPosition.Contains(Index_i))
{
continue;
}
TempRemapVertexPosition.FindOrAdd(Index_i) = VertexCount;
// only need to search forward, since we add pairs both ways
for (int32 j = i + 1; j < VertIndexAndZ.Num(); j++)
{
if (FMath::Abs(VertIndexAndZ[j].Z - VertIndexAndZ[i].Z) > SMALL_NUMBER)
break; // can't be any more dups
const FVector3f& PositionA = *(VertIndexAndZ[i].OriginalVector);
const FVector3f& PositionB = *(VertIndexAndZ[j].OriginalVector);
if (PositionA.Equals(PositionB, SMALL_NUMBER))
{
TempRemapVertexPosition.FindOrAdd(VertIndexAndZ[j].Index) = VertexCount;
}
}
VertexCount++;
}
//Make sure the vertex are added in the same order to be lossless when converting the FRawMesh
//In case there is a duplicate even reordering it will not be lossless, but MeshDescription do not support
//bad data like duplicated vertex position.
RemapVertexPosition.AddUninitialized(NumVertex);
DestinationMeshDescription.ReserveNewVertices(VertexCount);
TArray<FVertexID> UniqueVertexDone;
UniqueVertexDone.AddUninitialized(VertexCount);
for (int32 VertexIndex = 0; VertexIndex < VertexCount; ++VertexIndex)
{
UniqueVertexDone[VertexIndex] = INDEX_NONE;
}
for (int32 VertexIndex = 0; VertexIndex < NumVertex; ++VertexIndex)
{
int32 RealIndex = TempRemapVertexPosition[VertexIndex];
if (UniqueVertexDone[RealIndex] != INDEX_NONE)
{
RemapVertexPosition[VertexIndex] = UniqueVertexDone[RealIndex];
continue;
}
FVertexID VertexID = DestinationMeshDescription.CreateVertex();
UniqueVertexDone[RealIndex] = VertexID;
VertexPositions[VertexID] = RawMeshVertexPositions[VertexIndex];
RemapVertexPosition[VertexIndex] = VertexID;
}
}
//Discover degenerated triangle
bool IsTriangleDegenerated(const FRawMesh& SourceRawMesh, const TArray<FVertexID>& RemapVertexPosition, const int32 VerticeIndexBase)
{
FVertexID VertexIDs[3];
for (int32 Corner = 0; Corner < 3; ++Corner)
{
int32 VerticeIndex = VerticeIndexBase + Corner;
VertexIDs[Corner] = RemapVertexPosition[SourceRawMesh.WedgeIndices[VerticeIndex]];
}
return (VertexIDs[0] == VertexIDs[1] || VertexIDs[0] == VertexIDs[2] || VertexIDs[1] == VertexIDs[2]);
}
void FStaticMeshOperations::ConvertFromRawMesh(const FRawMesh& SourceRawMesh, FMeshDescription& DestinationMeshDescription, const TMap<int32, FName>& MaterialMap, bool bSkipNormalsAndTangents, const TCHAR* DebugName)
{
DestinationMeshDescription.Empty();
DestinationMeshDescription.ReserveNewVertexInstances(SourceRawMesh.WedgeIndices.Num());
DestinationMeshDescription.ReserveNewPolygons(SourceRawMesh.WedgeIndices.Num() / 3);
//Approximately 2.5 edges per polygons
DestinationMeshDescription.ReserveNewEdges(SourceRawMesh.WedgeIndices.Num() * 2.5f / 3);
//Gather all array data
TVertexInstanceAttributesRef<FVector3f> VertexInstanceNormals = DestinationMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector3f>(MeshAttribute::VertexInstance::Normal);
TVertexInstanceAttributesRef<FVector3f> VertexInstanceTangents = DestinationMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector3f>(MeshAttribute::VertexInstance::Tangent);
TVertexInstanceAttributesRef<float> VertexInstanceBinormalSigns = DestinationMeshDescription.VertexInstanceAttributes().GetAttributesRef<float>(MeshAttribute::VertexInstance::BinormalSign);
TVertexInstanceAttributesRef<FVector4f> VertexInstanceColors = DestinationMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector4f>(MeshAttribute::VertexInstance::Color);
TVertexInstanceAttributesRef<FVector2f> VertexInstanceUVs = DestinationMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2f>(MeshAttribute::VertexInstance::TextureCoordinate);
TPolygonGroupAttributesRef<FName> PolygonGroupImportedMaterialSlotNames = DestinationMeshDescription.PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
int32 NumTexCoords = 0;
int32 MaxTexCoords = MAX_MESH_TEXTURE_COORDS;
TArray<int32> TextureCoordinnateRemapIndex;
TextureCoordinnateRemapIndex.AddZeroed(MaxTexCoords);
for (int32 TextureCoordinnateIndex = 0; TextureCoordinnateIndex < MaxTexCoords; ++TextureCoordinnateIndex)
{
TextureCoordinnateRemapIndex[TextureCoordinnateIndex] = INDEX_NONE;
if (SourceRawMesh.WedgeTexCoords[TextureCoordinnateIndex].Num() == SourceRawMesh.WedgeIndices.Num())
{
TextureCoordinnateRemapIndex[TextureCoordinnateIndex] = NumTexCoords;
NumTexCoords++;
}
}
VertexInstanceUVs.SetNumChannels(NumTexCoords);
//Ensure we do not have any duplicate, We found all duplicated vertex and compact them and build a remap indice array to remap the wedgeindices
TArray<FVertexID> RemapVertexPosition;
FillMeshDescriptionVertexPositionNoDuplicate(SourceRawMesh.VertexPositions, DestinationMeshDescription, RemapVertexPosition);
bool bHasColors = SourceRawMesh.WedgeColors.Num() > 0;
bool bHasTangents = SourceRawMesh.WedgeTangentX.Num() > 0 && SourceRawMesh.WedgeTangentY.Num() > 0;
bool bHasNormals = SourceRawMesh.WedgeTangentZ.Num() > 0;
TArray<FPolygonGroupID> PolygonGroups;
TMap<int32, FPolygonGroupID> MaterialIndexToPolygonGroup;
//Create the PolygonGroups
for (int32 MaterialIndex : SourceRawMesh.FaceMaterialIndices)
{
if (!MaterialIndexToPolygonGroup.Contains(MaterialIndex))
{
FPolygonGroupID PolygonGroupID(MaterialIndex);
DestinationMeshDescription.CreatePolygonGroupWithID(PolygonGroupID);
if (MaterialMap.Contains(MaterialIndex))
{
PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = MaterialMap[MaterialIndex];
}
else
{
PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(*FString::Printf(TEXT("MaterialSlot_%d"), MaterialIndex));
}
PolygonGroups.Add(PolygonGroupID);
MaterialIndexToPolygonGroup.Add(MaterialIndex, PolygonGroupID);
}
}
//Triangles
int32 TriangleCount = SourceRawMesh.WedgeIndices.Num() / 3;
// Reserve enough memory to avoid as much as possible reallocations
DestinationMeshDescription.ReserveNewVertexInstances(SourceRawMesh.WedgeIndices.Num());
DestinationMeshDescription.ReserveNewTriangles(TriangleCount);
DestinationMeshDescription.ReserveNewPolygons(TriangleCount);
DestinationMeshDescription.ReserveNewEdges(TriangleCount * 2);
for (int32 TriangleIndex = 0; TriangleIndex < TriangleCount; ++TriangleIndex)
{
int32 VerticeIndexBase = TriangleIndex * 3;
//Check if the triangle is degenerated and skip the data if its the case
if (IsTriangleDegenerated(SourceRawMesh, RemapVertexPosition, VerticeIndexBase))
{
continue;
}
//PolygonGroup
FPolygonGroupID PolygonGroupID = INDEX_NONE;
FName PolygonGroupImportedMaterialSlotName = NAME_None;
int32 MaterialIndex = SourceRawMesh.FaceMaterialIndices[TriangleIndex];
if (MaterialIndexToPolygonGroup.Contains(MaterialIndex))
{
PolygonGroupID = MaterialIndexToPolygonGroup[MaterialIndex];
}
else if (MaterialMap.Num() > 0 && MaterialMap.Contains(MaterialIndex))
{
PolygonGroupImportedMaterialSlotName = MaterialMap[MaterialIndex];
for (const FPolygonGroupID SearchPolygonGroupID : DestinationMeshDescription.PolygonGroups().GetElementIDs())
{
if (PolygonGroupImportedMaterialSlotNames[SearchPolygonGroupID] == PolygonGroupImportedMaterialSlotName)
{
PolygonGroupID = SearchPolygonGroupID;
break;
}
}
}
if (PolygonGroupID == INDEX_NONE)
{
PolygonGroupID = DestinationMeshDescription.CreatePolygonGroup();
PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = (PolygonGroupImportedMaterialSlotName == NAME_None) ? FName(*FString::Printf(TEXT("MaterialSlot_%d"), MaterialIndex)) : PolygonGroupImportedMaterialSlotName;
PolygonGroups.Add(PolygonGroupID);
MaterialIndexToPolygonGroup.Add(MaterialIndex, PolygonGroupID);
}
FVertexInstanceID TriangleVertexInstanceIDs[3];
for (int32 Corner = 0; Corner < 3; ++Corner)
{
int32 VerticeIndex = VerticeIndexBase + Corner;
FVertexID VertexID = RemapVertexPosition[SourceRawMesh.WedgeIndices[VerticeIndex]];
FVertexInstanceID VertexInstanceID = DestinationMeshDescription.CreateVertexInstance(VertexID);
TriangleVertexInstanceIDs[Corner] = VertexInstanceID;
VertexInstanceColors[VertexInstanceID] = bHasColors ? FVector4f(FLinearColor::FromSRGBColor(SourceRawMesh.WedgeColors[VerticeIndex])) : FVector4f(FLinearColor::White);
VertexInstanceNormals[VertexInstanceID] = bHasNormals ? SourceRawMesh.WedgeTangentZ[VerticeIndex] : FVector3f(ForceInitToZero);
if (bHasTangents)
{
VertexInstanceTangents[VertexInstanceID] = SourceRawMesh.WedgeTangentX[VerticeIndex];
VertexInstanceBinormalSigns[VertexInstanceID] = FMatrix44f(SourceRawMesh.WedgeTangentX[VerticeIndex].GetSafeNormal(),
SourceRawMesh.WedgeTangentY[VerticeIndex].GetSafeNormal(),
SourceRawMesh.WedgeTangentZ[VerticeIndex].GetSafeNormal(),
FVector3f::ZeroVector).Determinant() < 0 ? -1.0f : +1.0f;
}
else
{
VertexInstanceTangents[VertexInstanceID] = FVector3f(ForceInitToZero);
VertexInstanceBinormalSigns[VertexInstanceID] = 0.0f;
}
for (int32 TextureCoordinnateIndex = 0; TextureCoordinnateIndex < NumTexCoords; ++TextureCoordinnateIndex)
{
int32 TextureCoordIndex = TextureCoordinnateRemapIndex[TextureCoordinnateIndex];
if (TextureCoordIndex == INDEX_NONE)
{
continue;
}
VertexInstanceUVs.Set(VertexInstanceID, TextureCoordIndex, SourceRawMesh.WedgeTexCoords[TextureCoordinnateIndex][VerticeIndex]);
}
}
DestinationMeshDescription.CreateTriangle(PolygonGroupID, TriangleVertexInstanceIDs);
}
ConvertSmoothGroupToHardEdges(SourceRawMesh.FaceSmoothingMasks, DestinationMeshDescription);
//Create the missing normals and tangents, should we use Mikkt space for tangent???
if (!bSkipNormalsAndTangents && (!bHasNormals || !bHasTangents))
{
//DestinationMeshDescription.ComputePolygonTangentsAndNormals(0.0f);
ComputeTriangleTangentsAndNormals(DestinationMeshDescription, 0.0f, DebugName);
//Create the missing normals and recompute the tangents with MikkTSpace.
EComputeNTBsFlags ComputeNTBsOptions = EComputeNTBsFlags::Tangents | EComputeNTBsFlags::UseMikkTSpace | EComputeNTBsFlags::BlendOverlappingNormals;
ComputeTangentsAndNormals(DestinationMeshDescription, ComputeNTBsOptions);
}
DestinationMeshDescription.BuildIndexers();
}
void FStaticMeshOperations::AppendMeshDescriptions(const TArray<const FMeshDescription*>& SourceMeshes, FMeshDescription& TargetMesh, FAppendSettings& AppendSettings)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshOperations::AppendMeshDescriptions);
FStaticMeshAttributes TargetAttributes(TargetMesh);
TVertexAttributesRef<FVector3f> TargetVertexPositions = TargetAttributes.GetVertexPositions();
TEdgeAttributesRef<bool> TargetEdgeHardnesses = TargetAttributes.GetEdgeHardnesses();
TPolygonGroupAttributesRef<FName> TargetImportedMaterialSlotNames = TargetAttributes.GetPolygonGroupMaterialSlotNames();
TVertexInstanceAttributesRef<FVector3f> TargetVertexInstanceNormals = TargetAttributes.GetVertexInstanceNormals();
TVertexInstanceAttributesRef<FVector3f> TargetVertexInstanceTangents = TargetAttributes.GetVertexInstanceTangents();
TVertexInstanceAttributesRef<float> TargetVertexInstanceBinormalSigns = TargetAttributes.GetVertexInstanceBinormalSigns();
TVertexInstanceAttributesRef<FVector4f> TargetVertexInstanceColors = TargetAttributes.GetVertexInstanceColors();
TVertexInstanceAttributesRef<FVector2f> TargetVertexInstanceUVs = TargetAttributes.GetVertexInstanceUVs();
TargetMesh.SuspendVertexInstanceIndexing();
TargetMesh.SuspendEdgeIndexing();
TargetMesh.SuspendPolygonIndexing();
TargetMesh.SuspendPolygonGroupIndexing();
TargetMesh.SuspendUVIndexing();
int32 NumVertices = 0;
int32 NumVertexInstances = 0;
int32 NumEdges = 0;
int32 NumTriangles = 0;
int32 MaxNumVertexInstanceUVChannels = TargetVertexInstanceUVs.GetNumChannels();
int32 MaxNumUVChannels = TargetMesh.GetNumUVElementChannels();
int32 MaxNumPolygonGroups = 0;
int32 MaxNumMeshVertices = 0;
int32 MaxNumEdges = 0;
int32 MaxNumVertexInstances = 0;
for (const FMeshDescription* SourceMeshPtr : SourceMeshes)
{
const FMeshDescription& SourceMesh = *SourceMeshPtr;
NumVertices += SourceMesh.Vertices().Num();
NumVertexInstances += SourceMesh.VertexInstances().Num();
NumEdges += SourceMesh.Edges().Num();
NumTriangles += SourceMesh.Triangles().Num();
FStaticMeshConstAttributes SourceAttributes(SourceMesh);
TVertexInstanceAttributesConstRef<FVector2f> SourceVertexInstanceUVs = SourceAttributes.GetVertexInstanceUVs();
for (int32 ChannelIdx = MaxNumVertexInstanceUVChannels; ChannelIdx < SourceVertexInstanceUVs.GetNumChannels(); ++ChannelIdx)
{
if (AppendSettings.bMergeUVChannels[ChannelIdx])
{
MaxNumVertexInstanceUVChannels = ChannelIdx + 1;
}
}
for (int32 ChannelIdx = MaxNumUVChannels; ChannelIdx < SourceMesh.GetNumUVElementChannels(); ++ChannelIdx)
{
if (AppendSettings.bMergeUVChannels[ChannelIdx])
{
MaxNumUVChannels = ChannelIdx + 1;
}
}
MaxNumPolygonGroups = FMath::Max(MaxNumPolygonGroups, SourceMesh.PolygonGroups().Num());
MaxNumMeshVertices = FMath::Max(MaxNumMeshVertices, SourceMesh.Vertices().Num());
MaxNumEdges = FMath::Max(MaxNumEdges, SourceMesh.Edges().Num());
MaxNumVertexInstances = FMath::Max(MaxNumVertexInstances, SourceMesh.VertexInstances().Num());
}
//Copy into the target mesh
TargetMesh.ReserveNewVertices(NumVertices);
TargetMesh.ReserveNewVertexInstances(NumVertexInstances);
TargetMesh.ReserveNewEdges(NumEdges);
TargetMesh.ReserveNewTriangles(NumTriangles);
if (MaxNumVertexInstanceUVChannels > TargetVertexInstanceUVs.GetNumChannels())
{
TargetVertexInstanceUVs.SetNumChannels(MaxNumVertexInstanceUVChannels);
}
if (MaxNumUVChannels > TargetMesh.GetNumUVElementChannels())
{
TargetMesh.SetNumUVChannels(MaxNumUVChannels);
}
PolygonGroupMap RemapPolygonGroup;
TMap<FVertexID, FVertexID> SourceToTargetVertexID;
SourceToTargetVertexID.Reserve(MaxNumMeshVertices);
TMap<FVertexInstanceID, FVertexInstanceID> SourceToTargetVertexInstanceID;
SourceToTargetVertexInstanceID.Reserve(MaxNumVertexInstances);
for (const FMeshDescription* SourceMeshPtr : SourceMeshes)
{
const FMeshDescription& SourceMesh = *SourceMeshPtr;
RemapPolygonGroup.Empty(MaxNumPolygonGroups);
FStaticMeshConstAttributes SourceAttributes(SourceMesh);
TVertexAttributesConstRef<FVector3f> SourceVertexPositions = SourceAttributes.GetVertexPositions();
TEdgeAttributesConstRef<bool> SourceEdgeHardnesses = SourceAttributes.GetEdgeHardnesses();
TPolygonGroupAttributesConstRef<FName> SourceImportedMaterialSlotNames = SourceAttributes.GetPolygonGroupMaterialSlotNames();
TVertexInstanceAttributesConstRef<FVector3f> SourceVertexInstanceNormals = SourceAttributes.GetVertexInstanceNormals();
TVertexInstanceAttributesConstRef<FVector3f> SourceVertexInstanceTangents = SourceAttributes.GetVertexInstanceTangents();
TVertexInstanceAttributesConstRef<float> SourceVertexInstanceBinormalSigns = SourceAttributes.GetVertexInstanceBinormalSigns();
TVertexInstanceAttributesConstRef<FVector4f> SourceVertexInstanceColors = SourceAttributes.GetVertexInstanceColors();
TVertexInstanceAttributesConstRef<FVector2f> SourceVertexInstanceUVs = SourceAttributes.GetVertexInstanceUVs();
// Fill the UV arrays
const int32 NumUVChannel = FMath::Min(TargetMesh.GetNumUVElementChannels(), SourceMesh.GetNumUVElementChannels());
for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVChannel; UVLayerIndex++)
{
TUVAttributesConstRef<FVector2f> SourceUVCoordinates = SourceMesh.UVAttributes(UVLayerIndex).GetAttributesRef<FVector2f>(MeshAttribute::UV::UVCoordinate);
TUVAttributesRef<FVector2f> TargetUVCoordinates = TargetMesh.UVAttributes(UVLayerIndex).GetAttributesRef<FVector2f>(MeshAttribute::UV::UVCoordinate);
int32 UVCount = SourceUVCoordinates.GetNumElements();
TargetMesh.ReserveNewUVs(UVCount, UVLayerIndex);
for (FUVID SourceUVID : SourceMesh.UVs(UVLayerIndex).GetElementIDs())
{
FUVID TargetUVID = TargetMesh.CreateUV(UVLayerIndex);
TargetUVCoordinates[TargetUVID] = SourceUVCoordinates[SourceUVID];
}
}
//PolygonGroups
if (AppendSettings.PolygonGroupsDelegate.IsBound())
{
AppendSettings.PolygonGroupsDelegate.Execute(SourceMesh, TargetMesh, RemapPolygonGroup);
}
else
{
for (FPolygonGroupID SourcePolygonGroupID : SourceMesh.PolygonGroups().GetElementIDs())
{
FPolygonGroupID TargetMatchingID = INDEX_NONE;
for (FPolygonGroupID TargetPolygonGroupID : TargetMesh.PolygonGroups().GetElementIDs())
{
if (SourceImportedMaterialSlotNames[SourcePolygonGroupID] == TargetImportedMaterialSlotNames[TargetPolygonGroupID])
{
TargetMatchingID = TargetPolygonGroupID;
break;
}
}
if (TargetMatchingID == INDEX_NONE)
{
TargetMatchingID = TargetMesh.CreatePolygonGroup();
TargetImportedMaterialSlotNames[TargetMatchingID] = SourceImportedMaterialSlotNames[SourcePolygonGroupID];
}
RemapPolygonGroup.Add(SourcePolygonGroupID, TargetMatchingID);
}
}
FPolygonGroupID SinglePolygonGroup = TargetMesh.PolygonGroups().Num() == 1 ? TargetMesh.PolygonGroups().GetFirstValidID() : INDEX_NONE;
//Vertices
for (FVertexID SourceVertexID : SourceMesh.Vertices().GetElementIDs())
{
FVertexID TargetVertexID = TargetMesh.CreateVertex();
TargetVertexPositions[TargetVertexID] = (SourceVertexPositions[SourceVertexID] - FVector3f(AppendSettings.MergedAssetPivot)); //LWC_TODO: Precision loss
SourceToTargetVertexID.Add(SourceVertexID, TargetVertexID);
}
// Transform vertices properties
if (AppendSettings.MeshTransform)
{
const FTransform& Transform = AppendSettings.MeshTransform.GetValue();
for (const TPair<FVertexID, FVertexID>& VertexIDPair : SourceToTargetVertexID)
{
FVector3f& Position = TargetVertexPositions[VertexIDPair.Value];
Position = FVector3f(Transform.TransformPosition(FVector(Position))); //LWC_TODO: Precision loss
}
}
//Edges
for (const FEdgeID SourceEdgeID : SourceMesh.Edges().GetElementIDs())
{
const FVertexID EdgeVertex0 = SourceMesh.GetEdgeVertex(SourceEdgeID, 0);
const FVertexID EdgeVertex1 = SourceMesh.GetEdgeVertex(SourceEdgeID, 1);
FEdgeID TargetEdgeID = TargetMesh.CreateEdge(SourceToTargetVertexID[EdgeVertex0], SourceToTargetVertexID[EdgeVertex1]);
TargetEdgeHardnesses[TargetEdgeID] = SourceEdgeHardnesses[SourceEdgeID];
}
//VertexInstances
for (const FVertexInstanceID SourceVertexInstanceID : SourceMesh.VertexInstances().GetElementIDs())
{
const FVertexID SourceVertexID = SourceMesh.GetVertexInstanceVertex(SourceVertexInstanceID);
FVertexInstanceID TargetVertexInstanceID = TargetMesh.CreateVertexInstance(SourceToTargetVertexID[SourceVertexID]);
TargetVertexInstanceNormals[TargetVertexInstanceID] = SourceVertexInstanceNormals[SourceVertexInstanceID];
TargetVertexInstanceTangents[TargetVertexInstanceID] = SourceVertexInstanceTangents[SourceVertexInstanceID];
TargetVertexInstanceBinormalSigns[TargetVertexInstanceID] = SourceVertexInstanceBinormalSigns[SourceVertexInstanceID];
if (AppendSettings.bMergeVertexColor)
{
TargetVertexInstanceColors[TargetVertexInstanceID] = SourceVertexInstanceColors[SourceVertexInstanceID];
}
for (int32 UVChannelIndex = 0; UVChannelIndex < MaxNumVertexInstanceUVChannels && UVChannelIndex < SourceVertexInstanceUVs.GetNumChannels(); ++UVChannelIndex)
{
TargetVertexInstanceUVs.Set(TargetVertexInstanceID, UVChannelIndex, SourceVertexInstanceUVs.Get(SourceVertexInstanceID, UVChannelIndex));
}
SourceToTargetVertexInstanceID.Add(SourceVertexInstanceID, TargetVertexInstanceID);
}
bool bReverseCulling = false;
// Transform vertex instances properties
if (AppendSettings.MeshTransform)
{
const FTransform& Transform = AppendSettings.MeshTransform.GetValue();
FMatrix TransformInverseTransposeMatrix = Transform.ToMatrixWithScale().Inverse().GetTransposed();
TransformInverseTransposeMatrix.RemoveScaling();
bReverseCulling = Transform.GetDeterminant() < 0;
float BinormalSignsFactor = bReverseCulling ? -1.f : 1.f;
for (const TPair<FVertexInstanceID, FVertexInstanceID>& VertexInstanceIDPair : SourceToTargetVertexInstanceID)
{
FVertexInstanceID InstanceID = VertexInstanceIDPair.Value;
FVector3f& Normal = TargetVertexInstanceNormals[InstanceID];
Normal = (FVector3f)FVector(TransformInverseTransposeMatrix.TransformVector((FVector)Normal).GetSafeNormal());
FVector3f& Tangent = TargetVertexInstanceTangents[InstanceID];
Tangent = (FVector3f)FVector(TransformInverseTransposeMatrix.TransformVector((FVector)Tangent).GetSafeNormal());
TargetVertexInstanceBinormalSigns[InstanceID] *= BinormalSignsFactor;
}
}
// Triangles
for (const FTriangleID SourceTriangleID : SourceMesh.Triangles().GetElementIDs())
{
TArrayView<const FVertexInstanceID> TriangleVertexInstanceIDs = SourceMesh.GetTriangleVertexInstances(SourceTriangleID);
//Find the polygonGroupID
FPolygonGroupID TargetPolygonGroupID = SinglePolygonGroup != INDEX_NONE ? SinglePolygonGroup : RemapPolygonGroup[SourceMesh.GetTrianglePolygonGroup(SourceTriangleID)];
TArray<FVertexInstanceID, TInlineAllocator<3>> VertexInstanceIDs;
VertexInstanceIDs.Reserve(3);
if (bReverseCulling)
{
for (int32 ReverseVertexInstanceIdIndex = TriangleVertexInstanceIDs.Num()-1; ReverseVertexInstanceIdIndex >= 0; ReverseVertexInstanceIdIndex--)
{
VertexInstanceIDs.Add(SourceToTargetVertexInstanceID[TriangleVertexInstanceIDs[ReverseVertexInstanceIdIndex]]);
}
}
else
{
for (const FVertexInstanceID& VertexInstanceID : TriangleVertexInstanceIDs)
{
VertexInstanceIDs.Add(SourceToTargetVertexInstanceID[VertexInstanceID]);
}
}
// Insert a triangle into the mesh
TargetMesh.CreateTriangle(TargetPolygonGroupID, VertexInstanceIDs);
}
}
TargetMesh.ResumeVertexInstanceIndexing();
TargetMesh.ResumeEdgeIndexing();
TargetMesh.ResumePolygonIndexing();
TargetMesh.ResumePolygonGroupIndexing();
TargetMesh.ResumeUVIndexing();
}
void FStaticMeshOperations::AppendMeshDescription(const FMeshDescription& SourceMesh, FMeshDescription& TargetMesh, FAppendSettings& AppendSettings)
{
AppendMeshDescriptions({ &SourceMesh }, TargetMesh, AppendSettings);
}
//////////////////////////////////////////////////////////////////////////
// Normals tangents and Bi-normals
void FStaticMeshOperations::HasInvalidVertexInstanceNormalsOrTangents(const FMeshDescription& MeshDescription, bool& bHasInvalidNormals, bool& bHasInvalidTangents)
{
bHasInvalidNormals = false;
bHasInvalidTangents = false;
FStaticMeshConstAttributes Attributes(MeshDescription);
TArrayView<const FVector3f> VertexInstanceNormals = Attributes.GetVertexInstanceNormals().GetRawArray();
TArrayView<const FVector3f> VertexInstanceTangents = Attributes.GetVertexInstanceTangents().GetRawArray();
for (const FVertexInstanceID VertexInstanceID : MeshDescription.VertexInstances().GetElementIDs())
{
bHasInvalidNormals |= (VertexInstanceNormals[VertexInstanceID].IsNearlyZero() || VertexInstanceNormals[VertexInstanceID].ContainsNaN());
bHasInvalidTangents |= (VertexInstanceTangents[VertexInstanceID].IsNearlyZero() || VertexInstanceTangents[VertexInstanceID].ContainsNaN());
if (bHasInvalidNormals && bHasInvalidTangents)
{
break;
}
}
}
void ClearNormalsAndTangentsData(FMeshDescription& MeshDescription, bool bClearNormals, bool bClearTangents)
{
if (!bClearNormals && !bClearTangents)
{
return;
}
FStaticMeshAttributes Attributes(MeshDescription);
TArrayView<FVector3f> VertexInstanceNormals = Attributes.GetVertexInstanceNormals().GetRawArray();
TArrayView<FVector3f> VertexInstanceTangents = Attributes.GetVertexInstanceTangents().GetRawArray();
TArrayView<float> VertexInstanceBinormals = Attributes.GetVertexInstanceBinormalSigns().GetRawArray();
//Zero out all value that need to be recompute
for (const FVertexInstanceID VertexInstanceID : MeshDescription.VertexInstances().GetElementIDs())
{
if (bClearNormals)
{
VertexInstanceNormals[VertexInstanceID] = FVector3f::ZeroVector;
}
if (bClearTangents)
{
// Dump the tangents
VertexInstanceBinormals[VertexInstanceID] = 0.0f;
VertexInstanceTangents[VertexInstanceID] = FVector3f::ZeroVector;
}
}
}
struct FNTBGroupKeyFuncs : public TDefaultMapKeyFuncs<FVector2f, FVector3f, false>
{
//We need to sanitize the key here to make sure -0.0f fall on the same hash then 0.0f
static FORCEINLINE_DEBUGGABLE uint32 GetKeyHash(KeyInitType Key)
{
FVector2f TmpKey;
TmpKey.X = FMath::IsNearlyZero(Key.X) ? 0.0f : Key.X;
TmpKey.Y = FMath::IsNearlyZero(Key.Y) ? 0.0f : Key.Y;
return FCrc::MemCrc32(&TmpKey, sizeof(TmpKey));
}
};
void FStaticMeshOperations::RecomputeNormalsAndTangentsIfNeeded(FMeshDescription& MeshDescription, EComputeNTBsFlags ComputeNTBsOptions)
{
if (!EnumHasAllFlags(ComputeNTBsOptions, EComputeNTBsFlags::Normals | EComputeNTBsFlags::Tangents))
{
bool bRecomputeNormals = false;
bool bRecomputeTangents = false;
HasInvalidVertexInstanceNormalsOrTangents(MeshDescription, bRecomputeNormals, bRecomputeTangents);
ComputeNTBsOptions |= (bRecomputeNormals ? EComputeNTBsFlags::Normals : EComputeNTBsFlags::None);
ComputeNTBsOptions |= (bRecomputeTangents ? EComputeNTBsFlags::Tangents : EComputeNTBsFlags::None);
}
if (EnumHasAnyFlags(ComputeNTBsOptions, EComputeNTBsFlags::Normals | EComputeNTBsFlags::Tangents))
{
ComputeTangentsAndNormals(MeshDescription, ComputeNTBsOptions);
}
}
void FStaticMeshOperations::ComputeTangentsAndNormals(FMeshDescription& MeshDescription, EComputeNTBsFlags ComputeNTBsOptions)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshOperations::ComputeTangentsAndNormals);
//For each vertex compute the normals for every connected edges that are smooth between hard edges
// H A B
// \ || /
// G -- ** -- C
// // | \
// F E D
//
// The double ** are the vertex, the double line are hard edges, the single line are soft edge.
// A and F are hard, all other edges are soft. The goal is to compute two average normals one from
// A to F and a second from F to A. Then we can set the vertex instance normals accordingly.
// First normal(A to F) = Normalize(A+B+C+D+E+F)
// Second normal(F to A) = Normalize(F+G+H+A)
// We found the connected edge using the triangle that share edges
struct FTriangleCornerData
{
FVertexInstanceID VertexInstanceID;
float CornerAngle;
};
struct FTriangleData
{
public:
//The area of the triangle
float Area;
//Set the corner angle data for a FVertexInstanceID
void SetCornerAngleData(FVertexInstanceID VertexInstanceID, float CornerAngle, int32 CornerIndex)
{
CornerAngleDatas[CornerIndex].VertexInstanceID = VertexInstanceID;
CornerAngleDatas[CornerIndex].CornerAngle = CornerAngle;
}
//Get the angle for the FVertexInstanceID
float GetCornerAngle(FVertexInstanceID VertexInstanceID)
{
for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
{
if (CornerAngleDatas[CornerIndex].VertexInstanceID == VertexInstanceID)
{
return CornerAngleDatas[CornerIndex].CornerAngle;
}
}
//We should always found a valid VertexInstanceID
check(false);
return 0.0f;
}
private:
//The data for each corner
FTriangleCornerData CornerAngleDatas[3];
};
#if 0
//Make sure the meshdescription is triangulate
if (MeshDescription.Triangles().Num() < MeshDescription.Polygons().Num())
{
//Triangulate the mesh, we compute the normals on triangle not on polygon.
MeshDescription.TriangulateMesh();
}
#endif
if (MeshDescription.Triangles().Num() == 0)
{
return;
}
const bool bForceComputeNormals = EnumHasAllFlags(ComputeNTBsOptions, EComputeNTBsFlags::Normals);
const bool bForceComputeTangent = EnumHasAnyFlags(ComputeNTBsOptions, EComputeNTBsFlags::Normals | EComputeNTBsFlags::Tangents);
const bool bComputeTangentWithMikkTSpace = bForceComputeTangent && EnumHasAllFlags(ComputeNTBsOptions, EComputeNTBsFlags::UseMikkTSpace);
const bool bComputeWeightedNormals = EnumHasAllFlags(ComputeNTBsOptions, EComputeNTBsFlags::WeightedNTBs);
//Clear any data we want to force-recompute since the following code actually look for any invalid data and recompute it.
ClearNormalsAndTangentsData(MeshDescription, bForceComputeNormals, bForceComputeTangent);
// Going to iterate over all triangles, so mandate that the triangle elements are compact, i.e. there are no holes
const int32 NumTriangles = MeshDescription.Triangles().Num();
check(MeshDescription.Triangles().GetArraySize() == NumTriangles);
// Compute the weight (area and angle) for each triangles
TArray<FTriangleData> TriangleDatas;
TriangleDatas.SetNum(NumTriangles);
if (bComputeWeightedNormals)
{
FStaticMeshAttributes Attributes(MeshDescription);
TArrayView<const FVector3f> VertexPositions = Attributes.GetVertexPositions().GetRawArray();
TArrayView<const FVertexID> TriVertexIDs = Attributes.GetTriangleVertexIndices().GetRawArray();
TArrayView<const FVertexInstanceID> TriVertexInstanceIDs = Attributes.GetTriangleVertexInstanceIndices().GetRawArray();
TriangleDatas.Reserve(NumTriangles);
for (int32 Index = 0, TriIndex = 0; Index < NumTriangles; Index++, TriIndex += 3)
{
const FVector3f PointA(VertexPositions[TriVertexIDs[TriIndex + 0]]);
const FVector3f PointB(VertexPositions[TriVertexIDs[TriIndex + 1]]);
const FVector3f PointC(VertexPositions[TriVertexIDs[TriIndex + 2]]);
FTriangleData& TriangleData = TriangleDatas[Index];
TriangleData.Area = TriangleUtilities::ComputeTriangleArea(PointA, PointB, PointC);
TriangleData.SetCornerAngleData(TriVertexInstanceIDs[TriIndex + 0], TriangleUtilities::ComputeTriangleCornerAngle(PointA, PointB, PointC), 0);
TriangleData.SetCornerAngleData(TriVertexInstanceIDs[TriIndex + 1], TriangleUtilities::ComputeTriangleCornerAngle(PointB, PointC, PointA), 1);
TriangleData.SetCornerAngleData(TriVertexInstanceIDs[TriIndex + 2], TriangleUtilities::ComputeTriangleCornerAngle(PointC, PointA, PointB), 2);
}
}
// Ensure certain indexers are built in anticipation
MeshDescription.BuildVertexIndexers();
MeshDescription.BuildEdgeIndexers();
// Going to iterate over all vertices, so mandate that the vertex elements are compact, i.e. there are no holes
const int32 NumVertices = MeshDescription.Vertices().Num();
check(MeshDescription.Vertices().GetArraySize() == NumVertices);
// Split work in batch to reduce call and allocation overhead
const int32 BatchSize = 128 * 1024;
const int32 BatchCount = (NumVertices + BatchSize - 1) / BatchSize;
//Iterate all vertex to compute normals for all vertex instance
ParallelFor( TEXT("ComputeTangentsAndNormals.PF"), BatchCount,1,
[NumVertices, BatchSize, bComputeTangentWithMikkTSpace, bComputeWeightedNormals, &MeshDescription, &TriangleDatas](int32 BatchIndex)
{
FStaticMeshAttributes Attributes(MeshDescription);
TArrayView<const FVector2f> VertexUVs;
TArrayView<const FVector3f> TriangleNormals = Attributes.GetTriangleNormals().GetRawArray();
TArrayView<const FVector3f> TriangleTangents = Attributes.GetTriangleTangents().GetRawArray();
TArrayView<const FVector3f> TriangleBinormals = Attributes.GetTriangleBinormals().GetRawArray();
TArrayView<const bool> EdgeHardnesses = Attributes.GetEdgeHardnesses().GetRawArray();
TArrayView<FVector3f> VertexNormals = Attributes.GetVertexInstanceNormals().GetRawArray();
TArrayView<FVector3f> VertexTangents = Attributes.GetVertexInstanceTangents().GetRawArray();
TArrayView<float> VertexBinormalSigns = Attributes.GetVertexInstanceBinormalSigns().GetRawArray();
// If the mesh has no UVs, average all tangents/bi-normals for a given vertex, rather than try to maintain
// the UV flow.
if (Attributes.GetVertexInstanceUVs().GetNumChannels() > 0)
{
// Use UV0 as the base. Same as with ComputeTriangleTangentsAndNormals
VertexUVs = Attributes.GetVertexInstanceUVs().GetRawArray(0);
}
check(TriangleNormals.Num() > 0);
check(TriangleTangents.Num() > 0);
check(TriangleBinormals.Num() > 0);
//Reuse containers between iterations to reduce allocations
TMap<FVector2f, FVector3f, FDefaultSetAllocator, FNTBGroupKeyFuncs> GroupTangent;
TMap<FVector2f, FVector3f, FDefaultSetAllocator, FNTBGroupKeyFuncs> GroupBiNormal;
TMap<FTriangleID, FVertexInfo> VertexInfoMap;
TArray<TArray<FTriangleID, TInlineAllocator<8>>> Groups;
TSet<FTriangleID> ConsumedTriangle;
TArray<FTriangleID> PolygonQueue;
TArray<FVertexInstanceID> VertexInstanceInGroup;
VertexInfoMap.Reserve(20);
int32 StartIndex = BatchIndex * BatchSize;
int32 LastIndex = FMath::Min(StartIndex + BatchSize, NumVertices);
for (int32 Index = StartIndex; Index < LastIndex; ++Index)
{
VertexInfoMap.Reset();
const FVertexID VertexID(Index);
bool bPointHasAllTangents = true;
// Fill the VertexInfoMap
for (const FEdgeID& EdgeID : MeshDescription.GetVertexConnectedEdgeIDs(VertexID))
{
for (const FTriangleID& TriangleID : MeshDescription.GetEdgeConnectedTriangleIDs(EdgeID))
{
FVertexInfo& VertexInfo = VertexInfoMap.FindOrAdd(TriangleID);
int32 EdgeIndex = VertexInfo.EdgeIDs.AddUnique(EdgeID);
if (VertexInfo.TriangleID == INDEX_NONE)
{
VertexInfo.TriangleID = TriangleID;
for (const FVertexInstanceID& VertexInstanceID : MeshDescription.GetTriangleVertexInstances(TriangleID))
{
if (MeshDescription.GetVertexInstanceVertex(VertexInstanceID) == VertexID)
{
VertexInfo.VertexInstanceID = VertexInstanceID;
if (!VertexUVs.IsEmpty())
{
VertexInfo.UVs = VertexUVs[VertexInstanceID]; // UV0
}
bPointHasAllTangents &= !VertexNormals[VertexInstanceID].IsNearlyZero() && !VertexTangents[VertexInstanceID].IsNearlyZero();
if (bPointHasAllTangents)
{
FVector3f TangentX = VertexTangents[VertexInstanceID].GetSafeNormal();
FVector3f TangentZ = VertexNormals[VertexInstanceID].GetSafeNormal();
FVector3f TangentY = (FVector3f::CrossProduct(TangentZ, TangentX).GetSafeNormal() * VertexBinormalSigns[VertexInstanceID]).GetSafeNormal();
if (TangentX.ContainsNaN() || TangentX.IsNearlyZero(SMALL_NUMBER) ||
TangentY.ContainsNaN() || TangentY.IsNearlyZero(SMALL_NUMBER) ||
TangentZ.ContainsNaN() || TangentZ.IsNearlyZero(SMALL_NUMBER))
{
bPointHasAllTangents = false;
}
}
break;
}
}
}
}
}
if (bPointHasAllTangents)
{
continue;
}
//Build all group by recursively traverse all polygon connected to the vertex
Groups.Reset();
ConsumedTriangle.Reset();
TSet<FEdgeID> ConsumedEdges;
for (auto Kvp : VertexInfoMap)
{
if (ConsumedTriangle.Contains(Kvp.Key))
{
continue;
}
int32 CurrentGroupIndex = Groups.AddZeroed();
TArray<FTriangleID, TInlineAllocator<8>>& CurrentGroup = Groups[CurrentGroupIndex];
PolygonQueue.Reset();
PolygonQueue.Add(Kvp.Key); //Use a queue to avoid recursive function
while (PolygonQueue.Num() > 0)
{
FTriangleID CurrentPolygonID = PolygonQueue.Pop(EAllowShrinking::No);
FVertexInfo& CurrentVertexInfo = VertexInfoMap.FindOrAdd(CurrentPolygonID);
CurrentGroup.AddUnique(CurrentVertexInfo.TriangleID);
ConsumedTriangle.Add(CurrentVertexInfo.TriangleID);
for (const FEdgeID& EdgeID : CurrentVertexInfo.EdgeIDs)
{
if (EdgeHardnesses[EdgeID] || ConsumedEdges.Contains(EdgeID))
{
//End of the group or non manifold edge
continue;
}
ConsumedEdges.Add(EdgeID);
for (const FTriangleID& TriangleID : MeshDescription.GetEdgeConnectedTriangleIDs(EdgeID))
{
if (TriangleID == CurrentVertexInfo.TriangleID)
{
continue;
}
//Add this polygon to the group
FVertexInfo& OtherVertexInfo = VertexInfoMap.FindOrAdd(TriangleID);
//Do not repeat polygons
if (!ConsumedTriangle.Contains(OtherVertexInfo.TriangleID))
{
PolygonQueue.Add(TriangleID);
}
}
}
}
}
for (const TArray<FTriangleID, TInlineAllocator<8>>& Group : Groups)
{
//Compute tangents data
GroupTangent.Reset();
GroupBiNormal.Reset();
VertexInstanceInGroup.Reset();
FVector3f GroupNormal(FVector3f::ZeroVector);
for (const FTriangleID& TriangleID : Group)
{
FVertexInfo& CurrentVertexInfo = VertexInfoMap.FindOrAdd(TriangleID);
float CornerWeight = 1.0f;
if (bComputeWeightedNormals)
{
FTriangleData& TriangleData = TriangleDatas[TriangleID];
CornerWeight = TriangleData.Area * TriangleData.GetCornerAngle(CurrentVertexInfo.VertexInstanceID);
}
const FVector3f TriNormal = CornerWeight * TriangleNormals[TriangleID];
const FVector3f TriTangent = CornerWeight * TriangleTangents[TriangleID];
const FVector3f TriBinormal = CornerWeight * TriangleBinormals[TriangleID];
VertexInstanceInGroup.Add(VertexInfoMap[TriangleID].VertexInstanceID);
if (!TriNormal.IsNearlyZero(SMALL_NUMBER) && !TriNormal.ContainsNaN())
{
GroupNormal += TriNormal;
}
if (!bComputeTangentWithMikkTSpace)
{
const FVector2f& UVs = VertexInfoMap[TriangleID].UVs;
bool CreateGroup = (!GroupTangent.Contains(UVs));
FVector3f& GroupTangentValue = GroupTangent.FindOrAdd(UVs);
FVector3f& GroupBiNormalValue = GroupBiNormal.FindOrAdd(UVs);
if (CreateGroup)
{
GroupTangentValue = FVector3f(0.0f);
GroupBiNormalValue = FVector3f(0.0f);
}
if (!TriTangent.IsNearlyZero(SMALL_NUMBER) && !TriTangent.ContainsNaN())
{
GroupTangentValue += TriTangent;
}
if (!TriBinormal.IsNearlyZero(SMALL_NUMBER) && !TriBinormal.ContainsNaN())
{
GroupBiNormalValue += TriBinormal;
}
}
}
//////////////////////////////////////////////////////////////////////////
//Apply the group to the Mesh
GroupNormal.Normalize();
if (!bComputeTangentWithMikkTSpace)
{
for (auto& Kvp : GroupTangent)
{
Kvp.Value.Normalize();
}
for (auto& Kvp : GroupBiNormal)
{
Kvp.Value.Normalize();
}
}
//Apply the average NTB on all Vertex instance
for (const FVertexInstanceID& VertexInstanceID : VertexInstanceInGroup)
{
const FVector2f& VertexUV = !VertexUVs.IsEmpty() ? VertexUVs[VertexInstanceID] : FVector2f::ZeroVector;
if (VertexNormals[VertexInstanceID].IsNearlyZero(SMALL_NUMBER))
{
VertexNormals[VertexInstanceID] = GroupNormal;
}
//If we are not computing the tangent with MikkTSpace, make sure the tangents are valid.
if (!bComputeTangentWithMikkTSpace)
{
//Avoid changing the original group value
FVector3f GroupTangentValue = GroupTangent[VertexUV];
FVector3f GroupBiNormalValue = GroupBiNormal[VertexUV];
if (!VertexTangents[VertexInstanceID].IsNearlyZero(SMALL_NUMBER))
{
GroupTangentValue = VertexTangents[VertexInstanceID];
}
FVector3f BiNormal(0.0f);
const FVector3f& VertexNormal(VertexNormals[VertexInstanceID]);
if (!VertexNormal.IsNearlyZero(SMALL_NUMBER) && !VertexTangents[VertexInstanceID].IsNearlyZero(SMALL_NUMBER))
{
BiNormal = FVector3f::CrossProduct(VertexNormal, VertexTangents[VertexInstanceID]).GetSafeNormal() * VertexBinormalSigns[VertexInstanceID];
}
if (!BiNormal.IsNearlyZero(SMALL_NUMBER))
{
GroupBiNormalValue = BiNormal;
}
// Gram-Schmidt orthogonalization
GroupBiNormalValue -= GroupTangentValue * (GroupTangentValue | GroupBiNormalValue);
GroupBiNormalValue.Normalize();
GroupTangentValue -= VertexNormal * (VertexNormal | GroupTangentValue);
GroupTangentValue.Normalize();
GroupBiNormalValue -= VertexNormal * (VertexNormal | GroupBiNormalValue);
GroupBiNormalValue.Normalize();
//Set the value
VertexTangents[VertexInstanceID] = GroupTangentValue;
//If the BiNormal is zero set the sign to 1.0f, inlining GetBasisDeterminantSign() to avoid depending on RenderCore.
VertexBinormalSigns[VertexInstanceID] = FMatrix44f(GroupTangentValue, GroupBiNormalValue, VertexNormal, FVector3f::ZeroVector).Determinant() < 0 ? -1.0f : +1.0f;
}
}
}
}
}
);
if (bForceComputeTangent && bComputeTangentWithMikkTSpace)
{
ComputeMikktTangents(MeshDescription, EnumHasAnyFlags(ComputeNTBsOptions, EComputeNTBsFlags::IgnoreDegenerateTriangles));
}
}
#if WITH_MIKKTSPACE
namespace MeshDescriptionMikktSpaceInterface
{
struct FMeshDescriptionCachedData
{
int32 NumTriangles;
TArrayView<const FVertexID> TriangleVertexIDs;
TArrayView<const FVertexInstanceID> TriangleVertexInstanceIDs;
TArrayView<const FVector3f> VertexPositions;
TArrayView<const FVector3f> VertexInstanceNormals;
TArrayView<const FVector2f> VertexInstanceUVs;
TArrayView<FVector3f> VertexInstanceTangents;
TArrayView<float> VertexInstanceBinormalSigns;
};
int MikkGetNumFaces(const SMikkTSpaceContext* Context)
{
FMeshDescriptionCachedData* UserData = (FMeshDescriptionCachedData*)(Context->m_pUserData);
return UserData->NumTriangles;
}
int MikkGetNumVertsOfFace(const SMikkTSpaceContext* Context, const int FaceIdx)
{
FMeshDescriptionCachedData* UserData = (FMeshDescriptionCachedData*)(Context->m_pUserData);
return 3;
}
void MikkGetPosition(const SMikkTSpaceContext* Context, float Position[3], const int FaceIdx, const int VertIdx)
{
FMeshDescriptionCachedData* UserData = (FMeshDescriptionCachedData*)(Context->m_pUserData);
const FVector3f& VertexPosition = UserData->VertexPositions[UserData->TriangleVertexIDs[FaceIdx * 3 + VertIdx]];
Position[0] = VertexPosition.X;
Position[1] = VertexPosition.Y;
Position[2] = VertexPosition.Z;
}
void MikkGetNormal(const SMikkTSpaceContext* Context, float Normal[3], const int FaceIdx, const int VertIdx)
{
FMeshDescriptionCachedData* UserData = (FMeshDescriptionCachedData*)(Context->m_pUserData);
const FVector3f& VertexNormal = UserData->VertexInstanceNormals[UserData->TriangleVertexInstanceIDs[FaceIdx * 3 + VertIdx]];
Normal[0] = VertexNormal.X;
Normal[1] = VertexNormal.Y;
Normal[2] = VertexNormal.Z;
}
void MikkSetTSpaceBasic(const SMikkTSpaceContext* Context, const float Tangent[3], const float BitangentSign, const int FaceIdx, const int VertIdx)
{
FMeshDescriptionCachedData* UserData = (FMeshDescriptionCachedData*)(Context->m_pUserData);
const FVertexInstanceID VertexInstanceID = UserData->TriangleVertexInstanceIDs[FaceIdx * 3 + VertIdx];
UserData->VertexInstanceTangents[VertexInstanceID] = FVector3f(Tangent[0], Tangent[1], Tangent[2]);
UserData->VertexInstanceBinormalSigns[VertexInstanceID] = -BitangentSign;
}
void MikkGetTexCoord(const SMikkTSpaceContext* Context, float UV[2], const int FaceIdx, const int VertIdx)
{
FMeshDescriptionCachedData* UserData = (FMeshDescriptionCachedData*)(Context->m_pUserData);
const FVector2f& TexCoord = UserData->VertexInstanceUVs[UserData->TriangleVertexInstanceIDs[FaceIdx * 3 + VertIdx]];
UV[0] = TexCoord.X;
UV[1] = TexCoord.Y;
}
}
#endif //#WITH_MIKKTSPACE
void FStaticMeshOperations::ComputeMikktTangents(FMeshDescription& MeshDescription, bool bIgnoreDegenerateTriangles)
{
#if WITH_MIKKTSPACE
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshOperations::ComputeMikktTangents);
// The Mikkt interface does not handle properly polygon array with 'holes'
// Compact mesh description if this is the case
if (MeshDescription.NeedsCompact())
{
FElementIDRemappings Remappings;
MeshDescription.Compact(Remappings);
}
int32 NumTriangles = MeshDescription.Triangles().Num();
if (NumTriangles == 0)
{
return; // nothing to compute
}
// we can use mikktspace to calculate the tangents
SMikkTSpaceInterface MikkTInterface;
MikkTInterface.m_getNormal = MeshDescriptionMikktSpaceInterface::MikkGetNormal;
MikkTInterface.m_getNumFaces = MeshDescriptionMikktSpaceInterface::MikkGetNumFaces;
MikkTInterface.m_getNumVerticesOfFace = MeshDescriptionMikktSpaceInterface::MikkGetNumVertsOfFace;
MikkTInterface.m_getPosition = MeshDescriptionMikktSpaceInterface::MikkGetPosition;
MikkTInterface.m_getTexCoord = MeshDescriptionMikktSpaceInterface::MikkGetTexCoord;
MikkTInterface.m_setTSpaceBasic = MeshDescriptionMikktSpaceInterface::MikkSetTSpaceBasic;
MikkTInterface.m_setTSpace = nullptr;
MeshDescriptionMikktSpaceInterface::FMeshDescriptionCachedData UserData;
UserData.NumTriangles = MeshDescription.Triangles().Num();
FStaticMeshAttributes Attributes(MeshDescription);
UserData.TriangleVertexIDs = Attributes.GetTriangleVertexIndices().GetRawArray();
UserData.TriangleVertexInstanceIDs = Attributes.GetTriangleVertexInstanceIndices().GetRawArray();
UserData.VertexPositions = Attributes.GetVertexPositions().GetRawArray();
UserData.VertexInstanceUVs = Attributes.GetVertexInstanceUVs().GetRawArray(0);
UserData.VertexInstanceNormals = Attributes.GetVertexInstanceNormals().GetRawArray();
UserData.VertexInstanceTangents = Attributes.GetVertexInstanceTangents().GetRawArray();
UserData.VertexInstanceBinormalSigns = Attributes.GetVertexInstanceBinormalSigns().GetRawArray();
SMikkTSpaceContext MikkTContext;
MikkTContext.m_pInterface = &MikkTInterface;
MikkTContext.m_pUserData = (void*)(&UserData);
MikkTContext.m_bIgnoreDegenerates = bIgnoreDegenerateTriangles;
genTangSpaceDefault(&MikkTContext);
#else
ensureMsgf(false, TEXT("MikkTSpace tangent generation is not supported on this platform."));
#endif //WITH_MIKKTSPACE
}
void FStaticMeshOperations::FindOverlappingCorners(FOverlappingCorners& OutOverlappingCorners, const FMeshDescription& MeshDescription, float ComparisonThreshold)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshOperations::FindOverlappingCorners);
// @todo: this should be shared with FOverlappingCorners
const FVertexInstanceArray& VertexInstanceArray = MeshDescription.VertexInstances();
const FVertexArray& VertexArray = MeshDescription.Vertices();
int32 NumWedges = 3 * MeshDescription.Triangles().Num();
// Empty the old data and reserve space for new
OutOverlappingCorners.Init(NumWedges);
// Create a list of vertex Z/index pairs
TArray<MeshDescriptionOperationNamespace::FIndexAndZ> VertIndexAndZ;
VertIndexAndZ.Reserve(NumWedges);
TVertexAttributesConstRef<FVector3f> VertexPositions = MeshDescription.GetVertexPositions();
int32 WedgeIndex = 0;
for (const FPolygonID PolygonID : MeshDescription.Polygons().GetElementIDs())
{
TArrayView<const FTriangleID> TriangleIDs = MeshDescription.GetPolygonTriangles(PolygonID);
for (const FTriangleID& TriangleID : TriangleIDs)
{
for (int32 Corner = 0; Corner < 3; ++Corner)
{
const FVertexInstanceID VertexInstanceID = MeshDescription.GetTriangleVertexInstance(TriangleID, Corner);
VertIndexAndZ.Emplace(WedgeIndex, VertexPositions[MeshDescription.GetVertexInstanceVertex(VertexInstanceID)]);
++WedgeIndex;
}
}
}
// Sort the vertices by z value
VertIndexAndZ.Sort(MeshDescriptionOperationNamespace::FCompareIndexAndZ());
// Search for duplicates, quickly!
for (int32 i = 0; i < VertIndexAndZ.Num(); i++)
{
// only need to search forward, since we add pairs both ways
for (int32 j = i + 1; j < VertIndexAndZ.Num(); j++)
{
if (FMath::Abs(VertIndexAndZ[j].Z - VertIndexAndZ[i].Z) > ComparisonThreshold)
break; // can't be any more dups
const FVector3f& PositionA = *(VertIndexAndZ[i].OriginalVector);
const FVector3f& PositionB = *(VertIndexAndZ[j].OriginalVector);
if (PositionA.Equals(PositionB, ComparisonThreshold))
{
OutOverlappingCorners.Add(VertIndexAndZ[i].Index, VertIndexAndZ[j].Index);
OutOverlappingCorners.Add(VertIndexAndZ[j].Index, VertIndexAndZ[i].Index);
}
}
}
OutOverlappingCorners.FinishAdding();
}
struct FLayoutUVMeshDescriptionView final : FLayoutUV::IMeshView
{
FMeshDescription& MeshDescription;
TVertexAttributesConstRef<FVector3f> Positions;
TVertexInstanceAttributesConstRef<FVector3f> Normals;
TVertexInstanceAttributesRef<FVector2f> TexCoords;
const uint32 SrcChannel;
const uint32 DstChannel;
uint32 NumIndices = 0;
TArray<int32> RemapVerts;
TArray<FVector2f> FlattenedTexCoords;
FLayoutUVMeshDescriptionView(FMeshDescription& InMeshDescription, uint32 InSrcChannel, uint32 InDstChannel)
: MeshDescription(InMeshDescription)
, SrcChannel(InSrcChannel)
, DstChannel(InDstChannel)
{
FStaticMeshAttributes Attributes(InMeshDescription);
Positions = Attributes.GetVertexPositions();
Normals = Attributes.GetVertexInstanceNormals();
TexCoords = Attributes.GetVertexInstanceUVs();
uint32 NumTris = MeshDescription.Triangles().Num();
NumIndices = NumTris * 3;
FlattenedTexCoords.SetNumUninitialized(NumIndices);
RemapVerts.SetNumUninitialized(NumIndices);
int32 WedgeIndex = 0;
for (const FPolygonID PolygonID : MeshDescription.Polygons().GetElementIDs())
{
TArrayView<const FTriangleID> TriangleIDs = MeshDescription.GetPolygonTriangles(PolygonID);
for (const FTriangleID& TriangleID : TriangleIDs)
{
for (int32 Corner = 0; Corner < 3; ++Corner)
{
const FVertexInstanceID VertexInstanceID = MeshDescription.GetTriangleVertexInstance(TriangleID, Corner);
FlattenedTexCoords[WedgeIndex] = TexCoords.Get(VertexInstanceID, SrcChannel);
RemapVerts[WedgeIndex] = VertexInstanceID.GetValue();
++WedgeIndex;
}
}
}
}
uint32 GetNumIndices() const override { return NumIndices; }
FVector3f GetPosition(uint32 Index) const override
{
FVertexInstanceID VertexInstanceID(RemapVerts[Index]);
FVertexID VertexID = MeshDescription.GetVertexInstanceVertex(VertexInstanceID);
return Positions[VertexID];
}
FVector3f GetNormal(uint32 Index) const override
{
FVertexInstanceID VertexInstanceID(RemapVerts[Index]);
return Normals[VertexInstanceID];
}
FVector2f GetInputTexcoord(uint32 Index) const override
{
return FlattenedTexCoords[Index];
}
void InitOutputTexcoords(uint32 Num) override
{
// If current DstChannel is out of range of the number of UVs defined by the mesh description, change the index count accordingly
const uint32 NumUVs = TexCoords.GetNumChannels();
if (DstChannel >= NumUVs)
{
TexCoords.SetNumChannels(DstChannel + 1);
ensure(false); // not expecting it to get here
}
}
void SetOutputTexcoord(uint32 Index, const FVector2f& Value) override
{
const FVertexInstanceID VertexInstanceID(RemapVerts[Index]);
TexCoords.Set(VertexInstanceID, DstChannel, Value);
}
};
int32 FStaticMeshOperations::GetUVChartCount(FMeshDescription& MeshDescription, int32 SrcLightmapIndex, ELightmapUVVersion LightmapUVVersion, const FOverlappingCorners& OverlappingCorners)
{
uint32 UnusedDstIndex = -1;
FLayoutUVMeshDescriptionView MeshDescriptionView(MeshDescription, SrcLightmapIndex, UnusedDstIndex);
FLayoutUV Packer(MeshDescriptionView);
Packer.SetVersion(LightmapUVVersion);
return Packer.FindCharts(OverlappingCorners);
}
bool FStaticMeshOperations::CreateLightMapUVLayout(FMeshDescription& MeshDescription,
int32 SrcLightmapIndex,
int32 DstLightmapIndex,
int32 MinLightmapResolution,
ELightmapUVVersion LightmapUVVersion,
const FOverlappingCorners& OverlappingCorners)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshOperations::CreateLightMapUVLayout)
FLayoutUVMeshDescriptionView MeshDescriptionView(MeshDescription, SrcLightmapIndex, DstLightmapIndex);
FLayoutUV Packer(MeshDescriptionView);
Packer.SetVersion(LightmapUVVersion);
if (LightmapUVVersion >= ELightmapUVVersion::ForceLightmapPadding)
{
MinLightmapResolution -= 2;
}
Packer.FindCharts(OverlappingCorners);
bool bPackSuccess = Packer.FindBestPacking(MinLightmapResolution);
if (bPackSuccess)
{
Packer.CommitPackedUVs();
}
return bPackSuccess;
}
static bool GatherUniqueTriangles(const FMeshDescription& InMeshDescription, bool bMergeIdenticalMaterials, TArray<FTriangleID>& OutRemappedTriangles, TArray<FVertexInstanceID>* OutUniqueVerts, TArray<FTriangleID>* OutDuplicateTriangles)
{
FStaticMeshConstAttributes Attributes(InMeshDescription);
TVertexInstanceAttributesConstRef<FVector2f> TexCoords = Attributes.GetVertexInstanceUVs();
TVertexInstanceAttributesConstRef<FVector4f> VertexColors = Attributes.GetVertexInstanceColors();
int32 NumVertexInstances = InMeshDescription.VertexInstances().Num();
int32 NumTriangles = InMeshDescription.Triangles().Num();
OutRemappedTriangles.Reserve(NumTriangles);
TMap<uint32, FTriangleID> UniqueTriangles;
if (bMergeIdenticalMaterials)
{
UniqueTriangles.Reserve(NumTriangles);
}
if (OutUniqueVerts)
{
OutUniqueVerts->Reserve(NumVertexInstances);
}
if (OutDuplicateTriangles)
{
OutDuplicateTriangles->Reserve(NumTriangles);
}
// Compute an hash value per triangle, based on its UVs & vertices colors
auto HashAttribute = [](FVertexInstanceID InVertexInstanceID, auto InAttributeArrayRef, int32& TriangleHash)
{
for (int32 Channel = 0; Channel < InAttributeArrayRef.GetNumChannels(); ++Channel)
{
for (const auto& Element : InAttributeArrayRef.GetArrayView(InVertexInstanceID, Channel))
{
TriangleHash = HashCombine(TriangleHash, GetTypeHash(Element));
}
}
};
for (const FTriangleID TriangleID : InMeshDescription.Triangles().GetElementIDs())
{
const FPolygonGroupID RefPolygonGroupID = InMeshDescription.GetTrianglePolygonGroup(TriangleID);
TConstArrayView<const FVertexInstanceID> VertexInstancesIDs = InMeshDescription.GetTriangleVertexInstances(TriangleID);
TConstArrayView<const FVertexID> VertexIDs = InMeshDescription.GetTriangleVertices(TriangleID);
FTriangleID RemapTriangleID = TriangleID;
bool bUnique = true;
if (bMergeIdenticalMaterials)
{
int32 TriangleHash = GetTypeHash(RefPolygonGroupID);
for (const FVertexInstanceID& VertexInstanceID : VertexInstancesIDs)
{
// Compute hash based on UVs & vertices colors
HashAttribute(VertexInstanceID, TexCoords, TriangleHash);
HashAttribute(VertexInstanceID, VertexColors, TriangleHash);
}
FTriangleID* UniqueTriangleIDPtr = UniqueTriangles.Find(TriangleHash);
if (UniqueTriangleIDPtr != nullptr)
{
RemapTriangleID = *UniqueTriangleIDPtr;
bUnique = false;
if (OutDuplicateTriangles)
{
OutDuplicateTriangles->Add(TriangleID);
}
}
else
{
UniqueTriangles.Add(TriangleHash, TriangleID);
}
}
if (bUnique && OutUniqueVerts)
{
OutUniqueVerts->Append(VertexInstancesIDs);
}
OutRemappedTriangles.Add(RemapTriangleID);
}
const bool bPerformedRemapping = bMergeIdenticalMaterials && UniqueTriangles.Num() != OutRemappedTriangles.Num();
return bPerformedRemapping;
}
template <typename TSrcUVs, typename TDstUVs>
static void CopyRemappedUVs(const FMeshDescription& InMeshDescription, const TArray<FTriangleID>& RemappedTriangles, const FElementIDRemappings* SrcElementIDRemappings, bool bCopyOnlyRemappedTrianglesUVs, const TSrcUVs& SrcUVs, TDstUVs& DstUVs)
{
int32 RemappedTrianglesIdx = 0;
for (const FTriangleID TriangleID : InMeshDescription.Triangles().GetElementIDs())
{
FTriangleID RemappedTriangleID = RemappedTriangles[RemappedTrianglesIdx];
if (!bCopyOnlyRemappedTrianglesUVs || RemappedTriangleID != TriangleID)
{
if (SrcElementIDRemappings)
{
RemappedTriangleID = SrcElementIDRemappings->GetRemappedTriangleID(RemappedTriangleID);
}
TConstArrayView<const FVertexInstanceID> SrcVertexInstancesIDs = InMeshDescription.GetTriangleVertexInstances(RemappedTriangleID);
TConstArrayView<const FVertexInstanceID> DstVertexInstancesIDs = InMeshDescription.GetTriangleVertexInstances(TriangleID);
for (int32 i = 0; i < 3; i++)
{
DstUVs[DstVertexInstancesIDs[i]] = FVector2D(SrcUVs[SrcVertexInstancesIDs[i]]);
}
}
RemappedTrianglesIdx++;
}
}
// Mesh view that will expose only unique UVs if bMergeIdenticalMaterials is provided
struct FUniqueUVMeshDescriptionView final : FLayoutUV::IMeshView
{
const FMeshDescription& MeshDescription;
TVertexAttributesConstRef<FVector3f> Positions;
TVertexInstanceAttributesConstRef<FVector3f> Normals;
TVertexInstanceAttributesConstRef<FVector2f> TexCoords;
TArray<FTriangleID> RemapTriangles;
TArray<FVertexInstanceID> UniqueVerts;
TArray<FVector2D>& OutputTexCoords;
bool bMustRemap;
FUniqueUVMeshDescriptionView(const FMeshDescription& InMeshDescription, bool bMergeIdenticalMaterials, TArray<FVector2D>& InOutTexCoords)
: MeshDescription(InMeshDescription)
, OutputTexCoords(InOutTexCoords)
{
FStaticMeshConstAttributes Attributes(MeshDescription);
Positions = Attributes.GetVertexPositions();
Normals = Attributes.GetVertexInstanceNormals();
TexCoords = Attributes.GetVertexInstanceUVs();
OutputTexCoords.SetNumZeroed(MeshDescription.VertexInstances().Num());
bMustRemap = GatherUniqueTriangles(MeshDescription, bMergeIdenticalMaterials, RemapTriangles, &UniqueVerts, nullptr);
}
uint32 GetNumIndices() const override
{
return UniqueVerts.Num();
}
FVector3f GetPosition(uint32 Index) const override
{
FVertexID VertexID = MeshDescription.GetVertexInstanceVertex(UniqueVerts[Index]);
return Positions[VertexID];
}
FVector3f GetNormal(uint32 Index) const override
{
return Normals[UniqueVerts[Index]];
}
FVector2f GetInputTexcoord(uint32 Index) const override
{
return TexCoords.Get(UniqueVerts[Index], 0);
}
void InitOutputTexcoords(uint32 Num) override
{
check(Num == UniqueVerts.Num());
}
void SetOutputTexcoord(uint32 Index, const FVector2f& Value) override
{
OutputTexCoords[UniqueVerts[Index]] = FVector2D(Value);
}
void ResolvePackedUVs()
{
if (bMustRemap)
{
CopyRemappedUVs(MeshDescription, RemapTriangles, nullptr, true, OutputTexCoords, OutputTexCoords);
}
}
};
bool FStaticMeshOperations::GenerateUniqueUVsForStaticMesh(const FMeshDescription& MeshDescription, int32 TextureResolution, bool bMergeIdenticalMaterials, TArray<FVector2D>& OutTexCoords)
{
FGenerateUVOptions GenerateUVOptions;
GenerateUVOptions.TextureResolution = TextureResolution;
GenerateUVOptions.bMergeTrianglesWithIdenticalAttributes = bMergeIdenticalMaterials;
GenerateUVOptions.UVMethod = EGenerateUVMethod::Legacy;
return GenerateUV(MeshDescription, GenerateUVOptions, OutTexCoords);
}
bool FStaticMeshOperations::GenerateUV(const FMeshDescription& MeshDescription, const FGenerateUVOptions& GenerateUVOptions, TArray<FVector2D>& OutTexCoords)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshOperations::GenerateUniqueUVsForStaticMesh)
TVertexInstanceAttributesConstRef<FVector2f> VertexInstanceUVs = FStaticMeshConstAttributes(MeshDescription).GetVertexInstanceUVs();
OutTexCoords.Reset();
const bool bHasUVs = VertexInstanceUVs.GetNumElements() > 0;
#if WITH_EDITOR
if (bHasUVs && GenerateUVOptions.UVMethod == EGenerateUVMethod::Legacy)
#else
if (bHasUVs)
#endif
{
FUniqueUVMeshDescriptionView MeshDescriptionView(MeshDescription, GenerateUVOptions.bMergeTrianglesWithIdenticalAttributes, OutTexCoords);
// Find overlapping corners for UV generator. Allow some threshold - this should not produce any error in a case if resulting
// mesh will not merge these vertices.
FOverlappingCorners OverlappingCorners(MeshDescriptionView, THRESH_POINTS_ARE_SAME);
// Generate new UVs
FLayoutUV Packer(MeshDescriptionView);
int32 NumCharts = Packer.FindCharts(OverlappingCorners);
// Scale down texture resolution to speed up UV generation time
// Packing expects at least one texel per chart. This is the absolute minimum to generate valid UVs.
const int32 PackingResolution = FMath::Clamp(GenerateUVOptions.TextureResolution / 4, 32, 512);
const int32 AbsoluteMinResolution = 1 << FMath::CeilLogTwo(FMath::Sqrt((float)NumCharts));
const int32 FinalPackingResolution = FMath::Max(PackingResolution, AbsoluteMinResolution);
bool bPackSuccess = Packer.FindBestPacking(FinalPackingResolution);
if (bPackSuccess)
{
Packer.CommitPackedUVs();
MeshDescriptionView.ResolvePackedUVs();
}
else
{
OutTexCoords.Reset();
}
}
#if WITH_EDITOR
// Missing/invalid UVs, use the AutoUV interface
if (OutTexCoords.IsEmpty())
{
IGeometryProcessingInterfacesModule* GeomProcInterfaces = FModuleManager::Get().GetModulePtr<IGeometryProcessingInterfacesModule>("GeometryProcessingInterfaces");
IGeometryProcessing_MeshAutoUV* MeshAutoUV = GeomProcInterfaces ? GeomProcInterfaces->GetMeshAutoUVImplementation() : nullptr;
if (MeshAutoUV)
{
FMeshDescription MeshCopy = MeshDescription;
TArray<FTriangleID> RemapTriangles;
FElementIDRemappings ElementIDRemappings;
bool bMustRemap = false;
// Ensure we have properly setup TriangleUVs on our mesh
int32 UVChannelCount = MeshCopy.VertexInstanceAttributes().GetAttributeChannelCount(MeshAttribute::VertexInstance::TextureCoordinate);
MeshCopy.SetNumUVChannels(UVChannelCount);
if (GenerateUVOptions.bMergeTrianglesWithIdenticalAttributes)
{
TArray<FTriangleID> DuplicateTriangles;
bMustRemap = GatherUniqueTriangles(MeshDescription, true, RemapTriangles, nullptr, &DuplicateTriangles);
if (bMustRemap)
{
MeshCopy.DeleteTriangles(DuplicateTriangles);
MeshCopy.Compact(ElementIDRemappings);
}
}
auto GetAutoUVMethod = [](EGenerateUVMethod GenerateUVMethod) -> IGeometryProcessing_MeshAutoUV::EAutoUVMethod
{
switch (GenerateUVMethod)
{
case EGenerateUVMethod::UVAtlas: return IGeometryProcessing_MeshAutoUV::EAutoUVMethod::UVAtlas;
case EGenerateUVMethod::XAtlas: return IGeometryProcessing_MeshAutoUV::EAutoUVMethod::XAtlas;
default: return IGeometryProcessing_MeshAutoUV::EAutoUVMethod::PatchBuilder;
}
};
IGeometryProcessing_MeshAutoUV::FOptions Options = MeshAutoUV->ConstructDefaultOptions();
Options.Method = GetAutoUVMethod(GenerateUVOptions.UVMethod);
IGeometryProcessing_MeshAutoUV::FResults Results;
MeshAutoUV->GenerateUVs(MeshCopy, Options, Results);
if (Results.ResultCode == IGeometryProcessing_MeshAutoUV::EResultCode::Success)
{
TVertexInstanceAttributesConstRef<FVector2f> TexCoords;
FStaticMeshConstAttributes AttributesCopy(MeshCopy);
TexCoords = AttributesCopy.GetVertexInstanceUVs();
OutTexCoords.SetNumUninitialized(MeshDescription.VertexInstances().Num());
if (bMustRemap)
{
CopyRemappedUVs(MeshDescription, RemapTriangles, &ElementIDRemappings, false, TexCoords, OutTexCoords);
}
else
{
int32 VertexInstanceIndex = 0;
for (const FVertexInstanceID VertexInstanceID : MeshCopy.VertexInstances().GetElementIDs())
{
OutTexCoords[VertexInstanceIndex] = FVector2D(TexCoords.Get(VertexInstanceID, 0));
VertexInstanceIndex++;
}
}
}
}
}
#endif
check(OutTexCoords.IsEmpty() || OutTexCoords.Num() == MeshDescription.VertexInstances().Num());
return !OutTexCoords.IsEmpty();
}
bool FStaticMeshOperations::AddUVChannel(FMeshDescription& MeshDescription)
{
TVertexInstanceAttributesRef<FVector2f> VertexInstanceUVs = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2f>(MeshAttribute::VertexInstance::TextureCoordinate);
if (VertexInstanceUVs.GetNumChannels() >= MAX_MESH_TEXTURE_COORDS)
{
UE_LOG(LogStaticMeshOperations, Error, TEXT("AddUVChannel: Cannot add UV channel. Maximum number of UV channels reached (%d)."), MAX_MESH_TEXTURE_COORDS);
return false;
}
VertexInstanceUVs.SetNumChannels(VertexInstanceUVs.GetNumChannels() + 1);
return true;
}
bool FStaticMeshOperations::InsertUVChannel(FMeshDescription& MeshDescription, int32 UVChannelIndex)
{
TVertexInstanceAttributesRef<FVector2f> VertexInstanceUVs = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2f>(MeshAttribute::VertexInstance::TextureCoordinate);
if (UVChannelIndex < 0 || UVChannelIndex > VertexInstanceUVs.GetNumChannels())
{
UE_LOG(LogStaticMeshOperations, Error, TEXT("InsertUVChannel: Cannot insert UV channel. Given UV channel index %d is out of bounds."), UVChannelIndex);
return false;
}
if (VertexInstanceUVs.GetNumChannels() >= MAX_MESH_TEXTURE_COORDS)
{
UE_LOG(LogStaticMeshOperations, Error, TEXT("InsertUVChannel: Cannot insert UV channel. Maximum number of UV channels reached (%d)."), MAX_MESH_TEXTURE_COORDS);
return false;
}
VertexInstanceUVs.InsertChannel(UVChannelIndex);
return true;
}
bool FStaticMeshOperations::RemoveUVChannel(FMeshDescription& MeshDescription, int32 UVChannelIndex)
{
TVertexInstanceAttributesRef<FVector2f> VertexInstanceUVs = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2f>(MeshAttribute::VertexInstance::TextureCoordinate);
if (VertexInstanceUVs.GetNumChannels() == 1)
{
UE_LOG(LogStaticMeshOperations, Error, TEXT("RemoveUVChannel: Cannot remove UV channel. There must be at least one channel."));
return false;
}
if (UVChannelIndex < 0 || UVChannelIndex >= VertexInstanceUVs.GetNumChannels())
{
UE_LOG(LogStaticMeshOperations, Error, TEXT("RemoveUVChannel: Cannot remove UV channel. Given UV channel index %d is out of bounds."), UVChannelIndex);
return false;
}
VertexInstanceUVs.RemoveChannel(UVChannelIndex);
return true;
}
void FStaticMeshOperations::GeneratePlanarUV(const FMeshDescription& MeshDescription, const FUVMapParameters& Params, TMap<FVertexInstanceID, FVector2D>& OutTexCoords)
{
// Project along X-axis (left view), UV along Z Y axes
FVector U = FVector::UpVector;
FVector V = FVector::RightVector;
TMeshAttributesConstRef<FVertexID, FVector3f> VertexPositions = MeshDescription.GetVertexPositions();
OutTexCoords.Reserve(MeshDescription.VertexInstances().Num());
FVector Size(Params.Size * Params.Scale);
FVector Offset = Params.Position - Size / 2.f;
for (const FVertexInstanceID VertexInstanceID : MeshDescription.VertexInstances().GetElementIDs())
{
const FVertexID VertexID = MeshDescription.GetVertexInstanceVertex(VertexInstanceID);
FVector Vertex(VertexPositions[VertexID]);
// Apply the gizmo transforms
Vertex = Params.Rotation.RotateVector(Vertex);
Vertex -= Offset;
Vertex /= Size;
float UCoord = FVector::DotProduct(Vertex, U) * Params.UVTile.X;
float VCoord = FVector::DotProduct(Vertex, V) * Params.UVTile.Y;
OutTexCoords.Add(VertexInstanceID, FVector2D(UCoord, VCoord));
}
}
void FStaticMeshOperations::GenerateCylindricalUV(FMeshDescription& MeshDescription, const FUVMapParameters& Params, TMap<FVertexInstanceID, FVector2D>& OutTexCoords)
{
FVector3f Size(Params.Size * Params.Scale); //LWC_TODO: Precision loss
FVector3f Offset(Params.Position); //LWC_TODO: Precision loss
// Cylinder along X-axis, counterclockwise from -Y axis as seen from left view
FVector3f V = FVector3f::ForwardVector;
Offset.X -= Size.X / 2.f;
TMeshAttributesConstRef<FVertexID, FVector3f> VertexPositions = MeshDescription.GetVertexPositions();
OutTexCoords.Reserve(MeshDescription.VertexInstances().Num());
const float AngleOffset = PI; // offset to get the same result as in 3dsmax
for (const FVertexInstanceID VertexInstanceID : MeshDescription.VertexInstances().GetElementIDs())
{
const FVertexID VertexID = MeshDescription.GetVertexInstanceVertex(VertexInstanceID);
FVector3f Vertex = VertexPositions[VertexID];
// Apply the gizmo transforms
Vertex = FVector3f(Params.Rotation.RotateVector(FVector3d(Vertex)));
Vertex -= Offset;
Vertex /= Size;
float Angle = FMath::Atan2(Vertex.Z, Vertex.Y);
Angle += AngleOffset;
Angle *= Params.UVTile.X;
float UCoord = Angle / (2 * PI);
float VCoord = FVector3f::DotProduct(Vertex, V) * Params.UVTile.Y;
OutTexCoords.Add(VertexInstanceID, FVector2D(UCoord, VCoord));
}
// Fix the UV coordinates for triangles at the seam where the angle wraps around
for (const FPolygonID PolygonID : MeshDescription.Polygons().GetElementIDs())
{
TArray<FVertexInstanceID, TInlineAllocator<4>> VertexInstances = MeshDescription.GetPolygonVertexInstances<TInlineAllocator<4>>(PolygonID);
int32 NumInstances = VertexInstances.Num();
if (NumInstances >= 2)
{
for (int32 StartIndex = 0; StartIndex < NumInstances; ++StartIndex)
{
int32 EndIndex = StartIndex + 1;
if (EndIndex >= NumInstances)
{
EndIndex = EndIndex % NumInstances;
}
const FVector2D& StartUV = OutTexCoords[VertexInstances[StartIndex]];
FVector2D& EndUV = OutTexCoords[VertexInstances[EndIndex]];
// TODO: Improve fix for UVTile other than 1
float Threshold = 0.5f / Params.UVTile.X;
if (FMath::Abs(EndUV.X - StartUV.X) > Threshold)
{
// Fix the U coordinate to get the texture go counterclockwise
if (EndUV.X > Threshold)
{
if (EndUV.X >= 1.f)
{
EndUV.X -= 1.f;
}
}
else
{
if (EndUV.X <= 0)
{
EndUV.X += 1.f;
}
}
}
}
}
}
}
void FStaticMeshOperations::GenerateBoxUV(const FMeshDescription& MeshDescription, const FUVMapParameters& Params, TMap<FVertexInstanceID, FVector2D>& OutTexCoords)
{
FVector3f Size(Params.Size * Params.Scale); //LWC_TODO: Precision loss
FVector3f HalfSize = Size / 2.0f;
TMeshAttributesConstRef<FVertexID, FVector3f> VertexPositions = MeshDescription.GetVertexPositions();
OutTexCoords.Reserve(MeshDescription.VertexInstances().Num());
// Setup the UVs such that the mapping is from top-left to bottom-right when viewed orthographically
TArray<TPair<FVector3f, FVector3f>> PlaneUVs;
PlaneUVs.Add(TPair<FVector3f, FVector3f>(FVector3f::ForwardVector, FVector3f::RightVector)); // Top view
PlaneUVs.Add(TPair<FVector3f, FVector3f>(FVector3f::BackwardVector, FVector3f::RightVector)); // Bottom view
PlaneUVs.Add(TPair<FVector3f, FVector3f>(FVector3f::ForwardVector, FVector3f::DownVector)); // Right view
PlaneUVs.Add(TPair<FVector3f, FVector3f>(FVector3f::BackwardVector, FVector3f::DownVector)); // Left view
PlaneUVs.Add(TPair<FVector3f, FVector3f>(FVector3f::LeftVector, FVector3f::DownVector)); // Front view
PlaneUVs.Add(TPair<FVector3f, FVector3f>(FVector3f::RightVector, FVector3f::DownVector)); // Back view
TArray<FPlane4f> BoxPlanes;
const FVector3f Center(Params.Position); //LWC_TODO: Precision loss
BoxPlanes.Add(FPlane4f(Center + FVector3f(0, 0, HalfSize.Z), FVector3f::UpVector)); // Top plane
BoxPlanes.Add(FPlane4f(Center - FVector3f(0, 0, HalfSize.Z), FVector3f::DownVector)); // Bottom plane
BoxPlanes.Add(FPlane4f(Center + FVector3f(0, HalfSize.Y, 0), FVector3f::RightVector)); // Right plane
BoxPlanes.Add(FPlane4f(Center - FVector3f(0, HalfSize.Y, 0), FVector3f::LeftVector)); // Left plane
BoxPlanes.Add(FPlane4f(Center + FVector3f(HalfSize.X, 0, 0), FVector3f::ForwardVector)); // Front plane
BoxPlanes.Add(FPlane4f(Center - FVector3f(HalfSize.X, 0, 0), FVector3f::BackwardVector)); // Back plane
// For each polygon, find the box plane that best matches the polygon normal
for (const FTriangleID TriangleID : MeshDescription.Triangles().GetElementIDs())
{
TArrayView<const FVertexID> Vertices = MeshDescription.GetTriangleVertices(TriangleID);
TArrayView<const FVertexInstanceID> VertexInstances = MeshDescription.GetTriangleVertexInstances(TriangleID);
FVector3f Vertex0 = VertexPositions[Vertices[0]];
FVector3f Vertex1 = VertexPositions[Vertices[1]];
FVector3f Vertex2 = VertexPositions[Vertices[2]];
FPlane4f PolygonPlane(Vertex0, Vertex2, Vertex1);
// Find the box plane that is most aligned with the polygon plane
// TODO: Also take the distance between the planes into consideration
float MaxProj = 0.f;
int32 BestPlaneIndex = 0;
for (int32 Index = 0; Index < BoxPlanes.Num(); ++Index)
{
float Proj = FVector3f::DotProduct(BoxPlanes[Index], PolygonPlane);
if (Proj > MaxProj)
{
MaxProj = Proj;
BestPlaneIndex = Index;
}
}
FVector3f U = PlaneUVs[BestPlaneIndex].Key;
FVector3f V = PlaneUVs[BestPlaneIndex].Value;
FVector3f Offset = FVector3f(Params.Position) - HalfSize * (U + V);
for (const FVertexInstanceID& VertexInstanceID : VertexInstances)
{
const FVertexID VertexID = MeshDescription.GetVertexInstanceVertex(VertexInstanceID);
FVector3f Vertex = VertexPositions[VertexID];
// Apply the gizmo transforms
Vertex = FVector3f(Params.Rotation.RotateVector(FVector3d(Vertex)));
Vertex -= Offset;
// Normalize coordinates
Vertex.X = FMath::IsNearlyZero(Size.X) ? 0.0f : Vertex.X / Size.X;
Vertex.Y = FMath::IsNearlyZero(Size.Y) ? 0.0f : Vertex.Y / Size.Y;
Vertex.Z = FMath::IsNearlyZero(Size.Z) ? 0.0f : Vertex.Z / Size.Z;
float UCoord = FVector3f::DotProduct(Vertex, U) * Params.UVTile.X;
float VCoord = FVector3f::DotProduct(Vertex, V) * Params.UVTile.Y;
OutTexCoords.Add(VertexInstanceID, FVector2D(UCoord, VCoord));
}
}
}
void FStaticMeshOperations::SwapPolygonPolygonGroup(FMeshDescription& MeshDescription, int32 SectionIndex, int32 TriangleIndexStart, int32 TriangleIndexEnd, bool bRemoveEmptyPolygonGroup)
{
int32 TriangleIndex = 0;
TPolygonGroupAttributesRef<FName> PolygonGroupNames = MeshDescription.PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
FPolygonGroupID TargetPolygonGroupID(SectionIndex);
if (!bRemoveEmptyPolygonGroup)
{
while (!MeshDescription.PolygonGroups().IsValid(TargetPolygonGroupID))
{
TargetPolygonGroupID = MeshDescription.CreatePolygonGroup();
PolygonGroupNames[TargetPolygonGroupID] = FName(*(TEXT("SwapPolygonMaterialSlotName_") + FString::FromInt(TargetPolygonGroupID.GetValue())));
TargetPolygonGroupID = FPolygonGroupID(SectionIndex);
}
}
else
{
//This will not follow the SectionIndex value if the value is greater then the number of section (do not use this when merging meshes)
if (!MeshDescription.PolygonGroups().IsValid(TargetPolygonGroupID))
{
TargetPolygonGroupID = MeshDescription.CreatePolygonGroup();
PolygonGroupNames[TargetPolygonGroupID] = FName(*(TEXT("SwapPolygonMaterialSlotName_") + FString::FromInt(TargetPolygonGroupID.GetValue())));
}
}
for (const FPolygonID PolygonID : MeshDescription.Polygons().GetElementIDs())
{
int32 TriangleCount = MeshDescription.GetPolygonTriangles(PolygonID).Num();
if (TriangleIndex >= TriangleIndexStart && TriangleIndex < TriangleIndexEnd)
{
check(TriangleIndex + (TriangleCount - 1) < TriangleIndexEnd);
FPolygonGroupID OldpolygonGroupID = MeshDescription.GetPolygonPolygonGroup(PolygonID);
if (OldpolygonGroupID != TargetPolygonGroupID)
{
MeshDescription.SetPolygonPolygonGroup(PolygonID, TargetPolygonGroupID);
if (bRemoveEmptyPolygonGroup && MeshDescription.GetPolygonGroupPolygonIDs(OldpolygonGroupID).Num() < 1)
{
MeshDescription.DeletePolygonGroup(OldpolygonGroupID);
}
}
}
TriangleIndex += TriangleCount;
}
}
bool FStaticMeshOperations::HasVertexColor(const FMeshDescription& MeshDescription)
{
TVertexInstanceAttributesConstRef<FVector4f> VertexInstanceColors = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector4f>(MeshAttribute::VertexInstance::Color);
bool bHasVertexColor = false;
FVector4f WhiteColor(FLinearColor::White);
for (const FVertexInstanceID VertexInstanceID : MeshDescription.VertexInstances().GetElementIDs())
{
if (VertexInstanceColors[VertexInstanceID] != WhiteColor)
{
bHasVertexColor = true;
break;
}
}
return bHasVertexColor;
}
void FStaticMeshOperations::BuildWeldedVertexIDRemap(const FMeshDescription& MeshDescription, const float WeldingThreshold, TMap<FVertexID, FVertexID>& OutVertexIDRemap)
{
TVertexAttributesConstRef<FVector3f> VertexPositions = MeshDescription.GetVertexPositions();
int32 NumVertex = MeshDescription.Vertices().Num();
OutVertexIDRemap.Reserve(NumVertex);
// Create a list of vertex Z/index pairs
TArray<MeshDescriptionOperationNamespace::FIndexAndZ> VertIndexAndZ;
VertIndexAndZ.Reserve(NumVertex);
for (const FVertexID VertexID : MeshDescription.Vertices().GetElementIDs())
{
VertIndexAndZ.Emplace(VertexID.GetValue(), VertexPositions[VertexID]);
}
// Sort the vertices by z value
VertIndexAndZ.Sort(MeshDescriptionOperationNamespace::FCompareIndexAndZ());
// Search for duplicates, quickly!
for (int32 i = 0; i < VertIndexAndZ.Num(); i++)
{
FVertexID Index_i = FVertexID(VertIndexAndZ[i].Index);
if (OutVertexIDRemap.Contains(Index_i))
{
continue;
}
OutVertexIDRemap.FindOrAdd(Index_i) = Index_i;
// only need to search forward, since we add pairs both ways
for (int32 j = i + 1; j < VertIndexAndZ.Num(); j++)
{
if (FMath::Abs(VertIndexAndZ[j].Z - VertIndexAndZ[i].Z) > WeldingThreshold)
break; // can't be any more dups
const FVector3f& PositionA = *(VertIndexAndZ[i].OriginalVector);
const FVector3f& PositionB = *(VertIndexAndZ[j].OriginalVector);
if (PositionA.Equals(PositionB, WeldingThreshold))
{
OutVertexIDRemap.FindOrAdd(FVertexID(VertIndexAndZ[j].Index)) = Index_i;
}
}
}
}
FSHAHash FStaticMeshOperations::ComputeSHAHash(const FMeshDescription& MeshDescription, bool bSkipTransientAttributes)
{
FSHA1 HashState;
TArray< FName > AttributesNames;
auto HashAttributeSet = [&AttributesNames, &HashState, bSkipTransientAttributes](const auto& AttributeSet)
{
AttributesNames.Reset();
if (!bSkipTransientAttributes)
{
AttributeSet.GetAttributeNames(AttributesNames);
}
else
{
AttributeSet.ForEach([&AttributesNames](const FName AttributeName, auto AttributesRef)
{
bool bIsTransient = (AttributesRef.GetFlags() & EMeshAttributeFlags::Transient) != EMeshAttributeFlags::None;
if (!bIsTransient)
{
AttributesNames.Add(AttributeName);
}
});
}
AttributesNames.Sort(FNameLexicalLess());
for (FName AttributeName : AttributesNames)
{
uint32 AttributeHash = AttributeSet.GetHash(AttributeName);
HashState.Update((uint8*)&AttributeHash, sizeof(AttributeHash));
}
};
HashAttributeSet(MeshDescription.VertexAttributes());
HashAttributeSet(MeshDescription.VertexInstanceAttributes());
HashAttributeSet(MeshDescription.EdgeAttributes());
HashAttributeSet(MeshDescription.PolygonAttributes());
HashAttributeSet(MeshDescription.PolygonGroupAttributes());
FSHAHash OutHash;
HashState.Final();
HashState.GetHash(&OutHash.Hash[0]);
return OutHash;
}
void FStaticMeshOperations::FlipPolygons(FMeshDescription& MeshDescription)
{
TSet<FVertexInstanceID> VertexInstanceIDs;
for (const FTriangleID TriangleID : MeshDescription.Triangles().GetElementIDs())
{
TArrayView<const FVertexInstanceID> TriVertInstances = MeshDescription.GetTriangleVertexInstances(TriangleID);
for (const FVertexInstanceID TriVertInstance : TriVertInstances)
{
VertexInstanceIDs.Add(TriVertInstance);
}
MeshDescription.ReverseTriangleFacing(TriangleID);
}
// Flip tangents and normals
TVertexInstanceAttributesRef<FVector3f> VertexNormals = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector3f>(MeshAttribute::VertexInstance::Normal);
TVertexInstanceAttributesRef<FVector3f> VertexTangents = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector3f>(MeshAttribute::VertexInstance::Tangent);
for (const FVertexInstanceID VertexInstanceID : VertexInstanceIDs)
{
// Just reverse the sign of the normals/tangents; note that since binormals are the cross product of normal with tangent, they are left untouched
VertexNormals[VertexInstanceID] *= -1.0f;
VertexTangents[VertexInstanceID] *= -1.0f;
}
}
void FStaticMeshOperations::ApplyTransform(FMeshDescription& MeshDescription, const FTransform& Transform, bool bApplyCorrectNormalTransform)
{
ApplyTransform(MeshDescription, Transform.ToMatrixWithScale(), bApplyCorrectNormalTransform);
}
void FStaticMeshOperations::ApplyTransform(FMeshDescription& MeshDescription, const FMatrix& Transform, bool bApplyCorrectNormalTransform)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshOperations::ApplyTransform)
TVertexAttributesRef<FVector3f> VertexPositions = MeshDescription.VertexAttributes().GetAttributesRef<FVector3f>(MeshAttribute::Vertex::Position);
TVertexInstanceAttributesRef<FVector3f> VertexInstanceNormals = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector3f>(MeshAttribute::VertexInstance::Normal);
TVertexInstanceAttributesRef<FVector3f> VertexInstanceTangents = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector3f>(MeshAttribute::VertexInstance::Tangent);
TVertexInstanceAttributesRef<float> VertexInstanceBinormalSigns = MeshDescription.VertexInstanceAttributes().GetAttributesRef<float>(MeshAttribute::VertexInstance::BinormalSign);
for (const FVertexID VertexID : MeshDescription.Vertices().GetElementIDs())
{
VertexPositions[VertexID] = FVector4f(Transform.TransformPosition(FVector3d(VertexPositions[VertexID])));
}
const bool bIsMirrored = Transform.Determinant() < 0.f;
const float MulBy = bIsMirrored ? -1.f : 1.f;
FMatrix NormalsTransform, TangentsTransform;
if (bApplyCorrectNormalTransform)
{
// Note: Assuming we'll normalize after, transforming by the transpose-adjoint * the sign of the determinant
// is equivalent to transforming by the inverse transpose; ref: TMatrix::TransformByUsingAdjointT
NormalsTransform = Transform.TransposeAdjoint() * (double)MulBy;
// Note: Tangents *do not* transform by the Transform's inverse transpose, just by the Transform
TangentsTransform = Transform;
}
else // match UE renderer
{
// UE's renderer transforms normals and tangents without scale (as in FTransform::TransformVectorNoScale)
NormalsTransform = Transform;
NormalsTransform.RemoveScaling();
TangentsTransform = NormalsTransform;
}
for (const FVertexInstanceID VertexInstanceID : MeshDescription.VertexInstances().GetElementIDs())
{
FVector3f Tangent = VertexInstanceTangents[VertexInstanceID];
FVector3f Normal = VertexInstanceNormals[VertexInstanceID];
VertexInstanceTangents[VertexInstanceID] = (FVector3f)FVector(TangentsTransform.TransformVector((FVector)Tangent).GetSafeNormal());
VertexInstanceNormals[VertexInstanceID] = (FVector3f)FVector(NormalsTransform.TransformVector((FVector)Normal).GetSafeNormal());
float BinormalSign = VertexInstanceBinormalSigns[VertexInstanceID];
VertexInstanceBinormalSigns[VertexInstanceID] = BinormalSign * MulBy;
}
if (bIsMirrored)
{
MeshDescription.ReverseAllPolygonFacing();
}
}
namespace UE::Private
{
class FPrivateVertexInfo
{
public:
FVector3f Position;
FVector3f Normal;
FVector3f Tangents[2];
FLinearColor Color;
FVector2f TexCoords[MAX_MESH_TEXTURE_COORDS_MD];
void Validate()
{
Normal.Normalize();
Tangents[0] -= (Tangents[0] | Normal) * Normal;
Tangents[0].Normalize();
Tangents[1] -= (Tangents[1] | Normal) * Normal;
Tangents[1] -= (Tangents[1] | Tangents[0]) * Tangents[0];
Tangents[1].Normalize();
Color = Color.GetClamped();
}
bool Equals(const FPrivateVertexInfo& Other) const
{
constexpr float UVEpsilon = 1.0f / 1024.0f;
if (!Position.Equals(Other.Position, UE_THRESH_POINTS_ARE_SAME) ||
!Tangents[0].Equals(Other.Tangents[0], UE_THRESH_NORMALS_ARE_SAME) ||
!Tangents[1].Equals(Other.Tangents[1], UE_THRESH_NORMALS_ARE_SAME) ||
!Normal.Equals(Other.Normal, UE_THRESH_NORMALS_ARE_SAME) ||
!Color.Equals(Other.Color))
{
return false;
}
// UVs
for (int32 UVIndex = 0; UVIndex < MAX_MESH_TEXTURE_COORDS_MD; UVIndex++)
{
if (!TexCoords[UVIndex].Equals(Other.TexCoords[UVIndex], UVEpsilon))
{
return false;
}
}
return true;
}
};
}
int32 FStaticMeshOperations::GetUniqueVertexCount(const FMeshDescription& MeshDescription)
{
FOverlappingCorners OverlappingCorners;
FStaticMeshOperations::FindOverlappingCorners(OverlappingCorners, MeshDescription, UE_THRESH_POINTS_ARE_SAME);
return GetUniqueVertexCount(MeshDescription, OverlappingCorners);
}
int32 FStaticMeshOperations::GetUniqueVertexCount(const FMeshDescription& MeshDescription, const FOverlappingCorners& OverlappingCorners)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshOperations::GetUniqueVertexCount);
constexpr uint32 NumTexCoords = MAX_MESH_TEXTURE_COORDS_MD;
TArray< UE::Private::FPrivateVertexInfo > Verts;
Verts.Reserve(MeshDescription.Vertices().Num());
TMap< int32, int32 > VertsMap;
int32 NumFaces = MeshDescription.Triangles().Num();
int32 NumWedges = NumFaces * 3;
const FStaticMeshConstAttributes MeshAttribute(MeshDescription);
TVertexAttributesConstRef<FVector3f> VertexPositions = MeshAttribute.GetVertexPositions();
TVertexInstanceAttributesConstRef<FVector3f> VertexNormals = MeshAttribute.GetVertexInstanceNormals();
TVertexInstanceAttributesConstRef<FVector3f> VertexTangents = MeshAttribute.GetVertexInstanceTangents();
TVertexInstanceAttributesConstRef<float> VertexBinormalSigns = MeshAttribute.GetVertexInstanceBinormalSigns();
TVertexInstanceAttributesConstRef<FVector4f> VertexColors = MeshAttribute.GetVertexInstanceColors();
TVertexInstanceAttributesConstRef<FVector2f> VertexUVs = MeshAttribute.GetVertexInstanceUVs();
int32 WedgeIndex = 0;
for (const FTriangleID TriangleID : MeshDescription.Triangles().GetElementIDs())
{
TArrayView<const FVertexID> VertexIDs = MeshDescription.GetTriangleVertices(TriangleID);
FVector3f CornerPositions[3];
for (int32 TriVert = 0; TriVert < 3; ++TriVert)
{
const FVertexID VertexID = VertexIDs[TriVert];
CornerPositions[TriVert] = VertexPositions[VertexID];
}
// Don't process degenerate triangles.
if (CornerPositions[0].Equals(CornerPositions[1], UE_THRESH_POINTS_ARE_SAME) ||
CornerPositions[0].Equals(CornerPositions[2], UE_THRESH_POINTS_ARE_SAME) ||
CornerPositions[1].Equals(CornerPositions[2], UE_THRESH_POINTS_ARE_SAME))
{
WedgeIndex += 3;
continue;
}
for (int32 TriVert = 0; TriVert < 3; ++TriVert, ++WedgeIndex)
{
const FVertexInstanceID VertexInstanceID = MeshDescription.GetTriangleVertexInstance(TriangleID, TriVert);
const FVector3f& VertexPosition = CornerPositions[TriVert];
UE::Private::FPrivateVertexInfo NewVert;
NewVert.Position = CornerPositions[TriVert];
NewVert.Tangents[0] = VertexTangents[VertexInstanceID];
NewVert.Normal = VertexNormals[VertexInstanceID];
NewVert.Tangents[1] = FVector3f(0.0f);
if (!NewVert.Normal.IsNearlyZero(SMALL_NUMBER) && !NewVert.Tangents[0].IsNearlyZero(SMALL_NUMBER))
{
NewVert.Tangents[1] = FVector3f::CrossProduct(NewVert.Normal, NewVert.Tangents[0]).GetSafeNormal() * VertexBinormalSigns[VertexInstanceID];
}
// Fix bad tangents
NewVert.Tangents[0] = NewVert.Tangents[0].ContainsNaN() ? FVector3f::ZeroVector : NewVert.Tangents[0];
NewVert.Tangents[1] = NewVert.Tangents[1].ContainsNaN() ? FVector3f::ZeroVector : NewVert.Tangents[1];
NewVert.Normal = NewVert.Normal.ContainsNaN() ? FVector3f::ZeroVector : NewVert.Normal;
NewVert.Color = FLinearColor(VertexColors[VertexInstanceID]);
for (int32 UVIndex = 0; UVIndex < NumTexCoords; UVIndex++)
{
if (UVIndex < VertexUVs.GetNumChannels())
{
NewVert.TexCoords[UVIndex] = VertexUVs.Get(VertexInstanceID, UVIndex);
}
else
{
NewVert.TexCoords[UVIndex] = FVector2f::ZeroVector;
}
}
// Make sure this vertex is valid from the start
NewVert.Validate();
//Never add duplicated vertex instance
//Use WedgeIndex since OverlappingCorners has been built based on that
const TArray<int32>& DupVerts = OverlappingCorners.FindIfOverlapping(WedgeIndex);
int32 Index = INDEX_NONE;
for (int32 k = 0; k < DupVerts.Num(); k++)
{
if (DupVerts[k] >= WedgeIndex)
{
// the verts beyond me haven't been placed yet, so these duplicates are not relevant
break;
}
int32* Location = VertsMap.Find(DupVerts[k]);
if (Location)
{
UE::Private::FPrivateVertexInfo& FoundVert = Verts[*Location];
if (NewVert.Equals(FoundVert))
{
Index = *Location;
break;
}
}
}
if (Index == INDEX_NONE)
{
Index = Verts.Add(NewVert);
VertsMap.Add(WedgeIndex, Index);
}
}
}
return Verts.Num();
}
void FStaticMeshOperations::ReorderMeshDescriptionPolygonGroups(const FMeshDescription& SourceMeshDescription
, FMeshDescription& DestinationMeshDescription
, TOptional<const FString> DestinationUnmatchMaterialName_Msg
, TOptional<const FString> DestinationPolygonGroupCountDifferFromSource_Msg)
{
if (SourceMeshDescription.IsEmpty() || DestinationMeshDescription.IsEmpty() || DestinationMeshDescription.PolygonGroups().Num() <= 1)
{
//Nothing to re-order
return;
}
//Do not allow reorder if the material count is different between the destination and the source
if (DestinationMeshDescription.PolygonGroups().Num() != SourceMeshDescription.PolygonGroups().Num())
{
if(DestinationPolygonGroupCountDifferFromSource_Msg.IsSet())
{
UE_LOG(LogStaticMeshOperations, Warning, TEXT("%s"), *DestinationPolygonGroupCountDifferFromSource_Msg.GetValue());
}
return;
}
FStaticMeshConstAttributes SourceAttribute(SourceMeshDescription);
TPolygonGroupAttributesConstRef<FName> SourceMaterialSlotNameAttribute = SourceAttribute.GetPolygonGroupMaterialSlotNames();
FStaticMeshAttributes DestinationAttribute(DestinationMeshDescription);
TPolygonGroupAttributesConstRef<FName> DestinationMaterialSlotNameAttribute = DestinationAttribute.GetPolygonGroupMaterialSlotNames();
TMap<FPolygonGroupID, FPolygonGroupID> MatchPolygonGroupsDestSource;
TMap<FPolygonGroupID, bool> SourceMaterialMatched;
SourceMaterialMatched.Reserve(SourceMaterialSlotNameAttribute.GetNumElements());
for (FPolygonGroupID SourcePolygonGroupID : SourceMeshDescription.PolygonGroups().GetElementIDs())
{
SourceMaterialMatched.Add(SourcePolygonGroupID, false);
}
TMap<FPolygonGroupID, bool> DestinationMaterialMatched;
DestinationMaterialMatched.Reserve(DestinationMaterialSlotNameAttribute.GetNumElements());
for (FPolygonGroupID DestinationPolygonGroupID : DestinationMeshDescription.PolygonGroups().GetElementIDs())
{
DestinationMaterialMatched.Add(DestinationPolygonGroupID, false);
}
//Find the material name match
for (TPair<FPolygonGroupID, bool>& DestinationMatched : DestinationMaterialMatched)
{
const FName MaterialNameToMatch = DestinationMaterialSlotNameAttribute[DestinationMatched.Key];
FPolygonGroupID MatchPolygonGroupID = INDEX_NONE;
for (TPair<FPolygonGroupID, bool>& SourceMatched : SourceMaterialMatched)
{
if (SourceMatched.Value)
{
continue;
}
if (SourceMaterialSlotNameAttribute[SourceMatched.Key] == MaterialNameToMatch)
{
MatchPolygonGroupID = SourceMatched.Key;
SourceMatched.Value = true;
DestinationMatched.Value = true;
break;
}
}
if (MatchPolygonGroupID != INDEX_NONE)
{
MatchPolygonGroupsDestSource.FindOrAdd(DestinationMatched.Key) = MatchPolygonGroupID;
}
}
if (MatchPolygonGroupsDestSource.Num() < DestinationMaterialMatched.Num() && DestinationUnmatchMaterialName_Msg.IsSet())
{
UE_LOG(LogStaticMeshOperations, Warning, TEXT("%s"), *DestinationUnmatchMaterialName_Msg.GetValue());
}
//Iterate the unmatched destination and use the first unmatched source
for (TPair<FPolygonGroupID, bool>& DestinationMatched : DestinationMaterialMatched)
{
if (DestinationMatched.Value)
{
//Skip this destination because its already matched
continue;
}
//Match the first unmatched source we found
for (TPair<FPolygonGroupID, bool>& SourceMatched : SourceMaterialMatched)
{
if (SourceMatched.Value)
{
//Skip this source because its already matched
continue;
}
//Force match
MatchPolygonGroupsDestSource.FindOrAdd(DestinationMatched.Key) = SourceMatched.Key;
DestinationMatched.Value = true;
SourceMatched.Value = true;
break;
}
}
//Since both source and destination have the same amount of material, the MatchPolygonGroupsDestSource should have the same count.
if(ensure(MatchPolygonGroupsDestSource.Num() == DestinationMaterialMatched.Num()))
{
//Remap the polygon group with the correct ID
DestinationMeshDescription.RemapPolygonGroups(MatchPolygonGroupsDestSource);
}
}
bool FStaticMeshOperations::ValidateAndFixData(FMeshDescription& MeshDescription, const FString& DebugName)
{
bool bHasInvalidPositions = false;
bool bHasInvalidTangentSpaces = false;
bool bHasInvalidUVs = false;
bool bHasInvalidVertexColors = false;
FStaticMeshAttributes Attributes(MeshDescription);
TVertexAttributesRef<FVector3f> VertexPositions = Attributes.GetVertexPositions();
for (const FVertexID VertexID : MeshDescription.Vertices().GetElementIDs())
{
if (VertexPositions[VertexID].ContainsNaN())
{
bHasInvalidPositions = true;
VertexPositions[VertexID] = FVector3f::ZeroVector;
}
}
TVertexInstanceAttributesRef<FVector3f> VertexInstanceNormals = Attributes.GetVertexInstanceNormals();
TVertexInstanceAttributesRef<FVector3f> VertexInstanceTangents = Attributes.GetVertexInstanceTangents();
TVertexInstanceAttributesRef<float> VertexInstanceBinormalSigns = Attributes.GetVertexInstanceBinormalSigns();
TVertexInstanceAttributesRef<FVector2f> VertexInstanceUVs = Attributes.GetVertexInstanceUVs();
TVertexInstanceAttributesRef<FVector4f> VertexInstanceColors = Attributes.GetVertexInstanceColors();
const int32 NumUVs = VertexInstanceUVs.GetNumChannels();
for (const FVertexInstanceID VertexInstanceID : MeshDescription.VertexInstances().GetElementIDs())
{
if (VertexInstanceNormals[VertexInstanceID].ContainsNaN())
{
bHasInvalidTangentSpaces = true;
VertexInstanceNormals[VertexInstanceID] = FVector3f::Zero();
}
if (VertexInstanceTangents[VertexInstanceID].ContainsNaN())
{
bHasInvalidTangentSpaces = true;
VertexInstanceTangents[VertexInstanceID] = FVector3f::Zero();
}
if (FMath::IsNaN(VertexInstanceBinormalSigns[VertexInstanceID]))
{
bHasInvalidTangentSpaces = true;
VertexInstanceBinormalSigns[VertexInstanceID] = 0.0f;
}
for (int32 UVIndex = 0; UVIndex < NumUVs; UVIndex++)
{
if (VertexInstanceUVs.Get(VertexInstanceID, UVIndex).ContainsNaN())
{
bHasInvalidUVs = true;
VertexInstanceUVs.Set(VertexInstanceID, UVIndex, FVector2f::Zero());
}
}
if (VertexInstanceColors[VertexInstanceID].ContainsNaN())
{
bHasInvalidVertexColors = true;
VertexInstanceColors[VertexInstanceID] = FVector4f::One();
}
}
if (!DebugName.IsEmpty())
{
if (bHasInvalidPositions)
{
UE_LOG(LogStaticMeshOperations, Display, TEXT("Mesh %s has NaNs in it's vertex positions! Offending positions are set to zero."), *DebugName);
}
if (bHasInvalidTangentSpaces)
{
UE_LOG(LogStaticMeshOperations, Display, TEXT("Mesh %s has NaNs in it's vertex instance tangent space! Offending tangents are set to zero."), *DebugName);
}
if (bHasInvalidUVs)
{
UE_LOG(LogStaticMeshOperations, Display, TEXT("Mesh %s has NaNs in it's vertex instance uvs! Offending uvs are set to zero."), *DebugName);
}
if (bHasInvalidVertexColors)
{
UE_LOG(LogStaticMeshOperations, Display, TEXT("Mesh %s has NaNs in it's vertex instance colors! Offending colors are set to white."), *DebugName);
}
}
return !bHasInvalidPositions && !bHasInvalidTangentSpaces && !bHasInvalidUVs && !bHasInvalidVertexColors;
}
#undef LOCTEXT_NAMESPACE