You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
- 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]
1254 lines
52 KiB
C++
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
|