Files
UnrealEngineUWP/Engine/Source/Developer/MeshDescriptionOperations/Private/MeshDescriptionOperations.cpp
Richard TalbotWatkin 1dcb741a2b - Updated MeshDescription attribute calls to fix deprecation warnings.
- Removed TMeshAttributeArraySet::AddArray because the functionality was already available as part of SetNumIndices.
- Renamed TMeshAttributeArraySet::InsertArray, RemoveArray to InsertIndex, RemoveIndex for naming convention consistency (these methods deal with attribute indices, not with arrays). Added support for them in other attribute classes, and made them virtual so they can be called as part of an AttributesView.
- Removed redundant code in FUSDStaticMeshImportState::AddPolygons, when determining the number of UVs in the mesh description.
#rb none

[CL 4295326 by Richard TalbotWatkin in Dev-Editor branch]
2018-08-16 19:55:15 -04:00

1254 lines
52 KiB
C++

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "MeshDescriptionOperations.h"
#include "UObject/Package.h"
#include "MeshDescription.h"
#include "MeshAttributes.h"
#include "RawMesh.h"
#include "LayoutUV.h"
#include "OverlappingCorners.h"
#include "RenderUtils.h"
#include "mikktspace.h"
DEFINE_LOG_CATEGORY(LogMeshDescriptionOperations);
#define LOCTEXT_NAMESPACE "MeshDescriptionOperations"
//////////////////////////////////////////////////////////////////////////
// Local structure
struct FVertexInfo
{
FVertexInfo()
{
PolygonID = FPolygonID::Invalid;
VertexInstanceID = FVertexInstanceID::Invalid;
UVs = FVector2D(0.0f, 0.0f);
EdgeIDs.Reserve(2);//Most of the time a edge has two triangles
}
FPolygonID PolygonID;
FVertexInstanceID VertexInstanceID;
FVector2D UVs;
TArray<FEdgeID> EdgeIDs;
};
/** Helper struct for building acceleration structures. */
namespace MeshDescriptionOperationNamespace
{
struct FIndexAndZ
{
float Z;
int32 Index;
const FVector *OriginalVector;
/** Default constructor. */
FIndexAndZ() {}
/** Initialization constructor. */
FIndexAndZ(int32 InIndex, const FVector& 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()(FIndexAndZ const& A, FIndexAndZ const& B) const { return A.Z < B.Z; }
};
}
//////////////////////////////////////////////////////////////////////////
// Converters
void FMeshDescriptionOperations::ConvertHardEdgesToSmoothGroup(const FMeshDescription& SourceMeshDescription, FRawMesh& DestinationRawMesh)
{
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);
for (const FPolygonID PolygonID : SourceMeshDescription.Polygons().GetElementIDs())
{
if (ConsumedPolygons[PolygonID.GetValue()])
{
continue;
}
TArray<FPolygonID> ConnectedPolygons;
TArray<FPolygonID> LastConnectedPolygons;
ConnectedPolygons.Add(PolygonID);
LastConnectedPolygons.Add(FPolygonID::Invalid);
while (ConnectedPolygons.Num() > 0)
{
check(LastConnectedPolygons.Num() == ConnectedPolygons.Num());
FPolygonID LastPolygonID = LastConnectedPolygons.Pop(true);
FPolygonID CurrentPolygonID = ConnectedPolygons.Pop(true);
if (ConsumedPolygons[CurrentPolygonID.GetValue()])
{
continue;
}
TArray<FPolygonID> SoftEdgeNeigbors;
uint32& SmoothGroup = PolygonSmoothGroup.FindOrAdd(CurrentPolygonID);
uint32 AvoidSmoothGroup = 0;
uint32 NeighborSmoothGroup = 0;
const uint32 LastSmoothGroupValue = (LastPolygonID == FPolygonID::Invalid) ? 0 : PolygonSmoothGroup[LastPolygonID];
TArray<FEdgeID> PolygonEdges;
SourceMeshDescription.GetPolygonEdges(CurrentPolygonID, PolygonEdges);
for (const FEdgeID& EdgeID : PolygonEdges)
{
bool bIsHardEdge = EdgeHardnesses[EdgeID];
const TArray<FPolygonID>& EdgeConnectedPolygons = SourceMeshDescription.GetEdgeConnectedPolygons(EdgeID);
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;
}
}
//Now we have to put the data into the RawMesh
int32 TriangleIndex = 0;
for (const FPolygonID PolygonID : SourceMeshDescription.Polygons().GetElementIDs())
{
uint32 PolygonSmoothValue = PolygonSmoothGroup[PolygonID];
const TArray<FMeshTriangle>& Triangles = SourceMeshDescription.GetPolygonTriangles(PolygonID);
for (const FMeshTriangle& MeshTriangle : Triangles)
{
DestinationRawMesh.FaceSmoothingMasks[TriangleIndex++] = PolygonSmoothValue;
}
}
}
void FMeshDescriptionOperations::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(true);
int32 CurrentPolygonIDValue = CurrentPolygonID.GetValue();
check(FaceSmoothingMasks.IsValidIndex(CurrentPolygonIDValue));
const uint32 ReferenceSmoothGroup = FaceSmoothingMasks[CurrentPolygonIDValue];
TArray<FEdgeID> PolygonEdges;
DestinationMeshDescription.GetPolygonEdges(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 FMeshDescriptionOperations::ConvertToRawMesh(const FMeshDescription& SourceMeshDescription, FRawMesh& DestinationRawMesh, const TMap<FName, int32>& MaterialMap)
{
DestinationRawMesh.Empty();
//Gather all array data
TVertexAttributesConstRef<FVector> VertexPositions = SourceMeshDescription.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);
TVertexInstanceAttributesConstRef<FVector> VertexInstanceNormals = SourceMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Normal);
TVertexInstanceAttributesConstRef<FVector> VertexInstanceTangents = SourceMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Tangent);
TVertexInstanceAttributesConstRef<float> VertexInstanceBinormalSigns = SourceMeshDescription.VertexInstanceAttributes().GetAttributesRef<float>(MeshAttribute::VertexInstance::BinormalSign);
TVertexInstanceAttributesConstRef<FVector4> VertexInstanceColors = SourceMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector4>(MeshAttribute::VertexInstance::Color);
TVertexInstanceAttributesConstRef<FVector2D> VertexInstanceUVs = SourceMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
TPolygonGroupAttributesConstRef<FName> PolygonGroupMaterialSlotName = SourceMeshDescription.PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
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 = 0;
for (const FPolygonID& PolygonID : SourceMeshDescription.Polygons().GetElementIDs())
{
TriangleNumber += SourceMeshDescription.GetPolygonTriangles(PolygonID).Num();
}
DestinationRawMesh.FaceMaterialIndices.AddZeroed(TriangleNumber);
DestinationRawMesh.FaceSmoothingMasks.AddZeroed(TriangleNumber);
int32 WedgeIndexNumber = TriangleNumber * 3;
DestinationRawMesh.WedgeColors.AddZeroed(WedgeIndexNumber);
DestinationRawMesh.WedgeIndices.AddZeroed(WedgeIndexNumber);
DestinationRawMesh.WedgeTangentX.AddZeroed(WedgeIndexNumber);
DestinationRawMesh.WedgeTangentY.AddZeroed(WedgeIndexNumber);
DestinationRawMesh.WedgeTangentZ.AddZeroed(WedgeIndexNumber);
int32 ExistingUVCount = VertexInstanceUVs.GetNumIndices();
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();
const TArray<FMeshTriangle>& Triangles = SourceMeshDescription.GetPolygonTriangles(PolygonID);
for (const FMeshTriangle& MeshTriangle : Triangles)
{
if (MaterialMap.Num() > 0 && MaterialMap.Contains(PolygonGroupMaterialSlotName[PolygonGroupID]))
{
DestinationRawMesh.FaceMaterialIndices[TriangleIndex] = MaterialMap[PolygonGroupMaterialSlotName[PolygonGroupID]];
}
else
{
DestinationRawMesh.FaceMaterialIndices[TriangleIndex] = 0;
}
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 = MeshTriangle.GetVertexInstanceID(Corner);
DestinationRawMesh.WedgeColors[WedgeIndex] = FLinearColor(VertexInstanceColors[VertexInstanceID]).ToFColor(true);
DestinationRawMesh.WedgeIndices[WedgeIndex] = RemapVerts[SourceMeshDescription.GetVertexInstanceVertex(VertexInstanceID).GetValue()];
DestinationRawMesh.WedgeTangentX[WedgeIndex] = VertexInstanceTangents[VertexInstanceID];
DestinationRawMesh.WedgeTangentY[WedgeIndex] = FVector::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);
}
//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<FVector>& RawMeshVertexPositions, FMeshDescription& DestinationMeshDescription, TArray<FVertexID>& RemapVertexPosition)
{
TVertexAttributesRef<FVector> VertexPositions = DestinationMeshDescription.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);
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)
{
new(VertIndexAndZ)MeshDescriptionOperationNamespace::FIndexAndZ(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 FVector& PositionA = *(VertIndexAndZ[i].OriginalVector);
const FVector& 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] = FVertexID::Invalid;
}
for (int32 VertexIndex = 0; VertexIndex < NumVertex; ++VertexIndex)
{
int32 RealIndex = TempRemapVertexPosition[VertexIndex];
if (UniqueVertexDone[RealIndex] != FVertexID::Invalid)
{
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 FMeshDescriptionOperations::ConvertFromRawMesh(const FRawMesh& SourceRawMesh, FMeshDescription& DestinationMeshDescription, const TMap<int32, FName>& MaterialMap)
{
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<FVector> VertexInstanceNormals = DestinationMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Normal);
TVertexInstanceAttributesRef<FVector> VertexInstanceTangents = DestinationMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Tangent);
TVertexInstanceAttributesRef<float> VertexInstanceBinormalSigns = DestinationMeshDescription.VertexInstanceAttributes().GetAttributesRef<float>(MeshAttribute::VertexInstance::BinormalSign);
TVertexInstanceAttributesRef<FVector4> VertexInstanceColors = DestinationMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector4>(MeshAttribute::VertexInstance::Color);
TVertexInstanceAttributesRef<FVector2D> VertexInstanceUVs = DestinationMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2D>(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.SetNumIndices(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);
PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(*FString::Printf(TEXT("MaterialSlot_%d"), MaterialIndex));
if (MaterialMap.Contains(MaterialIndex))
{
PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = MaterialMap[MaterialIndex];
}
PolygonGroups.Add(PolygonGroupID);
MaterialIndexToPolygonGroup.Add(MaterialIndex, PolygonGroupID);
}
}
//Triangles
int32 TriangleCount = SourceRawMesh.WedgeIndices.Num() / 3;
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 = FPolygonGroupID::Invalid;
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 == FPolygonGroupID::Invalid)
{
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 ? FLinearColor::FromSRGBColor(SourceRawMesh.WedgeColors[VerticeIndex]) : FLinearColor::White;
VertexInstanceTangents[VertexInstanceID] = bHasTangents ? SourceRawMesh.WedgeTangentX[VerticeIndex] : FVector(ForceInitToZero);
VertexInstanceBinormalSigns[VertexInstanceID] = bHasTangents ? GetBasisDeterminantSign(SourceRawMesh.WedgeTangentX[VerticeIndex].GetSafeNormal(), SourceRawMesh.WedgeTangentY[VerticeIndex].GetSafeNormal(), SourceRawMesh.WedgeTangentZ[VerticeIndex].GetSafeNormal()) : 0.0f;
VertexInstanceNormals[VertexInstanceID] = bHasNormals ? SourceRawMesh.WedgeTangentZ[VerticeIndex] : FVector(ForceInitToZero);
for (int32 TextureCoordinnateIndex = 0; TextureCoordinnateIndex < NumTexCoords; ++TextureCoordinnateIndex)
{
int32 TextureCoordIndex = TextureCoordinnateRemapIndex[TextureCoordinnateIndex];
if (TextureCoordIndex == INDEX_NONE)
{
continue;
}
VertexInstanceUVs.Set(VertexInstanceID, TextureCoordIndex, SourceRawMesh.WedgeTexCoords[TextureCoordinnateIndex][VerticeIndex]);
}
}
//Create the polygon edges
TArray<FMeshDescription::FContourPoint> Contours;
for (uint32 Corner = 0; Corner < 3; ++Corner)
{
int32 ContourPointIndex = Contours.AddDefaulted();
FMeshDescription::FContourPoint& ContourPoint = Contours[ContourPointIndex];
//Find the matching edge ID
int32 CornerIndices[2];
CornerIndices[0] = (Corner + 0) % 3;
CornerIndices[1] = (Corner + 1) % 3;
FVertexID EdgeVertexIDs[2];
EdgeVertexIDs[0] = DestinationMeshDescription.GetVertexInstanceVertex(FVertexInstanceID(TriangleVertexInstanceIDs[CornerIndices[0]]));
EdgeVertexIDs[1] = DestinationMeshDescription.GetVertexInstanceVertex(FVertexInstanceID(TriangleVertexInstanceIDs[CornerIndices[1]]));
FEdgeID MatchEdgeId = DestinationMeshDescription.GetVertexPairEdge(EdgeVertexIDs[0], EdgeVertexIDs[1]);
if (MatchEdgeId == FEdgeID::Invalid)
{
MatchEdgeId = DestinationMeshDescription.CreateEdge(EdgeVertexIDs[0], EdgeVertexIDs[1]);
}
ContourPoint.EdgeID = MatchEdgeId;
ContourPoint.VertexInstanceID = FVertexInstanceID(TriangleVertexInstanceIDs[CornerIndices[0]]);
}
const FPolygonID NewPolygonID = DestinationMeshDescription.CreatePolygon(PolygonGroupID, Contours);
int32 NewTriangleIndex = DestinationMeshDescription.GetPolygonTriangles(NewPolygonID).AddDefaulted();
FMeshTriangle& NewTriangle = DestinationMeshDescription.GetPolygonTriangles(NewPolygonID)[NewTriangleIndex];
for (int32 Corner = 0; Corner < 3; ++Corner)
{
FVertexInstanceID VertexInstanceID = TriangleVertexInstanceIDs[Corner];
NewTriangle.SetVertexInstanceID(Corner, VertexInstanceID);
}
}
ConvertSmoothGroupToHardEdges(SourceRawMesh.FaceSmoothingMasks, DestinationMeshDescription);
//Create the missing normals and tangents, should we use Mikkt space for tangent???
if (!bHasNormals || !bHasTangents)
{
//DestinationMeshDescription.ComputePolygonTangentsAndNormals(0.0f);
FMeshDescriptionOperations::CreatePolygonNTB(DestinationMeshDescription, 0.0f);
//EComputeNTBsOptions ComputeNTBsOptions = (bHasNormals ? EComputeNTBsOptions::None : EComputeNTBsOptions::Normals) | (bHasTangents ? EComputeNTBsOptions::None : EComputeNTBsOptions::Tangents);
//DestinationMeshDescription.ComputeTangentsAndNormals(ComputeNTBsOptions);
//Create the missing normals and tangents
if (!bHasNormals)
{
CreateNormals(DestinationMeshDescription, ETangentOptions::BlendOverlappingNormals, false);
}
CreateMikktTangents(DestinationMeshDescription, ETangentOptions::BlendOverlappingNormals);
}
}
//////////////////////////////////////////////////////////////////////////
// Normals tangents and Bi-normals
void FMeshDescriptionOperations::CreatePolygonNTB(FMeshDescription& MeshDescription, float ComparisonThreshold)
{
const TVertexAttributesRef<FVector> VertexPositions = MeshDescription.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);
TVertexInstanceAttributesRef<FVector2D> VertexUVs = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
TPolygonAttributesRef<FVector> PolygonNormals = MeshDescription.PolygonAttributes().GetAttributesRef<FVector>(MeshAttribute::Polygon::Normal);
TPolygonAttributesRef<FVector> PolygonTangents = MeshDescription.PolygonAttributes().GetAttributesRef<FVector>(MeshAttribute::Polygon::Tangent);
TPolygonAttributesRef<FVector> PolygonBinormals = MeshDescription.PolygonAttributes().GetAttributesRef<FVector>(MeshAttribute::Polygon::Binormal);
FVertexInstanceArray& VertexInstanceArray = MeshDescription.VertexInstances();
FVertexArray& VertexArray = MeshDescription.Vertices();
FPolygonArray& PolygonArray = MeshDescription.Polygons();
for (const FPolygonID PolygonID : MeshDescription.Polygons().GetElementIDs())
{
if (!PolygonNormals[PolygonID].IsNearlyZero())
{
//By pass normal calculation if its already done
continue;
}
const TArray<FMeshTriangle>& MeshTriangles = MeshDescription.GetPolygonTriangles(PolygonID);
FVector TangentX(0.0f);
FVector TangentY(0.0f);
FVector TangentZ(0.0f);
for (const FMeshTriangle& MeshTriangle : MeshTriangles)
{
int32 UVIndex = 0;
FVector P[3];
FVector2D UVs[3];
for (int32 i = 0; i < 3; ++i)
{
const FVertexInstanceID VertexInstanceID = MeshTriangle.GetVertexInstanceID(i);
UVs[i] = VertexUVs.Get(VertexInstanceID, 0); // UV0
P[i] = VertexPositions[MeshDescription.GetVertexInstanceVertex(VertexInstanceID)];
}
const FVector Normal = ((P[1] - P[2]) ^ (P[0] - P[2])).GetSafeNormal(ComparisonThreshold);
//Check for degenerated polygons, avoid NAN
if (!Normal.IsNearlyZero(ComparisonThreshold))
{
FMatrix ParameterToLocal(
FPlane(P[1].X - P[0].X, P[1].Y - P[0].Y, P[1].Z - P[0].Z, 0),
FPlane(P[2].X - P[0].X, P[2].Y - P[0].Y, P[2].Z - P[0].Z, 0),
FPlane(P[0].X, P[0].Y, P[0].Z, 0),
FPlane(0, 0, 0, 1)
);
FMatrix ParameterToTexture(
FPlane(UVs[1].X - UVs[0].X, UVs[1].Y - UVs[0].Y, 0, 0),
FPlane(UVs[2].X - UVs[0].X, UVs[2].Y - UVs[0].Y, 0, 0),
FPlane(UVs[0].X, UVs[0].Y, 1, 0),
FPlane(0, 0, 0, 1)
);
// Use InverseSlow to catch singular matrices. Inverse can miss this sometimes.
const FMatrix TextureToLocal = ParameterToTexture.Inverse() * ParameterToLocal;
FVector TmpTangentX(0.0f);
FVector TmpTangentY(0.0f);
FVector TmpTangentZ(0.0f);
TmpTangentX = TextureToLocal.TransformVector(FVector(1, 0, 0)).GetSafeNormal();
TmpTangentY = TextureToLocal.TransformVector(FVector(0, 1, 0)).GetSafeNormal();
TmpTangentZ = Normal;
FVector::CreateOrthonormalBasis(TmpTangentX, TmpTangentY, TmpTangentZ);
TangentX += TmpTangentX;
TangentY += TmpTangentY;
TangentZ += TmpTangentZ;
}
else
{
//This will force a recompute of the normals and tangents
TangentX = FVector(0.0f);
TangentY = FVector(0.0f);
TangentZ = FVector(0.0f);
break;
}
}
TangentX.Normalize();
TangentY.Normalize();
TangentZ.Normalize();
PolygonTangents[PolygonID] = TangentX;
PolygonBinormals[PolygonID] = TangentY;
PolygonNormals[PolygonID] = TangentZ;
}
}
void FMeshDescriptionOperations::CreateNormals(FMeshDescription& MeshDescription, FMeshDescriptionOperations::ETangentOptions TangentOptions, bool bComputeTangent)
{
//For each vertex compute the normals for every connected edges that are smooth betwween 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
// @todo: provide an option to weight each contributing polygon normal according to the size of
// the angle it makes with the vertex being calculated. This means that triangulated faces whose
// internal edge meets the vertex doesn't get undue extra weight.
const TVertexInstanceAttributesRef<FVector2D> VertexUVs = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
TVertexInstanceAttributesRef<FVector> VertexNormals = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Normal);
TVertexInstanceAttributesRef<FVector> VertexTangents = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Tangent);
TVertexInstanceAttributesRef<float> VertexBinormalSigns = MeshDescription.VertexInstanceAttributes().GetAttributesRef<float>(MeshAttribute::VertexInstance::BinormalSign);
TPolygonAttributesRef<FVector> PolygonNormals = MeshDescription.PolygonAttributes().GetAttributesRef<FVector>(MeshAttribute::Polygon::Normal);
TPolygonAttributesRef<FVector> PolygonTangents = MeshDescription.PolygonAttributes().GetAttributesRef<FVector>(MeshAttribute::Polygon::Tangent);
TPolygonAttributesRef<FVector> PolygonBinormals = MeshDescription.PolygonAttributes().GetAttributesRef<FVector>(MeshAttribute::Polygon::Binormal);
TMap<FPolygonID, FVertexInfo> VertexInfoMap;
VertexInfoMap.Reserve(20);
//Iterate all vertex to compute normals for all vertex instance
for (const FVertexID VertexID : MeshDescription.Vertices().GetElementIDs())
{
VertexInfoMap.Reset();
bool bPointHasAllTangents = true;
//Fill the VertexInfoMap
for (const FEdgeID EdgeID : MeshDescription.GetVertexConnectedEdges(VertexID))
{
for (const FPolygonID PolygonID : MeshDescription.GetEdgeConnectedPolygons(EdgeID))
{
FVertexInfo& VertexInfo = VertexInfoMap.FindOrAdd(PolygonID);
int32 EdgeIndex = VertexInfo.EdgeIDs.AddUnique(EdgeID);
if (VertexInfo.PolygonID == FPolygonID::Invalid)
{
VertexInfo.PolygonID = PolygonID;
for (const FVertexInstanceID VertexInstanceID : MeshDescription.GetPolygonPerimeterVertexInstances(PolygonID))
{
if (MeshDescription.GetVertexInstanceVertex(VertexInstanceID) == VertexID)
{
VertexInfo.VertexInstanceID = VertexInstanceID;
VertexInfo.UVs = VertexUVs.Get(VertexInstanceID, 0); // UV0
bPointHasAllTangents &= !VertexNormals[VertexInstanceID].IsNearlyZero() && !VertexTangents[VertexInstanceID].IsNearlyZero();
if (bPointHasAllTangents)
{
FVector TangentX = VertexTangents[VertexInstanceID].GetSafeNormal();
FVector TangentZ = VertexNormals[VertexInstanceID].GetSafeNormal();
FVector TangentY = (FVector::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
TArray<TArray<FPolygonID>> Groups;
TArray<FPolygonID> ConsumedPolygon;
for (auto Kvp : VertexInfoMap)
{
if (ConsumedPolygon.Contains(Kvp.Key))
{
continue;
}
int32 CurrentGroupIndex = Groups.AddZeroed();
TArray<FPolygonID>& CurrentGroup = Groups[CurrentGroupIndex];
TArray<FPolygonID> PolygonQueue;
PolygonQueue.Add(Kvp.Key); //Use a queue to avoid recursive function
while (PolygonQueue.Num() > 0)
{
FPolygonID CurrentPolygonID = PolygonQueue.Pop(true);
FVertexInfo& CurrentVertexInfo = VertexInfoMap.FindOrAdd(CurrentPolygonID);
CurrentGroup.AddUnique(CurrentVertexInfo.PolygonID);
ConsumedPolygon.AddUnique(CurrentVertexInfo.PolygonID);
const TEdgeAttributesRef<bool> EdgeHardnesses = MeshDescription.EdgeAttributes().GetAttributesRef<bool>(MeshAttribute::Edge::IsHard);
for (const FEdgeID EdgeID : CurrentVertexInfo.EdgeIDs)
{
if (EdgeHardnesses[EdgeID])
{
//End of the group
continue;
}
for (const FPolygonID PolygonID : MeshDescription.GetEdgeConnectedPolygons(EdgeID))
{
if (PolygonID == CurrentVertexInfo.PolygonID)
{
continue;
}
//Add this polygon to the group
FVertexInfo& OtherVertexInfo = VertexInfoMap.FindOrAdd(PolygonID);
//Do not repeat polygons
if (!ConsumedPolygon.Contains(OtherVertexInfo.PolygonID))
{
PolygonQueue.Add(PolygonID);
}
}
}
}
}
//Smooth every connected group
ConsumedPolygon.Reset();
for (const TArray<FPolygonID>& Group : Groups)
{
//Compute tangents data
TMap<FVector2D, FVector> GroupTangent;
TMap<FVector2D, FVector> GroupBiNormal;
TArray<FVertexInstanceID> VertexInstanceInGroup;
FVector GroupNormal(0.0f);
for (const FPolygonID PolygonID : Group)
{
FVector PolyNormal = PolygonNormals[PolygonID];
FVector PolyTangent = PolygonTangents[PolygonID];
FVector PolyBinormal = PolygonBinormals[PolygonID];
ConsumedPolygon.Add(PolygonID);
VertexInstanceInGroup.Add(VertexInfoMap[PolygonID].VertexInstanceID);
if (!PolyNormal.IsNearlyZero(SMALL_NUMBER) && !PolyNormal.ContainsNaN())
{
GroupNormal += PolyNormal;
}
if (bComputeTangent)
{
const FVector2D UVs = VertexInfoMap[PolygonID].UVs;
bool CreateGroup = (!GroupTangent.Contains(UVs));
FVector& GroupTangentValue = GroupTangent.FindOrAdd(UVs);
FVector& GroupBiNormalValue = GroupBiNormal.FindOrAdd(UVs);
if (CreateGroup)
{
GroupTangentValue = FVector(0.0f);
GroupBiNormalValue = FVector(0.0f);
}
if (!PolyTangent.IsNearlyZero(SMALL_NUMBER) && !PolyTangent.ContainsNaN())
{
GroupTangentValue += PolyTangent;
}
if (!PolyBinormal.IsNearlyZero(SMALL_NUMBER) && !PolyBinormal.ContainsNaN())
{
GroupBiNormalValue += PolyBinormal;
}
}
}
//////////////////////////////////////////////////////////////////////////
//Apply the group to the Mesh
GroupNormal.Normalize();
if (bComputeTangent)
{
for (auto Kvp : GroupTangent)
{
FVector& GroupTangentValue = GroupTangent.FindOrAdd(Kvp.Key);
GroupTangentValue.Normalize();
}
for (auto Kvp : GroupBiNormal)
{
FVector& GroupBiNormalValue = GroupBiNormal.FindOrAdd(Kvp.Key);
GroupBiNormalValue.Normalize();
}
}
//Apply the average NTB on all Vertex instance
for (const FVertexInstanceID VertexInstanceID : VertexInstanceInGroup)
{
const FVector2D VertexUV = VertexUVs.Get(VertexInstanceID, 0); // UV0
if (VertexNormals[VertexInstanceID].IsNearlyZero(SMALL_NUMBER))
{
VertexNormals[VertexInstanceID] = GroupNormal;
}
if (bComputeTangent)
{
//Avoid changing the original group value
FVector GroupTangentValue = GroupTangent[VertexUV];
FVector GroupBiNormalValue = GroupBiNormal[VertexUV];
if (!VertexTangents[VertexInstanceID].IsNearlyZero(SMALL_NUMBER))
{
GroupTangentValue = VertexTangents[VertexInstanceID];
}
FVector BiNormal(0.0f);
if (!VertexNormals[VertexInstanceID].IsNearlyZero(SMALL_NUMBER) && !VertexTangents[VertexInstanceID].IsNearlyZero(SMALL_NUMBER))
{
BiNormal = FVector::CrossProduct(VertexNormals[VertexInstanceID], VertexTangents[VertexInstanceID]).GetSafeNormal() * VertexBinormalSigns[VertexInstanceID];
}
if (!BiNormal.IsNearlyZero(SMALL_NUMBER))
{
GroupBiNormalValue = BiNormal;
}
// Gram-Schmidt orthogonalization
GroupBiNormalValue -= GroupTangentValue * (GroupTangentValue | GroupBiNormalValue);
GroupBiNormalValue.Normalize();
GroupTangentValue -= VertexNormals[VertexInstanceID] * (VertexNormals[VertexInstanceID] | GroupTangentValue);
GroupTangentValue.Normalize();
GroupBiNormalValue -= VertexNormals[VertexInstanceID] * (VertexNormals[VertexInstanceID] | GroupBiNormalValue);
GroupBiNormalValue.Normalize();
//Set the value
VertexTangents[VertexInstanceID] = GroupTangentValue;
//If the BiNormal is zero set the sign to 1.0f
VertexBinormalSigns[VertexInstanceID] = GetBasisDeterminantSign(GroupTangentValue, GroupBiNormalValue, VertexNormals[VertexInstanceID]);
}
}
}
}
}
namespace MeshDescriptionMikktSpaceInterface
{
//Mikk t spce static function
int MikkGetNumFaces(const SMikkTSpaceContext* Context);
int MikkGetNumVertsOfFace(const SMikkTSpaceContext* Context, const int FaceIdx);
void MikkGetPosition(const SMikkTSpaceContext* Context, float Position[3], const int FaceIdx, const int VertIdx);
void MikkGetNormal(const SMikkTSpaceContext* Context, float Normal[3], const int FaceIdx, const int VertIdx);
void MikkSetTSpaceBasic(const SMikkTSpaceContext* Context, const float Tangent[3], const float BitangentSign, const int FaceIdx, const int VertIdx);
void MikkGetTexCoord(const SMikkTSpaceContext* Context, float UV[2], const int FaceIdx, const int VertIdx);
}
void FMeshDescriptionOperations::CreateMikktTangents(FMeshDescription& MeshDescription, FMeshDescriptionOperations::ETangentOptions TangentOptions)
{
bool bIgnoreDegenerateTriangles = (TangentOptions & FMeshDescriptionOperations::ETangentOptions::IgnoreDegenerateTriangles) != 0;
// 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;
SMikkTSpaceContext MikkTContext;
MikkTContext.m_pInterface = &MikkTInterface;
MikkTContext.m_pUserData = (void*)(&MeshDescription);
MikkTContext.m_bIgnoreDegenerates = bIgnoreDegenerateTriangles;
genTangSpaceDefault(&MikkTContext);
}
namespace MeshDescriptionMikktSpaceInterface
{
int MikkGetNumFaces(const SMikkTSpaceContext* Context)
{
FMeshDescription *MeshDescription = (FMeshDescription*)(Context->m_pUserData);
return MeshDescription->Polygons().GetArraySize();
}
int MikkGetNumVertsOfFace(const SMikkTSpaceContext* Context, const int FaceIdx)
{
// All of our meshes are triangles.
FMeshDescription *MeshDescription = (FMeshDescription*)(Context->m_pUserData);
if (MeshDescription->IsPolygonValid(FPolygonID(FaceIdx)))
{
const FMeshPolygon& Polygon = MeshDescription->GetPolygon(FPolygonID(FaceIdx));
return Polygon.PerimeterContour.VertexInstanceIDs.Num();
}
return 0;
}
void MikkGetPosition(const SMikkTSpaceContext* Context, float Position[3], const int FaceIdx, const int VertIdx)
{
FMeshDescription* MeshDescription = (FMeshDescription*)(Context->m_pUserData);
const FMeshPolygon& Polygon = MeshDescription->GetPolygon(FPolygonID(FaceIdx));
const FVertexInstanceID VertexInstanceID = Polygon.PerimeterContour.VertexInstanceIDs[VertIdx];
const FVertexID VertexID = MeshDescription->GetVertexInstanceVertex(VertexInstanceID);
const FVector& VertexPosition = MeshDescription->VertexAttributes().GetAttribute<FVector>(VertexID, MeshAttribute::Vertex::Position);
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)
{
FMeshDescription* MeshDescription = (FMeshDescription*)(Context->m_pUserData);
const FMeshPolygon& Polygon = MeshDescription->GetPolygon(FPolygonID(FaceIdx));
const FVertexInstanceID VertexInstanceID = Polygon.PerimeterContour.VertexInstanceIDs[VertIdx];
const FVector& VertexNormal = MeshDescription->VertexInstanceAttributes().GetAttribute<FVector>(VertexInstanceID, MeshAttribute::VertexInstance::Normal);
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)
{
FMeshDescription* MeshDescription = (FMeshDescription*)(Context->m_pUserData);
const FMeshPolygon& Polygon = MeshDescription->GetPolygon(FPolygonID(FaceIdx));
const FVertexInstanceID VertexInstanceID = Polygon.PerimeterContour.VertexInstanceIDs[VertIdx];
const FVector VertexTangent(Tangent[0], Tangent[1], Tangent[2]);
MeshDescription->VertexInstanceAttributes().SetAttribute<FVector>(VertexInstanceID, MeshAttribute::VertexInstance::Tangent, 0, VertexTangent);
MeshDescription->VertexInstanceAttributes().SetAttribute<float>(VertexInstanceID, MeshAttribute::VertexInstance::BinormalSign, 0, -BitangentSign);
}
void MikkGetTexCoord(const SMikkTSpaceContext* Context, float UV[2], const int FaceIdx, const int VertIdx)
{
FMeshDescription* MeshDescription = (FMeshDescription*)(Context->m_pUserData);
const FMeshPolygon& Polygon = MeshDescription->GetPolygon(FPolygonID(FaceIdx));
const FVertexInstanceID VertexInstanceID = Polygon.PerimeterContour.VertexInstanceIDs[VertIdx];
const FVector2D& TexCoord = MeshDescription->VertexInstanceAttributes().GetAttribute<FVector2D>(VertexInstanceID, MeshAttribute::VertexInstance::TextureCoordinate, 0);
UV[0] = TexCoord.X;
UV[1] = TexCoord.Y;
}
}
void FMeshDescriptionOperations::FindOverlappingCorners(FOverlappingCorners& OutOverlappingCorners, const FMeshDescription& MeshDescription, float ComparisonThreshold)
{
// @todo: this should be shared with FOverlappingCorners
const FVertexInstanceArray& VertexInstanceArray = MeshDescription.VertexInstances();
const FVertexArray& VertexArray = MeshDescription.Vertices();
const int32 NumWedges = VertexInstanceArray.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<FVector> VertexPositions = MeshDescription.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);
for (const FVertexInstanceID VertexInstanceID : VertexInstanceArray.GetElementIDs())
{
new(VertIndexAndZ)MeshDescriptionOperationNamespace::FIndexAndZ(VertexInstanceID.GetValue(), VertexPositions[MeshDescription.GetVertexInstanceVertex(VertexInstanceID)]);
}
// 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 FVector& PositionA = *(VertIndexAndZ[i].OriginalVector);
const FVector& 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<FVector> Positions;
TVertexInstanceAttributesConstRef<FVector> Normals;
TVertexInstanceAttributesRef<FVector2D> TexCoords;
const uint32 SrcChannel;
const uint32 DstChannel;
uint32 NumIndices = 0;
TArray<int32> RemapVerts;
TArray<FVector2D> FlattenedTexCoords;
FLayoutUVMeshDescriptionView(FMeshDescription& InMeshDescription, uint32 InSrcChannel, uint32 InDstChannel)
: MeshDescription(InMeshDescription)
, Positions(InMeshDescription.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position))
, Normals(InMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Normal))
, TexCoords(InMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate))
, SrcChannel(InSrcChannel)
, DstChannel(InDstChannel)
{
uint32 NumTris = 0;
for (const FPolygonID PolygonID : MeshDescription.Polygons().GetElementIDs())
{
NumTris += MeshDescription.GetPolygonTriangles(PolygonID).Num();
}
NumIndices = NumTris * 3;
FlattenedTexCoords.SetNumUninitialized(NumIndices);
RemapVerts.SetNumUninitialized(NumIndices);
int32 WedgeIndex = 0;
for (const FPolygonID PolygonID : MeshDescription.Polygons().GetElementIDs())
{
const TArray<FMeshTriangle>& Triangles = MeshDescription.GetPolygonTriangles(PolygonID);
for (const FMeshTriangle MeshTriangle : Triangles)
{
for (int32 Corner = 0; Corner < 3; ++Corner)
{
const FVertexInstanceID VertexInstanceID = MeshTriangle.GetVertexInstanceID(Corner);
FlattenedTexCoords[WedgeIndex] = TexCoords.Get(VertexInstanceID, SrcChannel);
RemapVerts[WedgeIndex] = VertexInstanceID.GetValue();
++WedgeIndex;
}
}
}
}
uint32 GetNumIndices() const override { return NumIndices; }
FVector GetPosition(uint32 Index) const override
{
FVertexInstanceID VertexInstanceID(RemapVerts[Index]);
FVertexID VertexID = MeshDescription.GetVertexInstanceVertex(VertexInstanceID);
return Positions[VertexID];
}
FVector GetNormal(uint32 Index) const override
{
FVertexInstanceID VertexInstanceID(RemapVerts[Index]);
return Normals[VertexInstanceID];
}
FVector2D 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.GetNumIndices();
if (DstChannel >= NumUVs)
{
TexCoords.SetNumIndices(DstChannel + 1);
ensure(false); // not expecting it to get here
}
}
void SetOutputTexcoord(uint32 Index, const FVector2D& Value) override
{
const FVertexInstanceID VertexInstanceID(RemapVerts[Index]);
TexCoords.Set(VertexInstanceID, DstChannel, Value);
}
};
void FMeshDescriptionOperations::CreateLightMapUVLayout(FMeshDescription& MeshDescription,
int32 SrcLightmapIndex,
int32 DstLightmapIndex,
int32 MinLightmapResolution,
ELightmapUVVersion LightmapUVVersion,
const FOverlappingCorners& OverlappingCorners)
{
FLayoutUVMeshDescriptionView MeshDescriptionView(MeshDescription, SrcLightmapIndex, DstLightmapIndex);
FLayoutUV Packer(MeshDescriptionView, MinLightmapResolution);
Packer.SetVersion(LightmapUVVersion);
Packer.FindCharts(OverlappingCorners);
bool bPackSuccess = Packer.FindBestPacking();
if (bPackSuccess)
{
Packer.CommitPackedUVs();
}
}
bool FMeshDescriptionOperations::GenerateUniqueUVsForStaticMesh(const FMeshDescription& MeshDescription, int32 TextureResolution, TArray<FVector2D>& OutTexCoords)
{
// Create a copy of original mesh (only copy necessary data)
FMeshDescription DuplicateMeshDescription(MeshDescription);
// 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;
FindOverlappingCorners(OverlappingCorners, DuplicateMeshDescription, THRESH_POINTS_ARE_SAME);
// Generate new UVs
FLayoutUVMeshDescriptionView DuplicateMeshDescriptionView(DuplicateMeshDescription, 0, 1);
FLayoutUV Packer(DuplicateMeshDescriptionView, FMath::Clamp(TextureResolution / 4, 32, 512));
Packer.FindCharts(OverlappingCorners);
bool bPackSuccess = Packer.FindBestPacking();
if (bPackSuccess)
{
Packer.CommitPackedUVs();
TVertexInstanceAttributesRef<FVector2D> VertexInstanceUVs = DuplicateMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
// Save generated UVs
check(VertexInstanceUVs.GetNumIndices() > 1);
OutTexCoords.AddZeroed(VertexInstanceUVs.GetNumElements());
int32 TextureCoordIndex = 0;
for (const FVertexInstanceID& VertexInstanceID : DuplicateMeshDescription.VertexInstances().GetElementIDs())
{
OutTexCoords[TextureCoordIndex++] = VertexInstanceUVs.Get(VertexInstanceID, 1); // UV1
}
}
return bPackSuccess;
}
bool FMeshDescriptionOperations::AddUVChannel(FMeshDescription& MeshDescription)
{
TVertexInstanceAttributesRef<FVector2D> VertexInstanceUVs = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
if (VertexInstanceUVs.GetNumIndices() >= MAX_MESH_TEXTURE_COORDS)
{
UE_LOG(LogMeshDescriptionOperations, Error, TEXT("AddUVChannel: Cannot add UV channel. Maximum number of UV channels reached (%d)."), MAX_MESH_TEXTURE_COORDS);
return false;
}
VertexInstanceUVs.SetNumIndices(VertexInstanceUVs.GetNumIndices() + 1);
return true;
}
bool FMeshDescriptionOperations::InsertUVChannel(FMeshDescription& MeshDescription, int32 UVChannelIndex)
{
TVertexInstanceAttributesRef<FVector2D> VertexInstanceUVs = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
if (UVChannelIndex < 0 || UVChannelIndex > VertexInstanceUVs.GetNumIndices())
{
UE_LOG(LogMeshDescriptionOperations, Error, TEXT("InsertUVChannel: Cannot insert UV channel. Given UV channel index %d is out of bounds."), UVChannelIndex);
return false;
}
if (VertexInstanceUVs.GetNumIndices() >= MAX_MESH_TEXTURE_COORDS)
{
UE_LOG(LogMeshDescriptionOperations, Error, TEXT("InsertUVChannel: Cannot insert UV channel. Maximum number of UV channels reached (%d)."), MAX_MESH_TEXTURE_COORDS);
return false;
}
VertexInstanceUVs.InsertIndex(UVChannelIndex);
return true;
}
bool FMeshDescriptionOperations::RemoveUVChannel(FMeshDescription& MeshDescription, int32 UVChannelIndex)
{
TVertexInstanceAttributesRef<FVector2D> VertexInstanceUVs = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
if (VertexInstanceUVs.GetNumIndices() == 1)
{
UE_LOG(LogMeshDescriptionOperations, Error, TEXT("RemoveUVChannel: Cannot remove UV channel. There must be at least one channel."));
return false;
}
if (UVChannelIndex < 0 || UVChannelIndex >= VertexInstanceUVs.GetNumIndices())
{
UE_LOG(LogMeshDescriptionOperations, Error, TEXT("RemoveUVChannel: Cannot remove UV channel. Given UV channel index %d is out of bounds."), UVChannelIndex);
return false;
}
VertexInstanceUVs.RemoveIndex(UVChannelIndex);
return true;
}
#undef LOCTEXT_NAMESPACE