//=========== Copyright © Valve Corporation, All rights reserved. ============// // // Purpose: Mesh class UV parameterization operations. // //===========================================================================// #include "mesh.h" #include "tier1/utlbuffer.h" Vector4D PlaneFromTriangle( Vector &A, Vector &B, Vector &C ) { // Calculate normal Vector vAB = B - A; Vector vAC = C - A; Vector vNorm = -CrossProduct( vAC, vAB ); vNorm.NormalizeInPlace(); float d = DotProduct( A, vNorm ); return Vector4D( vNorm.x, vNorm.y, vNorm.z, d ); } Vector4D CMesh::PlaneFromTriangle( int nTriangle ) const { Vector A = *(Vector*)GetVertex( m_pIndices[ nTriangle * 3 ] ); Vector B = *(Vector*)GetVertex( m_pIndices[ nTriangle * 3 + 1 ] ); Vector C = *(Vector*)GetVertex( m_pIndices[ nTriangle * 3 + 2 ] ); return ::PlaneFromTriangle( A, B, C ); } int AddTriangleToChart( const CMesh &inputMesh, int nTriangle, UVChart_t *pChart, float flThreshold, CUtlVector &usedTriangles, int* pAdjacency ) { if ( usedTriangles[ nTriangle ] == true ) return 0; Vector4D vTriPlane = inputMesh.PlaneFromTriangle( nTriangle ); float flDot = DotProduct( vTriPlane.AsVector3D(), pChart->m_vPlane.AsVector3D() ); if ( flDot < flThreshold ) { return 0; } // Add this triangle to the chart pChart->m_TriangleList.AddToTail( nTriangle ); usedTriangles[ nTriangle ] = true; int nAdded = 1; // Add any adjacent triangles to the chart for ( int i=0; i<3; ++i ) { int nAdj = pAdjacency[ nTriangle * 3 + i ]; if ( nAdj != -1) { nAdded += AddTriangleToChart( inputMesh, nAdj, pChart, flThreshold, usedTriangles, pAdjacency ); } } return nAdded; } //-------------------------------------------------------------------------------------- // CreateUniqueUVParameterization // // Creates a unique parameterization for the mesh in 0..1 UV space. The mesh is assumed // to be welded and clean before this is called. //-------------------------------------------------------------------------------------- bool CreateUniqueUVParameterization( CMesh *pMeshOut, const CMesh &inputMesh, float flThreshold, int nAtlasTextureSizeX, int nAtlasTextureSizeY, float flGutterSize ) { int nMaxCharts = 10000; // Generate adjacency int *pAdjacencyBuffer = new int[ inputMesh.m_nIndexCount ]; if ( !inputMesh.CalculateAdjacency( pAdjacencyBuffer, inputMesh.m_nIndexCount ) ) { return false; } int nPosOffset = inputMesh.FindFirstAttributeOffset( VERTEX_ELEMENT_POSITION ); int nTexOffset = inputMesh.FindFirstAttributeOffset( VERTEX_ELEMENT_TEXCOORD2D_0 ); if ( nTexOffset == -1 ) { nTexOffset = inputMesh.FindFirstAttributeOffset( VERTEX_ELEMENT_TEXCOORD3D_0 ); } if ( nTexOffset == -1 || nPosOffset == -1) { Warning( "Cannot create UV parameterization without position or texcoords!\n" ); return false; } // Now go through the triangles and add them to charts based upon minimum angle between charts CUtlVector triangleIndices; CUtlVector usedTriangles; CUtlVector chartList; int nTris = inputMesh.m_nIndexCount / 3; triangleIndices.EnsureCount( nTris ); usedTriangles.EnsureCount( nTris ); for( int t=0; t 0 ) { // Select a random triangle int nTriangle = -1; for( int t=0; t -1 ) { Vector4D vTriPlane = inputMesh.PlaneFromTriangle( nTriangle ); if ( vTriPlane.AsVector3D().LengthSqr() < 0.9f ) { // degenerate tri, just get rid of it pUsedTriangles[ nTriangle ] = true; nRemaining --; } else { // create a new chart UVChart_t *pNewChart = new UVChart_t; pNewChart->m_vPlane = vTriPlane; pNewChart->m_vMinUV = Vector2D( FLT_MAX, FLT_MAX ); pNewChart->m_vMaxUV = Vector2D( -FLT_MAX, -FLT_MAX ); int nAdded = AddTriangleToChart( inputMesh, nTriangle, pNewChart, flThreshold, usedTriangles, pAdjacencyBuffer ); if ( nAdded < 1 ) { Msg( "Error: didn't add any triangles to chart: %d\n", nTriangle ); } nRemaining -= nAdded; // Add the chart to the list chartList.AddToTail( pNewChart ); if ( chartList.Count() > nMaxCharts ) { nRemaining = 0; break; } } } else { Assert( nRemaining == 0 ); nRemaining = 0; } } delete []pAdjacencyBuffer; pAdjacencyBuffer = NULL; // create a local texture vector CUtlVector atlasChartVector; CMesh tempMesh; int nTotalChartTriangles = 0; int nCharts = chartList.Count(); float flTotalArea = 0; if ( nCharts < nMaxCharts ) { // Average each chart's plane and create a new texture for each chart int nCharts = chartList.Count(); atlasChartVector.EnsureCount( nCharts ); for ( int c=0; cm_TriangleList.Count(); Vector4D vPlane(0,0,0,0); for ( int p=0; pm_TriangleList[p]; vPlane += inputMesh.PlaneFromTriangle( iTri ); } nTotalChartTriangles += nTris; Vector vNorm = vPlane.AsVector3D().Normalized(); pChart->m_vPlane = Vector4D( vNorm.x, vNorm.y, vNorm.z, 0 ); AtlasChart_t AtlasChart; AtlasChart.m_bAtlased = false; AtlasChart.m_vAtlasMin.Init( 0, 0 ); AtlasChart.m_vAtlasMax.Init( 0, 0 ); AtlasChart.m_vMaxTextureSize.Init( 0, 0 ); atlasChartVector[ c ] = AtlasChart; } // Create a new temporary mesh tempMesh.AllocateMesh( nTotalChartTriangles * 3, nTotalChartTriangles * 3, inputMesh.m_nVertexStrideFloats, inputMesh.m_pAttributes, inputMesh.m_nAttributeCount ); // loop through all charts and add the vertices and indices to the new mesh int nNewVertex = 0; flTotalArea = 0.0f; for ( int c=0; cm_vPlane.AsVector3D(); Vector vUp(0,1,0); if ( DotProduct( vNorm, vUp ) > 0.95f ) vUp = Vector(0,0,1); Vector vRight = CrossProduct( vNorm, vUp ); vRight.NormalizeInPlace(); vUp = CrossProduct( vRight, vNorm ); vUp.NormalizeInPlace(); pChart->m_nVertexStart = nNewVertex; // project the vertices onto the chart plane // and find the min and max plane boundaries int nTris = pChart->m_TriangleList.Count(); for ( int t=0; tm_TriangleList[t]; for ( int i=0; i<3; ++i ) { int nIndex = inputMesh.m_pIndices[ iTri * 3 + i ]; float *pVert = (float*)inputMesh.GetVertex( nIndex ); Vector &vPos = *( ( Vector* )( pVert + nPosOffset ) ); Vector2D vTexcoord; vTexcoord.x = DotProduct( vPos, vRight ); vTexcoord.y = DotProduct( vPos, vUp ); pChart->m_vMinUV.x = MIN( vTexcoord.x, pChart->m_vMinUV.x ); pChart->m_vMinUV.y = MIN( vTexcoord.y, pChart->m_vMinUV.y ); pChart->m_vMaxUV.x = MAX( vTexcoord.x, pChart->m_vMaxUV.x ); pChart->m_vMaxUV.y = MAX( vTexcoord.y, pChart->m_vMaxUV.y ); float *pNewVert = tempMesh.GetVertex( nNewVertex ); // New vertex CopyVertex( pNewVert, pVert, inputMesh.m_nVertexStrideFloats ); Vector2D *pNewTex = (Vector2D*)( pNewVert + nTexOffset ); *pNewTex = vTexcoord; // New index tempMesh.m_pIndices[ nNewVertex ] = nNewVertex; nNewVertex++; } } pChart->m_nVertexCount = nNewVertex - pChart->m_nVertexStart; // update size of the texture AtlasChart_t &chart = atlasChartVector[ c ]; chart.m_vMaxTextureSize.x = pChart->m_vMaxUV.x - pChart->m_vMinUV.x; chart.m_vMaxTextureSize.y = pChart->m_vMaxUV.y - pChart->m_vMinUV.y; flTotalArea += chart.m_vMaxTextureSize.x * chart.m_vMaxTextureSize.y; Vector2D vChartUVDelta = pChart->m_vMaxUV - pChart->m_vMinUV; // Normalize texture coordinates within the chart plane boundaries if ( vChartUVDelta.x == 0 || vChartUVDelta.y == 0 ) { // Zero texcoords if our chart is infintesimally small for ( int v=pChart->m_nVertexStart; vm_nVertexStart + pChart->m_nVertexCount; ++v ) { float *pNewVert = tempMesh.GetVertex( v ); Vector2D *pNewTex = (Vector2D*)( pNewVert + nTexOffset ); *pNewTex = Vector2D(0,0); } } else { for ( int v=pChart->m_nVertexStart; vm_nVertexStart + pChart->m_nVertexCount; ++v ) { float *pNewVert = tempMesh.GetVertex( v ); Vector2D *pNewTex = (Vector2D*)( pNewVert + nTexOffset ); Vector2D vNewTex = ( *pNewTex - pChart->m_vMinUV ) / vChartUVDelta; *pNewTex = vNewTex; } } } } bool bMadeAtlas = false; if ( nCharts < nMaxCharts ) { // We made a chart bMadeAtlas = true; // Create an atlas Msg( "Attempting to atlas %d charts\n", nCharts ); int nAtlasSideSize = (int)(sqrtf( flTotalArea )); int nGrowAmount = MAX( 8, nAtlasSideSize / 64 ); nAtlasSideSize -= nGrowAmount * 2; PackChartsIntoAtlas( atlasChartVector.Base(), atlasChartVector.Count(), nAtlasSideSize, nAtlasSideSize, nGrowAmount ); Vector2D vTextureSize( nAtlasTextureSizeX, nAtlasTextureSizeY ); Vector2D vGutterOffset( flGutterSize / vTextureSize.x, flGutterSize / vTextureSize.y ); // Update triangle coordinates to fit into this atlas for ( int c=0; cm_nVertexStart; vm_nVertexStart + pChart->m_nVertexCount; ++v ) { float *pNewVert = tempMesh.GetVertex( v ); Vector2D *pNewTex = (Vector2D*)( pNewVert + nTexOffset ); pNewTex->x = pNewTex->x * vAtlasUVSize.x + vShift.x; pNewTex->y = pNewTex->y * vAtlasUVSize.y + vShift.y; } } // Clean and weld the mesh float flEpsilon = 1e-6; float *pEpsilons = new float[ tempMesh.m_nVertexStrideFloats ]; for ( int e=0; em_nVertexCount; ++v ) { float *pNewVert = pMeshOut->GetVertex( v ); Vector *pPos = ( Vector* )( pNewVert + nPosOffset ); Vector2D *pNewTex = ( Vector2D* )( pNewVert + nTexOffset ); pNewTex->x = ( pPos->x - vMinBounds.x ) / vBoundsDelta.x; pNewTex->y = ( pPos->z - vMinBounds.z ) / vBoundsDelta.z; } } // Delete the charts nCharts = chartList.Count(); for ( int c=0; c