// Copyright Epic Games, Inc. All Rights Reserved. #include "MuR/OpMeshPrepareLayout.h" #include "MuR/MeshPrivate.h" #include "MuR/MutableTrace.h" #include "MuR/Platform.h" #include "MuR/Mesh.h" #include "MuR/Layout.h" #include "MuR/Image.h" #include "MuR/MutableRuntimeModule.h" namespace mu { template struct TArray2D { int32 SizeX = 0; int32 SizeY = 0; TArray Data; void Init(const T& Value, int32 InSizeX, int32 InSizeY) { SizeX = InSizeX; SizeY = InSizeY; Data.Init(Value, SizeX * SizeY); } inline const T& Get(int32 X, int32 Y) const { check(X >= 0 && X < SizeX); check(Y >= 0 && Y < SizeY); return Data[SizeX * Y + X]; } inline void Set(int32 X, int32 Y, const T& Value) { check(X >= 0 && X < SizeX); check(Y >= 0 && Y < SizeY); Data[SizeX * Y + X] = Value; } }; void MeshPrepareLayout( FMesh& Mesh, const FLayout& InLayout, int32 LayoutChannel, bool bNormalizeUVs, bool bClampUVIslands, bool bEnsureAllVerticesHaveLayoutBlock, bool bUseAbsoluteBlockIds ) { MUTABLE_CPUPROFILER_SCOPE(MeshPrepareLayout); if (Mesh.GetVertexCount() == 0) { return; } TSharedPtr Layout = InLayout.Clone(); // The layout must have block ids. check(Layout->Blocks.IsEmpty() || Layout->Blocks[0].Id != FLayoutBlock::InvalidBlockId); Mesh.AddLayout(Layout); const int32 NumVertices = Mesh.GetVertexCount(); const int32 NumBlocks = Layout->GetBlockCount(); // Find block ids for each block in the grid. Calculate a grid size that contains all blocks FIntPoint LayoutGrid = Layout->GetGridSize(); FIntPoint WorkingGrid = LayoutGrid; for (const FLayoutBlock& Block : Layout->Blocks) { WorkingGrid.X = FMath::Max(WorkingGrid.X, Block.Min.X + Block.Size.X); WorkingGrid.Y = FMath::Max(WorkingGrid.Y, Block.Min.Y + Block.Size.Y); } TArray2D GridBlockBlockId; GridBlockBlockId.Init(MAX_uint16, WorkingGrid.X, WorkingGrid.Y); // TArray> BlockRects; BlockRects.SetNumUninitialized(NumBlocks); // Create an array of block index per cell TArray OverlappingBlocks; { MUTABLE_CPUPROFILER_SCOPE(CreateGrid); for (int32 BlockIndex = 0; BlockIndex < NumBlocks; ++BlockIndex) { // TODO bool bBlockHasMask = false; // GeneratedLayout.Source->Blocks[BlockIndex].Mask != nullptr; // Fill the block rect FIntVector2 Min = Layout->Blocks[BlockIndex].Min; FIntVector2 Size = Layout->Blocks[BlockIndex].Size; box& BlockRect = BlockRects[BlockIndex]; BlockRect.min[0] = float(Min.X) / float(LayoutGrid.X); BlockRect.min[1] = float(Min.Y) / float(LayoutGrid.Y); BlockRect.size[0] = float(Size.X) / float(LayoutGrid.X); BlockRect.size[1] = float(Size.Y) / float(LayoutGrid.Y); // Fill the block index per cell array // Ignore the block in this stage if it has a mask, because blocks with masks will very likely overlap other blocks if (!bBlockHasMask) { for (uint16 Y = Min.Y; Y < Min.Y + Size.Y; ++Y) { for (uint16 X = Min.X; X < Min.X + Size.X; ++X) { if (GridBlockBlockId.Get(X, Y) == MAX_uint16) { GridBlockBlockId.Set(X, Y, BlockIndex); } else { OverlappingBlocks.AddUnique(BlockIndex); } } } } } } // Notify Overlapping layout blocks if (!OverlappingBlocks.IsEmpty()) { UE_LOG(LogMutableCore, Warning, TEXT("Mesh has %d layout block overlapping in channel %d."), OverlappingBlocks.Num() + 1, LayoutChannel); } // Get the information about the texture coordinates channel int32 TexCoordsBufferIndex = -1; int32 TexCoordsChannelIndex = -1; Mesh.GetVertexBuffers().FindChannel(EMeshBufferSemantic::TexCoords, LayoutChannel, &TexCoordsBufferIndex, &TexCoordsChannelIndex); if (TexCoordsBufferIndex < 0 || TexCoordsChannelIndex < 0) { // This is actually fine when using shared materials across LODs UE_LOG(LogMutableCore, Log, TEXT("Trying to generate layout for missing UV channel %d. No layout blocks generated."), LayoutChannel); return; } check(TexCoordsBufferIndex >= 0); check(TexCoordsChannelIndex >= 0); const FMeshBufferChannel& TexCoordsChannel = Mesh.VertexBuffers.Buffers[TexCoordsBufferIndex].Channels[TexCoordsChannelIndex]; check(TexCoordsChannel.Semantic == EMeshBufferSemantic::TexCoords); uint8* TexCoordData = Mesh.GetVertexBuffers().GetBufferData(TexCoordsBufferIndex); int32 TexElemSize = Mesh.GetVertexBuffers().GetElementSize(TexCoordsBufferIndex); int32 channelOffset = TexCoordsChannel.Offset; TexCoordData += channelOffset; // Get a copy of the UVs as FVector2f to work with them. TArray TexCoords; { MUTABLE_CPUPROFILER_SCOPE(CopyUVs); TexCoords.SetNumUninitialized(NumVertices); bool bNonNormalizedUVs = false; const bool bIsOverlayLayout = Layout->GetLayoutPackingStrategy() == mu::EPackStrategy::Overlay; const uint8* pVertices = TexCoordData; for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) { FVector2f& UV = TexCoords[VertexIndex]; if (TexCoordsChannel.Format == EMeshBufferFormat::Float32) { UV = *((FVector2f*)pVertices); } else if (TexCoordsChannel.Format == EMeshBufferFormat::Float16) { const FFloat16* pUV = reinterpret_cast(pVertices); UV = FVector2f(float(pUV[0]), float(pUV[1])); } // Check that UVs are normalized. If not, clamp the values and throw a warning. if (bNormalizeUVs && !bIsOverlayLayout && (UV[0] < 0.f || UV[0] > 1.f || UV[1] < 0.f || UV[1] > 1.f)) { UV[0] = FMath::Clamp(UV[0], 0.f, 1.f); UV[1] = FMath::Clamp(UV[1], 0.f, 1.f); bNonNormalizedUVs = true; } pVertices += TexElemSize; } // Mutable does not support non-normalized UVs if (bNonNormalizedUVs && !bIsOverlayLayout) { UE_LOG(LogMutableCore, Warning, TEXT("Source mesh has non-normalized UVs index %d"), LayoutChannel); } } const uint32 MaxGridX = bNormalizeUVs ? MAX_uint32 : WorkingGrid.X - 1; const uint32 MaxGridY = bNormalizeUVs ? MAX_uint32 : WorkingGrid.Y - 1; // Allocate the per-vertex layout block data TArray LayoutData; constexpr uint16 NullBlockId = MAX_uint16 - 1; LayoutData.SetNumUninitialized(NumVertices); // Assign a block to each vertex { MUTABLE_CPUPROFILER_SCOPE(Assign); for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) { uint16 BlockIndex = NullBlockId; FVector2f UV = TexCoords[VertexIndex]; int32 VertexWorkingGridX = FMath::Clamp(LayoutGrid.X * UV[0], 0, LayoutGrid.X - 1); int32 VertexWorkingGridY = FMath::Clamp(LayoutGrid.Y * UV[1], 0, LayoutGrid.Y - 1); // First: Assign the vertices to masked blocks in order for (int32 CandidateBlockIndex = 0; CandidateBlockIndex < NumBlocks; ++CandidateBlockIndex) { // TODO const mu::FImage* Mask = nullptr; // GeneratedLayout.Source->Blocks[CandidateBlockIndex].Mask.Get(); if (Mask) { // First discard with block limits. FIntVector2 Min = Layout->Blocks[CandidateBlockIndex].Min; FIntVector2 Size = Layout->Blocks[CandidateBlockIndex].Size; bool bInBlock = (VertexWorkingGridX >= Min.X && VertexWorkingGridX < Min.X + Size.X) && (VertexWorkingGridY >= Min.Y && VertexWorkingGridY < Min.Y + Size.Y); if (bInBlock) { // TODO: This always clamps the UVs FVector2f SampleUV; SampleUV.X = FMath::Fmod(UV.X, 1.0); SampleUV.Y = FMath::Fmod(UV.Y, 1.0); FVector4f MaskValue = Mask->Sample(SampleUV); if (MaskValue.X > 0.5f) { BlockIndex = CandidateBlockIndex; break; } } } } // Second: Assign to non-masked blocks if not assigned yet if (BlockIndex == NullBlockId) { uint32 ClampedX = FMath::Min(MaxGridX, FMath::Max(0, VertexWorkingGridX)); uint32 ClampedY = FMath::Min(MaxGridY, FMath::Max(0, VertexWorkingGridY)); BlockIndex = GridBlockBlockId.Get(ClampedX, ClampedY); } LayoutData[VertexIndex] = BlockIndex; } } // Correct UVs and block assignment if necessary const int32 NumTriangles = Mesh.GetIndexCount() / 3; TArray ConflictiveTriangles; if (bClampUVIslands) { MUTABLE_CPUPROFILER_SCOPE(DetectConflictiveTriangles); UntypedMeshBufferIteratorConst ItIndices(Mesh.GetIndexBuffers(), EMeshBufferSemantic::VertexIndex); for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex) { uint32 Index0 = ItIndices.GetAsUINT32(); ++ItIndices; uint32 Index1 = ItIndices.GetAsUINT32(); ++ItIndices; uint32 Index2 = ItIndices.GetAsUINT32(); ++ItIndices; const uint16 BlockIndexV0 = LayoutData[Index0]; const uint16 BlockIndexV1 = LayoutData[Index1]; const uint16 BlockIndexV2 = LayoutData[Index2]; if (BlockIndexV0 != BlockIndexV1 || BlockIndexV0 != BlockIndexV2) { ConflictiveTriangles.Add(TriangleIndex); } } } if (bClampUVIslands && !ConflictiveTriangles.IsEmpty()) { MUTABLE_CPUPROFILER_SCOPE(ResolveConflictiveTriangles); TArray Triangles; // Vertices mapped to unique vertex index TArray CollapsedVertices; // Vertex to face map used to speed up connectivity building TMultiMap VertexToFaceMap; // Find Unique Vertices if (bClampUVIslands) { VertexToFaceMap.Reserve(NumTriangles * 3); Triangles.SetNumUninitialized(NumTriangles); MeshCreateCollapsedVertexMap(&Mesh, CollapsedVertices); } UntypedMeshBufferIteratorConst ItIndices(Mesh.GetIndexBuffers(), EMeshBufferSemantic::VertexIndex); { MUTABLE_CPUPROFILER_SCOPE(CreateTriangles); for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex) { uint32 Index0 = ItIndices.GetAsUINT32(); ++ItIndices; uint32 Index1 = ItIndices.GetAsUINT32(); ++ItIndices; uint32 Index2 = ItIndices.GetAsUINT32(); ++ItIndices; const uint16 BlockIndexV0 = LayoutData[Index0]; const uint16 BlockIndexV1 = LayoutData[Index1]; const uint16 BlockIndexV2 = LayoutData[Index2]; FTriangleInfo& Triangle = Triangles[TriangleIndex]; Triangle.Indices[0] = Index0; Triangle.Indices[1] = Index1; Triangle.Indices[2] = Index2; Triangle.CollapsedIndices[0] = CollapsedVertices[Index0]; Triangle.CollapsedIndices[1] = CollapsedVertices[Index1]; Triangle.CollapsedIndices[2] = CollapsedVertices[Index2]; Triangle.BlockIndices[0] = BlockIndexV0; Triangle.BlockIndices[1] = BlockIndexV1; Triangle.BlockIndices[2] = BlockIndexV2; Triangle.bUVsFixed = false; VertexToFaceMap.Add(Triangle.CollapsedIndices[0], TriangleIndex); VertexToFaceMap.Add(Triangle.CollapsedIndices[1], TriangleIndex); VertexToFaceMap.Add(Triangle.CollapsedIndices[2], TriangleIndex); } } // Clamp UV islands to the predominant block of each island. { MUTABLE_CPUPROFILER_SCOPE(ClampUVs); for (int32 ConflictiveTriangleIndex : ConflictiveTriangles) { FTriangleInfo& Triangle = Triangles[ConflictiveTriangleIndex]; // Skip the ones that have been fixed already if (Triangle.bUVsFixed) { continue; } // Find triangles from the same UV Island TArray TriangleIndices; GetUVIsland(Triangles, ConflictiveTriangleIndex, TriangleIndices, TexCoords, VertexToFaceMap); // Get predominant BlockId != MAX_uint16 TArray NumVerticesPerBlock; NumVerticesPerBlock.SetNumZeroed(NumBlocks); for (int32 TriangleIndex : TriangleIndices) { FTriangleInfo& OtherTriangle = Triangles[TriangleIndex]; for (int32 VertexIndex = 0; VertexIndex < 3; ++VertexIndex) { const uint16& BlockIndex = OtherTriangle.BlockIndices[VertexIndex]; if (BlockIndex != MAX_uint16) { NumVerticesPerBlock[BlockIndex]++; } } } uint16 BlockIndex = 0; uint32 CurrentMaxVertices = 0; for (int32 Index = 0; Index < NumBlocks; ++Index) { if (NumVerticesPerBlock[Index] > CurrentMaxVertices) { BlockIndex = Index; CurrentMaxVertices = NumVerticesPerBlock[Index]; } } // Get the limits of the predominant block rect const FLayoutBlock& LayoutBlock = Layout->Blocks[BlockIndex]; const float SmallNumber = 0.000001; const float MinX = ((float)LayoutBlock.Min.X) / (float)LayoutGrid.X + SmallNumber; const float MinY = ((float)LayoutBlock.Min.Y) / (float)LayoutGrid.Y + SmallNumber; const float MaxX = (((float)LayoutBlock.Size.X + LayoutBlock.Min.X) / (float)LayoutGrid.X) - 2 * SmallNumber; const float MaxY = (((float)LayoutBlock.Size.Y + LayoutBlock.Min.Y) / (float)LayoutGrid.Y) - 2 * SmallNumber; // Iterate triangles and clamp the UVs for (int32 TriangleIndex : TriangleIndices) { FTriangleInfo& OtherTriangle = Triangles[TriangleIndex]; for (int8 VertexIndex = 0; VertexIndex < 3; ++VertexIndex) { if (OtherTriangle.BlockIndices[VertexIndex] == BlockIndex) { continue; } OtherTriangle.BlockIndices[VertexIndex] = BlockIndex; // Clamp UVs to the block they are assigned to const int32 UVIndex = OtherTriangle.Indices[VertexIndex]; FVector2f& UV = TexCoords[UVIndex]; UV[0] = FMath::Clamp(UV[0], MinX, MaxX); UV[1] = FMath::Clamp(UV[1], MinY, MaxY); LayoutData[UVIndex] = BlockIndex; } OtherTriangle.bUVsFixed = true; } } } } // Warn about vertices without a block id //int32 FirstLODToIgnoreWarnings = GeneratedLayout.Source->FirstLODToIgnoreWarnings; //if (FirstLODToIgnoreWarnings == -1 || StaticMeshOptions.LODIndex < FirstLODToIgnoreWarnings) //{ // TArray UnassignedUVs; // UnassignedUVs.Reserve(NumVertices / 100); // const FVector2f* UVs = TexCoords.GetData(); // for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) // { // if (LayoutData[VertexIndex] == MAX_uint16) // { // UnassignedUVs.Add((*(UVs + VertexIndex))[0]); // UnassignedUVs.Add((*(UVs + VertexIndex))[1]); // } // } // if (!UnassignedUVs.IsEmpty()) // { // // TODO: How do we translate this to editor-time info? // UE_LOG(LogMutableCore, Warning, TEXT("Source mesh has %d vertices not assigned to any layout block in LOD %d in UVs index %d"), UnassignedUVs.Num(), LayoutChannel); // } //} { MUTABLE_CPUPROFILER_SCOPE(CreateBuffers); // Create the layout block vertex buffer uint8* LayoutBufferPtr = nullptr; { const int32 LayoutBufferIndex = Mesh.GetVertexBuffers().GetBufferCount(); Mesh.GetVertexBuffers().SetBufferCount(LayoutBufferIndex + 1); // TODO check(Layout->GetBlockCount() < MAX_uint16); const EMeshBufferSemantic LayoutSemantic = EMeshBufferSemantic::LayoutBlock; const int32 LayoutSemanticIndex = int32(LayoutChannel); const EMeshBufferFormat LayoutFormat = bUseAbsoluteBlockIds ? EMeshBufferFormat::UInt64 : EMeshBufferFormat::UInt16; const int32 LayoutComponents = 1; const int32 LayoutOffset = 0; int32 ElementSize = bUseAbsoluteBlockIds ? sizeof(uint64) : sizeof(uint16); Mesh.GetVertexBuffers().SetBuffer ( LayoutBufferIndex, ElementSize, 1, &LayoutSemantic, &LayoutSemanticIndex, &LayoutFormat, &LayoutComponents, &LayoutOffset ); LayoutBufferPtr = Mesh.GetVertexBuffers().GetBufferData(LayoutBufferIndex); } { MUTABLE_CPUPROFILER_SCOPE(LayoutAndHomogenize); for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) { FVector2f& UV = TexCoords[VertexIndex]; uint16 LayoutBlockIndex = LayoutData[VertexIndex]; if (Layout->Blocks.IsValidIndex(LayoutBlockIndex)) { uint64 LayoutBlockId = Layout->Blocks[LayoutBlockIndex].Id; UV = BlockRects[LayoutBlockIndex].Homogenize(UV); // Replace block index by the actual id of the block if (bUseAbsoluteBlockIds) { uint64* Ptr = reinterpret_cast(LayoutBufferPtr) + VertexIndex; *Ptr = LayoutBlockId; } else { uint16* Ptr = reinterpret_cast(LayoutBufferPtr) + VertexIndex; *Ptr = uint16(LayoutBlockId & 0xffff); } } else { // Map vertices without block if (bUseAbsoluteBlockIds) { uint64* Ptr = reinterpret_cast(LayoutBufferPtr) + VertexIndex; *Ptr = bEnsureAllVerticesHaveLayoutBlock ? 0 : std::numeric_limits::max(); } else { uint16* Ptr = reinterpret_cast(LayoutBufferPtr) + VertexIndex; *Ptr = bEnsureAllVerticesHaveLayoutBlock ? 0 : std::numeric_limits::max(); } } } } { MUTABLE_CPUPROFILER_SCOPE(CopyUVs); // Copy UVs if (TexCoordsChannel.Format == EMeshBufferFormat::Float32 && TexElemSize==sizeof(float)*2) { FMemory::Memcpy(TexCoordData, TexCoords.GetData(), sizeof(float) * 2 * NumVertices); } else { MUTABLE_CPUPROFILER_SCOPE(SlowPath); for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) { FVector2f& UV = TexCoords[VertexIndex]; if (TexCoordsChannel.Format == EMeshBufferFormat::Float32) { FVector2f* pUV = reinterpret_cast(TexCoordData); *pUV = UV; } else if (TexCoordsChannel.Format == EMeshBufferFormat::Float16) { FFloat16* pUV = reinterpret_cast(TexCoordData); pUV[0] = FFloat16(UV[0]); pUV[1] = FFloat16(UV[1]); } TexCoordData += TexElemSize; } } } } } }