// 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 "RenderUtils.h" #include "mikktspace.h" #include "LayoutUV.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 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 PolygonSmoothGroup; PolygonSmoothGroup.Reserve(SourceMeshDescription.Polygons().GetArraySize()); TArray ConsumedPolygons; ConsumedPolygons.AddZeroed(SourceMeshDescription.Polygons().GetArraySize()); TMap < FPolygonID, uint32> PolygonAvoidances; const TEdgeAttributeArray& EdgeHardnesses = SourceMeshDescription.EdgeAttributes().GetAttributes(MeshAttribute::Edge::IsHard); for (const FPolygonID PolygonID : SourceMeshDescription.Polygons().GetElementIDs()) { if (ConsumedPolygons[PolygonID.GetValue()]) { continue; } TArray ConnectedPolygons; TArray 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 SoftEdgeNeigbors; uint32& SmoothGroup = PolygonSmoothGroup.FindOrAdd(CurrentPolygonID); uint32 AvoidSmoothGroup = 0; uint32 NeighborSmoothGroup = 0; const uint32 LastSmoothGroupValue = (LastPolygonID == FPolygonID::Invalid) ? 0 : PolygonSmoothGroup[LastPolygonID]; TArray PolygonEdges; SourceMeshDescription.GetPolygonEdges(CurrentPolygonID, PolygonEdges); for (const FEdgeID& EdgeID : PolygonEdges) { bool bIsHardEdge = EdgeHardnesses[EdgeID]; const TArray& 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& Triangles = SourceMeshDescription.GetPolygonTriangles(PolygonID); for (const FMeshTriangle& MeshTriangle : Triangles) { DestinationRawMesh.FaceSmoothingMasks[TriangleIndex++] = PolygonSmoothValue; } } } void FMeshDescriptionOperations::ConvertSmoothGroupToHardEdges(const FRawMesh& SourceRawMesh, FMeshDescription& DestinationMeshDescription) { TEdgeAttributeArray& EdgeHardnesses = DestinationMeshDescription.EdgeAttributes().GetAttributes(MeshAttribute::Edge::IsHard); TArray ConsumedPolygons; ConsumedPolygons.AddZeroed(DestinationMeshDescription.Polygons().Num()); for (const FPolygonID PolygonID : DestinationMeshDescription.Polygons().GetElementIDs()) { if (ConsumedPolygons[PolygonID.GetValue()]) { continue; } TArray ConnectedPolygons; ConnectedPolygons.Add(PolygonID); while (ConnectedPolygons.Num() > 0) { FPolygonID CurrentPolygonID = ConnectedPolygons.Pop(true); int32 CurrentPolygonIDValue = CurrentPolygonID.GetValue(); check(SourceRawMesh.FaceSmoothingMasks.IsValidIndex(CurrentPolygonIDValue)); const uint32 ReferenceSmoothGroup = SourceRawMesh.FaceSmoothingMasks[CurrentPolygonIDValue]; TArray PolygonEdges; DestinationMeshDescription.GetPolygonEdges(CurrentPolygonID, PolygonEdges); for (const FEdgeID& EdgeID : PolygonEdges) { const bool bIsHardEdge = EdgeHardnesses[EdgeID]; if (bIsHardEdge) { continue; } const TArray& EdgeConnectedPolygons = DestinationMeshDescription.GetEdgeConnectedPolygons(EdgeID); for (const FPolygonID& EdgePolygonID : EdgeConnectedPolygons) { int32 EdgePolygonIDValue = EdgePolygonID.GetValue(); if (EdgePolygonID == CurrentPolygonID || ConsumedPolygons[EdgePolygonIDValue]) { continue; } check(SourceRawMesh.FaceSmoothingMasks.IsValidIndex(EdgePolygonIDValue)); const uint32 TestSmoothGroup = SourceRawMesh.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& MaterialMap) { DestinationRawMesh.Empty(); //Gather all array data const TVertexAttributeArray& VertexPositions = SourceMeshDescription.VertexAttributes().GetAttributes(MeshAttribute::Vertex::Position); const TVertexInstanceAttributeArray& VertexInstanceNormals = SourceMeshDescription.VertexInstanceAttributes().GetAttributes(MeshAttribute::VertexInstance::Normal); const TVertexInstanceAttributeArray& VertexInstanceTangents = SourceMeshDescription.VertexInstanceAttributes().GetAttributes(MeshAttribute::VertexInstance::Tangent); const TVertexInstanceAttributeArray& VertexInstanceBinormalSigns = SourceMeshDescription.VertexInstanceAttributes().GetAttributes(MeshAttribute::VertexInstance::BinormalSign); const TVertexInstanceAttributeArray& VertexInstanceColors = SourceMeshDescription.VertexInstanceAttributes().GetAttributes(MeshAttribute::VertexInstance::Color); const TVertexInstanceAttributeIndicesArray& VertexInstanceUVs = SourceMeshDescription.VertexInstanceAttributes().GetAttributesSet(MeshAttribute::VertexInstance::TextureCoordinate); const TPolygonGroupAttributeArray& PolygonGroupMaterialSlotName = SourceMeshDescription.PolygonGroupAttributes().GetAttributes(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); DestinationRawMesh.VertexPositions.AddZeroed(SourceMeshDescription.Vertices().Num()); TArray 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& 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.GetArrayForIndex(UVIndex)[VertexInstanceID]; } ++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& RawMeshVertexPositions, FMeshDescription& DestinationMeshDescription, TArray& RemapVertexPosition) { TVertexAttributeArray& VertexPositions = DestinationMeshDescription.VertexAttributes().GetAttributes(MeshAttribute::Vertex::Position); const int32 NumVertex = RawMeshVertexPositions.Num(); TMap TempRemapVertexPosition; TempRemapVertexPosition.Reserve(NumVertex); // Create a list of vertex Z/index pairs TArray 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 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& 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& 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 TVertexInstanceAttributeArray& VertexInstanceNormals = DestinationMeshDescription.VertexInstanceAttributes().GetAttributes(MeshAttribute::VertexInstance::Normal); TVertexInstanceAttributeArray& VertexInstanceTangents = DestinationMeshDescription.VertexInstanceAttributes().GetAttributes(MeshAttribute::VertexInstance::Tangent); TVertexInstanceAttributeArray& VertexInstanceBinormalSigns = DestinationMeshDescription.VertexInstanceAttributes().GetAttributes(MeshAttribute::VertexInstance::BinormalSign); TVertexInstanceAttributeArray& VertexInstanceColors = DestinationMeshDescription.VertexInstanceAttributes().GetAttributes(MeshAttribute::VertexInstance::Color); TVertexInstanceAttributeIndicesArray& VertexInstanceUVs = DestinationMeshDescription.VertexInstanceAttributes().GetAttributesSet(MeshAttribute::VertexInstance::TextureCoordinate); TPolygonGroupAttributeArray& PolygonGroupImportedMaterialSlotNames = DestinationMeshDescription.PolygonGroupAttributes().GetAttributes(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); int32 NumTexCoords = 0; int32 MaxTexCoords = MAX_MESH_TEXTURE_COORDS; TArray 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 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 PolygonGroups; TMap 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; } TMeshAttributeArray& Uvs = VertexInstanceUVs.GetArrayForIndex(TextureCoordIndex); Uvs[VertexInstanceID] = SourceRawMesh.WedgeTexCoords[TextureCoordinnateIndex][VerticeIndex]; } } //Create the polygon edges TArray 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, 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 TVertexAttributeArray& VertexPositions = MeshDescription.VertexAttributes().GetAttributes(MeshAttribute::Vertex::Position); TVertexInstanceAttributeArray& VertexUVs = MeshDescription.VertexInstanceAttributes().GetAttributes(MeshAttribute::VertexInstance::TextureCoordinate, 0); TPolygonAttributeArray& PolygonNormals = MeshDescription.PolygonAttributes().GetAttributes(MeshAttribute::Polygon::Normal); TPolygonAttributeArray& PolygonTangents = MeshDescription.PolygonAttributes().GetAttributes(MeshAttribute::Polygon::Tangent); TPolygonAttributeArray& PolygonBinormals = MeshDescription.PolygonAttributes().GetAttributes(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& 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[VertexInstanceID]; 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 TVertexInstanceAttributeArray& VertexUVs = MeshDescription.VertexInstanceAttributes().GetAttributes(MeshAttribute::VertexInstance::TextureCoordinate, 0); TVertexInstanceAttributeArray& VertexNormals = MeshDescription.VertexInstanceAttributes().GetAttributes(MeshAttribute::VertexInstance::Normal, 0); TVertexInstanceAttributeArray& VertexTangents = MeshDescription.VertexInstanceAttributes().GetAttributes(MeshAttribute::VertexInstance::Tangent, 0); TVertexInstanceAttributeArray& VertexBinormalSigns = MeshDescription.VertexInstanceAttributes().GetAttributes(MeshAttribute::VertexInstance::BinormalSign, 0); TPolygonAttributeArray& PolygonNormals = MeshDescription.PolygonAttributes().GetAttributes(MeshAttribute::Polygon::Normal); TPolygonAttributeArray& PolygonTangents = MeshDescription.PolygonAttributes().GetAttributes(MeshAttribute::Polygon::Tangent); TPolygonAttributeArray& PolygonBinormals = MeshDescription.PolygonAttributes().GetAttributes(MeshAttribute::Polygon::Binormal); TMap 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[VertexInstanceID]; 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> Groups; TArray ConsumedPolygon; for (auto Kvp : VertexInfoMap) { if (ConsumedPolygon.Contains(Kvp.Key)) { continue; } int32 CurrentGroupIndex = Groups.AddZeroed(); TArray& CurrentGroup = Groups[CurrentGroupIndex]; TArray 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 TEdgeAttributeArray& EdgeHardnesses = MeshDescription.EdgeAttributes().GetAttributes(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& Group : Groups) { //Compute tangents data TMap GroupTangent; TMap GroupBiNormal; TArray 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[VertexInstanceID]; 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(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(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(VertexInstanceID, MeshAttribute::VertexInstance::Tangent, 0, VertexTangent); MeshDescription->VertexInstanceAttributes().SetAttribute(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(VertexInstanceID, MeshAttribute::VertexInstance::TextureCoordinate, 0); UV[0] = TexCoord.X; UV[1] = TexCoord.Y; } } void FMeshDescriptionOperations::FindOverlappingCorners(TMultiMap& OverlappingCorners, const FMeshDescription& MeshDescription, float ComparisonThreshold) { //Empty the old data OverlappingCorners.Reset(); const FVertexInstanceArray& VertexInstanceArray = MeshDescription.VertexInstances(); const FVertexArray& VertexArray = MeshDescription.Vertices(); const int32 NumWedges = VertexInstanceArray.Num(); // Create a list of vertex Z/index pairs TArray VertIndexAndZ; VertIndexAndZ.Reserve(NumWedges); const TVertexAttributeArray& VertexPositions = MeshDescription.VertexAttributes().GetAttributes(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)) { OverlappingCorners.Add(VertIndexAndZ[i].Index, VertIndexAndZ[j].Index); OverlappingCorners.Add(VertIndexAndZ[j].Index, VertIndexAndZ[i].Index); } } } } void FMeshDescriptionOperations::CreateLightMapUVLayout(FMeshDescription& MeshDescription, int32 SrcLightmapIndex, int32 DstLightmapIndex, int32 MinLightmapResolution, ELightmapUVVersion LightmapUVVersion, const TMultiMap& OverlappingCorners) { MeshDescriptionOp::FLayoutUV Packer(MeshDescription, SrcLightmapIndex, DstLightmapIndex, MinLightmapResolution); Packer.SetVersion(LightmapUVVersion); Packer.FindCharts(OverlappingCorners); bool bPackSuccess = Packer.FindBestPacking(); if (bPackSuccess) { Packer.CommitPackedUVs(); } } bool FMeshDescriptionOperations::GenerateUniqueUVsForStaticMesh(const FMeshDescription& MeshDescription, int32 TextureResolution, TArray& 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. TMultiMap OverlappingCorners; FindOverlappingCorners(OverlappingCorners, DuplicateMeshDescription, THRESH_POINTS_ARE_SAME); // Generate new UVs MeshDescriptionOp::FLayoutUV Packer(DuplicateMeshDescription, 0, 1, FMath::Clamp(TextureResolution / 4, 32, 512)); Packer.FindCharts(OverlappingCorners); bool bPackSuccess = Packer.FindBestPacking(); if (bPackSuccess) { Packer.CommitPackedUVs(); TVertexInstanceAttributeIndicesArray& VertexInstanceUVs = DuplicateMeshDescription.VertexInstanceAttributes().GetAttributesSet(MeshAttribute::VertexInstance::TextureCoordinate); // Save generated UVs check(VertexInstanceUVs.GetNumIndices() > 1); auto& UniqueUVsArray = VertexInstanceUVs.GetArrayForIndex(1); OutTexCoords.AddZeroed(UniqueUVsArray.Num()); int32 TextureCoordIndex = 0; for (const FVertexInstanceID& VertexInstanceID : DuplicateMeshDescription.VertexInstances().GetElementIDs()) { OutTexCoords[TextureCoordIndex++] = UniqueUVsArray[VertexInstanceID]; } } return bPackSuccess; } bool FMeshDescriptionOperations::AddUVChannel(FMeshDescription& MeshDescription) { TVertexInstanceAttributeIndicesArray& VertexInstanceUVs = MeshDescription.VertexInstanceAttributes().GetAttributesSet(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.AddArray(); return true; } bool FMeshDescriptionOperations::InsertUVChannel(FMeshDescription& MeshDescription, int32 UVChannelIndex) { TVertexInstanceAttributeIndicesArray& VertexInstanceUVs = MeshDescription.VertexInstanceAttributes().GetAttributesSet(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.InsertArray(UVChannelIndex); return true; } bool FMeshDescriptionOperations::RemoveUVChannel(FMeshDescription& MeshDescription, int32 UVChannelIndex) { TVertexInstanceAttributeIndicesArray& VertexInstanceUVs = MeshDescription.VertexInstanceAttributes().GetAttributesSet(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.RemoveArray(UVChannelIndex); return true; } #undef LOCTEXT_NAMESPACE