/** * Creates a FStaticMeshRenderData from a D3DXMesh * @param DestMesh Destination mesh to extract to * @param NumUVs Number of UVs * @param Elements Elements array * @return Boolean representing success or failure */ bool ConvertD3DXMeshToRawMesh( TRefCountPtr<ID3DXMesh>& D3DMesh, FRawMesh& DestMesh, int32 NumUVs ) { // Extract simplified data to LOD FUtilVertex* D3DVertices; uint16* D3DIndices; ::DWORD * D3DAttributes; D3DMesh->LockVertexBuffer(D3DLOCK_READONLY,(LPVOID*)&D3DVertices); D3DMesh->LockIndexBuffer(D3DLOCK_READONLY,(LPVOID*)&D3DIndices); D3DMesh->LockAttributeBuffer(D3DLOCK_READONLY,&D3DAttributes); int32 NumFaces = D3DMesh->GetNumFaces(); int32 NumWedges = NumFaces * 3; DestMesh.FaceMaterialIndices.Init(NumFaces); DestMesh.FaceSmoothingMasks.Init(NumFaces); DestMesh.VertexPositions.Init(NumWedges); DestMesh.WedgeIndices.Init(NumWedges); DestMesh.WedgeColors.Init(NumWedges); DestMesh.WedgeTangentX.Init(NumWedges); DestMesh.WedgeTangentY.Init(NumWedges); DestMesh.WedgeTangentZ.Init(NumWedges); for (int32 UVIndex = 0; UVIndex < NumUVs; ++UVIndex) { DestMesh.WedgeTexCoords[UVIndex].Init(NumWedges); } for(int32 I=0;I<NumFaces;I++) { // Copy smoothing mask and index from any vertex into this triangle DestMesh.FaceSmoothingMasks[I] = D3DVertices[D3DIndices[I*3+0]].SmoothingMask; DestMesh.FaceMaterialIndices[I] = D3DAttributes[I]; for(int UVs=0;UVs<NumUVs;UVs++) { DestMesh.WedgeTexCoords[UVs][I*3+0] = D3DVertices[D3DIndices[I*3+0]].UVs[UVs]; DestMesh.WedgeTexCoords[UVs][I*3+1] = D3DVertices[D3DIndices[I*3+1]].UVs[UVs]; DestMesh.WedgeTexCoords[UVs][I*3+2] = D3DVertices[D3DIndices[I*3+2]].UVs[UVs]; } for(int32 K=0;K<3;K++) { DestMesh.WedgeIndices[I*3+K] = I*3+K; DestMesh.VertexPositions[I*3+K] = D3DVertices[D3DIndices[I*3+K]].Position; DestMesh.WedgeColors[I*3+K] = D3DVertices[D3DIndices[I*3+K]].Color; DestMesh.WedgeTangentX[I*3+K] = D3DVertices[D3DIndices[I*3+K]].TangentX; DestMesh.WedgeTangentY[I*3+K] = D3DVertices[D3DIndices[I*3+K]].TangentY; DestMesh.WedgeTangentZ[I*3+K] = D3DVertices[D3DIndices[I*3+K]].TangentZ; } } D3DMesh->UnlockIndexBuffer(); D3DMesh->UnlockVertexBuffer(); D3DMesh->UnlockAttributeBuffer(); return true; }
bool FD3D9MeshUtilities::GenerateUVs( struct FRawMesh& RawMesh, uint32 TexCoordIndex, float MinChartSpacingPercent, float BorderSpacingPercent, bool bUseMaxStretch, const TArray< int32 >* InFalseEdgeIndices, uint32& MaxCharts, float& MaxDesiredStretch, FText& OutError ) { OutError = FText(); if(!IsValid()) { OutError = LOCTEXT("GenerateUVs_FailedInvalid", "GenerateUVs failed, mesh was invalid."); return false; } int32 NumTexCoords = 0; for (int32 i = 0; i < MAX_MESH_TEXTURE_COORDS; ++i) { if (RawMesh.WedgeTexCoords[i].Num() != RawMesh.WedgeIndices.Num()) { break; } NumTexCoords++; } if (TexCoordIndex > (uint32)NumTexCoords) { OutError = LOCTEXT("GenerateUVs_FailedUVs", "GenerateUVs failed, incorrect number of texcoords."); return false; } TRefCountPtr<ID3DXMesh> ChartMesh; TArray<uint32> AtlasAndChartAdjacency; TArray<int32> AtlasAndChartTriangleCharts; { const bool bUseFalseEdges = InFalseEdgeIndices != NULL; // When using false edges we don't remove degenerates as we want our incoming selected edge list to map // correctly to the D3DXMesh. const bool bRemoveDegenerateTriangles = !bUseFalseEdges; // Create a D3DXMesh for the triangles being charted. TRefCountPtr<ID3DXMesh> SourceMesh; if (!ConvertRawMeshToD3DXMesh(Device, RawMesh,bRemoveDegenerateTriangles,SourceMesh)) { OutError = LOCTEXT("GenerateUVs_FailedConvert", "GenerateUVs failed, couldn't convert to a D3DXMesh."); return false; } //generate adjacency info for the mesh, which is needed later TArray<uint32> Adjacency; GenerateAdjacency(SourceMesh,Adjacency,FFragmentedAdjacencyFilter()); // We don't clean the mesh as this can collapse vertices or delete degenerate triangles, and // we want our incoming selected edge list to map correctly to the D3DXMesh. if( !bUseFalseEdges ) { //clean the mesh TRefCountPtr<ID3DXMesh> TempMesh; TArray<uint32> CleanedAdjacency; CleanedAdjacency.AddUninitialized(SourceMesh->GetNumFaces() * 3); if( FAILED(D3DXCleanMesh( D3DXCLEAN_SIMPLIFICATION, SourceMesh, (::DWORD *)Adjacency.GetTypedData(), TempMesh.GetInitReference(), (::DWORD *)CleanedAdjacency.GetTypedData(), NULL ) ) ) { OutError = LOCTEXT("GenerateUVs_FailedClean", "GenerateUVs failed, couldn't clean mesh."); return false; } SourceMesh = TempMesh; Adjacency = CleanedAdjacency; } // Setup the D3DX "false edge" array. This is three DWORDS per face that define properties of the // face's edges. Values of -1 indicates that the edge may be used as a UV seam in a the chart. Any // other value indicates that the edge should never be a UV seam. This essentially allows us to // provide a precise list of edges to be used as UV seams in the new charts. uint32* FalseEdgeArray = NULL; TArray<uint32> FalseEdges; if( bUseFalseEdges ) { // -1 means "always use this edge as a chart UV seam" to D3DX FalseEdges.AddUninitialized( SourceMesh->GetNumFaces() * 3 ); for( int32 CurFalseEdgeIndex = 0; CurFalseEdgeIndex < (int32)SourceMesh->GetNumFaces() * 3; ++CurFalseEdgeIndex ) { FalseEdges[ CurFalseEdgeIndex ] = -1; } // For each tagged edge for( int32 CurTaggedEdgeIndex = 0; CurTaggedEdgeIndex < InFalseEdgeIndices->Num(); ++CurTaggedEdgeIndex ) { const int32 EdgeIndex = ( *InFalseEdgeIndices )[ CurTaggedEdgeIndex ]; // Mark this as a false edge by setting it to a value other than negative one FalseEdges[ EdgeIndex ] = Adjacency[ CurTaggedEdgeIndex ]; } FalseEdgeArray = (uint32*)FalseEdges.GetTypedData(); } // Partition the mesh's triangles into charts. TRefCountPtr<ID3DXBuffer> PartitionResultAdjacencyBuffer; TRefCountPtr<ID3DXBuffer> FacePartitionBuffer; HRESULT Result = D3DXUVAtlasPartition( SourceMesh, bUseMaxStretch ? 0 : MaxCharts, // Max charts (0 = use max stretch instead) MaxDesiredStretch, TexCoordIndex, (::DWORD *)Adjacency.GetTypedData(), (::DWORD *)FalseEdgeArray, // False edges NULL, // IMT data &GenerateUVsStatusCallback, 0.01f, // Callback frequency NULL, // Callback user data D3DXUVATLAS_GEODESIC_QUALITY, ChartMesh.GetInitReference(), FacePartitionBuffer.GetInitReference(), NULL, PartitionResultAdjacencyBuffer.GetInitReference(), &MaxDesiredStretch, &MaxCharts ); if (FAILED(Result)) { UE_LOG(LogD3D9MeshUtils, Warning, TEXT("D3DXUVAtlasPartition() returned %u with MaxDesiredStretch=%.2f, TexCoordIndex=%u."), Result, MaxDesiredStretch, TexCoordIndex ); OutError = LOCTEXT("GenerateUVs_FailedPartition", "GenerateUVs failed, D3DXUVAtlasPartition failed."); return false; } // Extract the chart adjacency data from the D3DX buffer into an array. for(uint32 TriangleIndex = 0;TriangleIndex < ChartMesh->GetNumFaces();TriangleIndex++) { for(int32 EdgeIndex = 0;EdgeIndex < 3;EdgeIndex++) { AtlasAndChartAdjacency.Add(*((uint32*)PartitionResultAdjacencyBuffer->GetBufferPointer()+TriangleIndex*3+EdgeIndex)); } } // Extract the triangle chart data from the D3DX buffer into an array. uint32* FacePartitionBufferPointer = (uint32*)FacePartitionBuffer->GetBufferPointer(); for(uint32 TriangleIndex = 0;TriangleIndex < ChartMesh->GetNumFaces();TriangleIndex++) { AtlasAndChartTriangleCharts.Add(*FacePartitionBufferPointer++); } // Scale the partitioned UVs down. FUtilVertex* LockedVertices; ChartMesh->LockVertexBuffer(0,(LPVOID*)&LockedVertices); for(uint32 VertexIndex = 0;VertexIndex < ChartMesh->GetNumVertices();VertexIndex++) { LockedVertices[VertexIndex].UVs[TexCoordIndex] /= 2048.0f; } ChartMesh->UnlockVertexBuffer(); } if(ChartMesh) { // Create a buffer to hold the triangle chart data. TRefCountPtr<ID3DXBuffer> MergedTriangleChartsBuffer; VERIFYD3D9RESULT(D3DXCreateBuffer( AtlasAndChartTriangleCharts.Num() * sizeof(int32), MergedTriangleChartsBuffer.GetInitReference() )); uint32* MergedTriangleChartsBufferPointer = (uint32*)MergedTriangleChartsBuffer->GetBufferPointer(); for(int32 TriangleIndex = 0;TriangleIndex < AtlasAndChartTriangleCharts.Num();TriangleIndex++) { *MergedTriangleChartsBufferPointer++ = AtlasAndChartTriangleCharts[TriangleIndex]; } const uint32 FakeTexSize = 1024; const float GutterSize = ( float )FakeTexSize * MinChartSpacingPercent * 0.01f; // Pack the charts into a unified atlas. HRESULT Result = D3DXUVAtlasPack( ChartMesh, FakeTexSize, FakeTexSize, GutterSize, TexCoordIndex, (::DWORD *)AtlasAndChartAdjacency.GetTypedData(), &GenerateUVsStatusCallback, 0.01f, // Callback frequency NULL, 0, MergedTriangleChartsBuffer ); if (FAILED(Result)) { UE_LOG(LogD3D9MeshUtils, Warning, TEXT("D3DXUVAtlasPack() returned %u."), Result ); OutError = LOCTEXT("GenerateUVs_FailedPack", "GenerateUVs failed, D3DXUVAtlasPack failed."); return false; } int32 NewNumTexCoords = FMath::Max<int32>(NumTexCoords, TexCoordIndex + 1); FRawMesh FinalMesh; if (!ConvertD3DXMeshToRawMesh(ChartMesh, FinalMesh, NewNumTexCoords)) { OutError = LOCTEXT("GenerateUVs_FailedSimple", "GenerateUVs failed, couldn't convert the simplified D3DXMesh back to a UStaticMesh."); return false; } // Scale/offset the UVs appropriately to ensure there is empty space around the border { const float BorderSize = BorderSpacingPercent * 0.01f; const float ScaleAmount = 1.0f - BorderSize * 2.0f; for( int32 CurUVIndex = 0; CurUVIndex < MAX_MESH_TEXTURE_COORDS; ++CurUVIndex ) { int32 NumWedges = FinalMesh.WedgeTexCoords[CurUVIndex].Num(); for( int32 WedgeIndex = 0; WedgeIndex < NumWedges; ++WedgeIndex ) { FVector2D& UV = FinalMesh.WedgeTexCoords[CurUVIndex][WedgeIndex]; UV.X = BorderSize + UV.X * ScaleAmount; UV.Y = BorderSize + UV.Y * ScaleAmount; } } } RawMesh = FinalMesh; } return true; }
bool FD3D9MeshUtilities::LayoutUVs( struct FRawMesh& RawMesh, uint32 TextureResolution, uint32 TexCoordIndex, FText& OutError ) { OutError = FText(); if(!IsValid() || !RawMesh.IsValid()) { OutError = LOCTEXT("LayoutUVs_FailedInvalid", "LayoutUVs failed, mesh was invalid."); return false; } int32 NumTexCoords = 0; for (int32 i = 0; i < MAX_MESH_TEXTURE_COORDS; ++i) { if (RawMesh.WedgeTexCoords[i].Num() != RawMesh.WedgeIndices.Num()) { break; } NumTexCoords++; } if (TexCoordIndex > (uint32)NumTexCoords) { OutError = LOCTEXT("LayoutUVs_FailedUVs", "LayoutUVs failed, incorrect number of texcoords."); return false; } // Sort the mesh's triangles by whether they need to be charted, or just to be packed into the atlas. FRawMesh MeshToAtlas = RawMesh; if (TexCoordIndex > 0) { MeshToAtlas.WedgeTexCoords[TexCoordIndex] = MeshToAtlas.WedgeTexCoords[0]; } TRefCountPtr<ID3DXMesh> ChartMesh; TArray<uint32> AtlasAndChartAdjacency; TArray<int32> AtlasAndChartTriangleCharts; TRefCountPtr<ID3DXMesh> MergedMesh; TArray<uint32> MergedAdjacency; TArray<int32> MergedTriangleCharts; TRefCountPtr<ID3DXMesh> AtlasOnlyMesh; TArray<uint32> AtlasOnlyAdjacency; TArray<int32> AtlasOnlyTriangleCharts; { // Create a D3DXMesh for the triangles that only need to be atlassed. const bool bRemoveDegenerateTriangles = true; if (!ConvertRawMeshToD3DXMesh(Device,MeshToAtlas,bRemoveDegenerateTriangles,AtlasOnlyMesh)) { OutError = LOCTEXT("LayoutUVs_FailedConvert", "LayoutUVs failed, couldn't convert to a D3DXMesh."); return false; } // generate mapping orientations info FLayoutUVWindingInfo WindingInfo(AtlasOnlyMesh, TexCoordIndex); // Generate adjacency for the pre-charted triangles based on their input charts. GenerateAdjacency(AtlasOnlyMesh,AtlasOnlyAdjacency,FUVChartAdjacencyFilter(TexCoordIndex), &WindingInfo); ////clean the mesh TRefCountPtr<ID3DXMesh> TempMesh; TArray<uint32> CleanedAdjacency; CleanedAdjacency.AddUninitialized(AtlasOnlyMesh->GetNumFaces() * 3); if( FAILED(D3DXCleanMesh( D3DXCLEAN_SIMPLIFICATION, AtlasOnlyMesh, (::DWORD *)AtlasOnlyAdjacency.GetTypedData(), TempMesh.GetInitReference(), (::DWORD *)CleanedAdjacency.GetTypedData(), NULL ) ) ) { OutError = LOCTEXT("LayoutUVs_FailedClean", "LayoutUVs failed, couldn't clean mesh."); return false; } // Group the pre-charted triangles into indexed charts based on their adjacency in the chart. AssignMinimalAdjacencyGroups(CleanedAdjacency,AtlasOnlyTriangleCharts); MergedMesh = TempMesh; MergedAdjacency = CleanedAdjacency; MergedTriangleCharts = AtlasOnlyTriangleCharts; } if(MergedMesh) { // Create a buffer to hold the triangle chart data. TRefCountPtr<ID3DXBuffer> MergedTriangleChartsBuffer; VERIFYD3D9RESULT(D3DXCreateBuffer( MergedTriangleCharts.Num() * sizeof(int32), MergedTriangleChartsBuffer.GetInitReference() )); uint32* MergedTriangleChartsBufferPointer = (uint32*)MergedTriangleChartsBuffer->GetBufferPointer(); for(int32 TriangleIndex = 0;TriangleIndex < MergedTriangleCharts.Num();TriangleIndex++) { *MergedTriangleChartsBufferPointer++ = MergedTriangleCharts[TriangleIndex]; } const float GutterSize = 2.0f; // Pack the charts into a unified atlas. HRESULT Result = D3DXUVAtlasPack( MergedMesh, TextureResolution, TextureResolution, GutterSize, TexCoordIndex, (::DWORD *)MergedAdjacency.GetTypedData(), NULL, 0, NULL, 0, MergedTriangleChartsBuffer ); if (FAILED(Result)) { UE_LOG(LogD3D9MeshUtils, Warning, TEXT("D3DXUVAtlasPack() returned %u."), Result ); OutError = LOCTEXT("LayoutUVs_FailedPack", "LayoutUVs failed, D3DXUVAtlasPack failed."); return false; } int32 NewNumTexCoords = FMath::Max<int32>(NumTexCoords, TexCoordIndex + 1); FRawMesh FinalMesh; if (!ConvertD3DXMeshToRawMesh(MergedMesh, FinalMesh, NewNumTexCoords)) { OutError = LOCTEXT("LayoutUVs_FailedSimple", "LayoutUVs failed, couldn't convert the simplified D3DXMesh back to a UStaticMesh."); return false; } RawMesh = FinalMesh; } return true; }