Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

392 lines
11 KiB

  1. //=========== Copyright � Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Mesh class UV parameterization operations.
  4. //
  5. //===========================================================================//
  6. #include "mesh.h"
  7. #include "tier1/utlbuffer.h"
  8. Vector4D PlaneFromTriangle( Vector &A, Vector &B, Vector &C )
  9. {
  10. // Calculate normal
  11. Vector vAB = B - A;
  12. Vector vAC = C - A;
  13. Vector vNorm = -CrossProduct( vAC, vAB );
  14. vNorm.NormalizeInPlace();
  15. float d = DotProduct( A, vNorm );
  16. return Vector4D( vNorm.x, vNorm.y, vNorm.z, d );
  17. }
  18. Vector4D CMesh::PlaneFromTriangle( int nTriangle ) const
  19. {
  20. Vector A = *(Vector*)GetVertex( m_pIndices[ nTriangle * 3 ] );
  21. Vector B = *(Vector*)GetVertex( m_pIndices[ nTriangle * 3 + 1 ] );
  22. Vector C = *(Vector*)GetVertex( m_pIndices[ nTriangle * 3 + 2 ] );
  23. return ::PlaneFromTriangle( A, B, C );
  24. }
  25. int AddTriangleToChart( const CMesh &inputMesh, int nTriangle, UVChart_t *pChart, float flThreshold, CUtlVector<bool> &usedTriangles, int* pAdjacency )
  26. {
  27. if ( usedTriangles[ nTriangle ] == true )
  28. return 0;
  29. Vector4D vTriPlane = inputMesh.PlaneFromTriangle( nTriangle );
  30. float flDot = DotProduct( vTriPlane.AsVector3D(), pChart->m_vPlane.AsVector3D() );
  31. if ( flDot < flThreshold )
  32. {
  33. return 0;
  34. }
  35. // Add this triangle to the chart
  36. pChart->m_TriangleList.AddToTail( nTriangle );
  37. usedTriangles[ nTriangle ] = true;
  38. int nAdded = 1;
  39. // Add any adjacent triangles to the chart
  40. for ( int i=0; i<3; ++i )
  41. {
  42. int nAdj = pAdjacency[ nTriangle * 3 + i ];
  43. if ( nAdj != -1)
  44. {
  45. nAdded += AddTriangleToChart( inputMesh, nAdj, pChart, flThreshold, usedTriangles, pAdjacency );
  46. }
  47. }
  48. return nAdded;
  49. }
  50. //--------------------------------------------------------------------------------------
  51. // CreateUniqueUVParameterization
  52. //
  53. // Creates a unique parameterization for the mesh in 0..1 UV space. The mesh is assumed
  54. // to be welded and clean before this is called.
  55. //--------------------------------------------------------------------------------------
  56. bool CreateUniqueUVParameterization( CMesh *pMeshOut, const CMesh &inputMesh, float flThreshold, int nAtlasTextureSizeX, int nAtlasTextureSizeY, float flGutterSize )
  57. {
  58. int nMaxCharts = 10000;
  59. // Generate adjacency
  60. int *pAdjacencyBuffer = new int[ inputMesh.m_nIndexCount ];
  61. if ( !inputMesh.CalculateAdjacency( pAdjacencyBuffer, inputMesh.m_nIndexCount ) )
  62. {
  63. return false;
  64. }
  65. int nPosOffset = inputMesh.FindFirstAttributeOffset( VERTEX_ELEMENT_POSITION );
  66. int nTexOffset = inputMesh.FindFirstAttributeOffset( VERTEX_ELEMENT_TEXCOORD2D_0 );
  67. if ( nTexOffset == -1 )
  68. {
  69. nTexOffset = inputMesh.FindFirstAttributeOffset( VERTEX_ELEMENT_TEXCOORD3D_0 );
  70. }
  71. if ( nTexOffset == -1 || nPosOffset == -1)
  72. {
  73. Warning( "Cannot create UV parameterization without position or texcoords!\n" );
  74. return false;
  75. }
  76. // Now go through the triangles and add them to charts based upon minimum angle between charts
  77. CUtlVector<int> triangleIndices;
  78. CUtlVector<bool> usedTriangles;
  79. CUtlVector<UVChart_t*> chartList;
  80. int nTris = inputMesh.m_nIndexCount / 3;
  81. triangleIndices.EnsureCount( nTris );
  82. usedTriangles.EnsureCount( nTris );
  83. for( int t=0; t<nTris; ++t )
  84. {
  85. triangleIndices[t] = t;
  86. usedTriangles[t] = false;
  87. }
  88. bool *pUsedTriangles = usedTriangles.Base();
  89. int nRemaining = nTris;
  90. while( nRemaining > 0 )
  91. {
  92. // Select a random triangle
  93. int nTriangle = -1;
  94. for( int t=0; t<nTris; ++t )
  95. {
  96. if ( pUsedTriangles[t] == false )
  97. {
  98. nTriangle = t;
  99. break;
  100. }
  101. }
  102. if ( nTriangle > -1 )
  103. {
  104. Vector4D vTriPlane = inputMesh.PlaneFromTriangle( nTriangle );
  105. if ( vTriPlane.AsVector3D().LengthSqr() < 0.9f )
  106. {
  107. // degenerate tri, just get rid of it
  108. pUsedTriangles[ nTriangle ] = true;
  109. nRemaining --;
  110. }
  111. else
  112. {
  113. // create a new chart
  114. UVChart_t *pNewChart = new UVChart_t;
  115. pNewChart->m_vPlane = vTriPlane;
  116. pNewChart->m_vMinUV = Vector2D( FLT_MAX, FLT_MAX );
  117. pNewChart->m_vMaxUV = Vector2D( -FLT_MAX, -FLT_MAX );
  118. int nAdded = AddTriangleToChart( inputMesh, nTriangle, pNewChart, flThreshold, usedTriangles, pAdjacencyBuffer );
  119. if ( nAdded < 1 )
  120. {
  121. Msg( "Error: didn't add any triangles to chart: %d\n", nTriangle );
  122. }
  123. nRemaining -= nAdded;
  124. // Add the chart to the list
  125. chartList.AddToTail( pNewChart );
  126. if ( chartList.Count() > nMaxCharts )
  127. {
  128. nRemaining = 0;
  129. break;
  130. }
  131. }
  132. }
  133. else
  134. {
  135. Assert( nRemaining == 0 );
  136. nRemaining = 0;
  137. }
  138. }
  139. delete []pAdjacencyBuffer;
  140. pAdjacencyBuffer = NULL;
  141. // create a local texture vector
  142. CUtlVector<AtlasChart_t> atlasChartVector;
  143. CMesh tempMesh;
  144. int nTotalChartTriangles = 0;
  145. int nCharts = chartList.Count();
  146. float flTotalArea = 0;
  147. if ( nCharts < nMaxCharts )
  148. {
  149. // Average each chart's plane and create a new texture for each chart
  150. int nCharts = chartList.Count();
  151. atlasChartVector.EnsureCount( nCharts );
  152. for ( int c=0; c<nCharts; ++c )
  153. {
  154. UVChart_t *pChart = chartList[c];
  155. int nTris = pChart->m_TriangleList.Count();
  156. Vector4D vPlane(0,0,0,0);
  157. for ( int p=0; p<nTris; ++p )
  158. {
  159. int iTri = pChart->m_TriangleList[p];
  160. vPlane += inputMesh.PlaneFromTriangle( iTri );
  161. }
  162. nTotalChartTriangles += nTris;
  163. Vector vNorm = vPlane.AsVector3D().Normalized();
  164. pChart->m_vPlane = Vector4D( vNorm.x, vNorm.y, vNorm.z, 0 );
  165. AtlasChart_t AtlasChart;
  166. AtlasChart.m_bAtlased = false;
  167. AtlasChart.m_vAtlasMin.Init( 0, 0 );
  168. AtlasChart.m_vAtlasMax.Init( 0, 0 );
  169. AtlasChart.m_vMaxTextureSize.Init( 0, 0 );
  170. atlasChartVector[ c ] = AtlasChart;
  171. }
  172. // Create a new temporary mesh
  173. tempMesh.AllocateMesh( nTotalChartTriangles * 3, nTotalChartTriangles * 3, inputMesh.m_nVertexStrideFloats, inputMesh.m_pAttributes, inputMesh.m_nAttributeCount );
  174. // loop through all charts and add the vertices and indices to the new mesh
  175. int nNewVertex = 0;
  176. flTotalArea = 0.0f;
  177. for ( int c=0; c<nCharts; ++c )
  178. {
  179. UVChart_t *pChart = chartList[c];
  180. Vector vNorm = pChart->m_vPlane.AsVector3D();
  181. Vector vUp(0,1,0);
  182. if ( DotProduct( vNorm, vUp ) > 0.95f )
  183. vUp = Vector(0,0,1);
  184. Vector vRight = CrossProduct( vNorm, vUp );
  185. vRight.NormalizeInPlace();
  186. vUp = CrossProduct( vRight, vNorm );
  187. vUp.NormalizeInPlace();
  188. pChart->m_nVertexStart = nNewVertex;
  189. // project the vertices onto the chart plane
  190. // and find the min and max plane boundaries
  191. int nTris = pChart->m_TriangleList.Count();
  192. for ( int t=0; t<nTris; ++t )
  193. {
  194. int iTri = pChart->m_TriangleList[t];
  195. for ( int i=0; i<3; ++i )
  196. {
  197. int nIndex = inputMesh.m_pIndices[ iTri * 3 + i ];
  198. float *pVert = (float*)inputMesh.GetVertex( nIndex );
  199. Vector &vPos = *( ( Vector* )( pVert + nPosOffset ) );
  200. Vector2D vTexcoord;
  201. vTexcoord.x = DotProduct( vPos, vRight );
  202. vTexcoord.y = DotProduct( vPos, vUp );
  203. pChart->m_vMinUV.x = MIN( vTexcoord.x, pChart->m_vMinUV.x );
  204. pChart->m_vMinUV.y = MIN( vTexcoord.y, pChart->m_vMinUV.y );
  205. pChart->m_vMaxUV.x = MAX( vTexcoord.x, pChart->m_vMaxUV.x );
  206. pChart->m_vMaxUV.y = MAX( vTexcoord.y, pChart->m_vMaxUV.y );
  207. float *pNewVert = tempMesh.GetVertex( nNewVertex );
  208. // New vertex
  209. CopyVertex( pNewVert, pVert, inputMesh.m_nVertexStrideFloats );
  210. Vector2D *pNewTex = (Vector2D*)( pNewVert + nTexOffset );
  211. *pNewTex = vTexcoord;
  212. // New index
  213. tempMesh.m_pIndices[ nNewVertex ] = nNewVertex;
  214. nNewVertex++;
  215. }
  216. }
  217. pChart->m_nVertexCount = nNewVertex - pChart->m_nVertexStart;
  218. // update size of the texture
  219. AtlasChart_t &chart = atlasChartVector[ c ];
  220. chart.m_vMaxTextureSize.x = pChart->m_vMaxUV.x - pChart->m_vMinUV.x;
  221. chart.m_vMaxTextureSize.y = pChart->m_vMaxUV.y - pChart->m_vMinUV.y;
  222. flTotalArea += chart.m_vMaxTextureSize.x * chart.m_vMaxTextureSize.y;
  223. Vector2D vChartUVDelta = pChart->m_vMaxUV - pChart->m_vMinUV;
  224. // Normalize texture coordinates within the chart plane boundaries
  225. if ( vChartUVDelta.x == 0 || vChartUVDelta.y == 0 )
  226. {
  227. // Zero texcoords if our chart is infintesimally small
  228. for ( int v=pChart->m_nVertexStart; v<pChart->m_nVertexStart + pChart->m_nVertexCount; ++v )
  229. {
  230. float *pNewVert = tempMesh.GetVertex( v );
  231. Vector2D *pNewTex = (Vector2D*)( pNewVert + nTexOffset );
  232. *pNewTex = Vector2D(0,0);
  233. }
  234. }
  235. else
  236. {
  237. for ( int v=pChart->m_nVertexStart; v<pChart->m_nVertexStart + pChart->m_nVertexCount; ++v )
  238. {
  239. float *pNewVert = tempMesh.GetVertex( v );
  240. Vector2D *pNewTex = (Vector2D*)( pNewVert + nTexOffset );
  241. Vector2D vNewTex = ( *pNewTex - pChart->m_vMinUV ) / vChartUVDelta;
  242. *pNewTex = vNewTex;
  243. }
  244. }
  245. }
  246. }
  247. bool bMadeAtlas = false;
  248. if ( nCharts < nMaxCharts )
  249. {
  250. // We made a chart
  251. bMadeAtlas = true;
  252. // Create an atlas
  253. Msg( "Attempting to atlas %d charts\n", nCharts );
  254. int nAtlasSideSize = (int)(sqrtf( flTotalArea ));
  255. int nGrowAmount = MAX( 8, nAtlasSideSize / 64 );
  256. nAtlasSideSize -= nGrowAmount * 2;
  257. PackChartsIntoAtlas( atlasChartVector.Base(), atlasChartVector.Count(), nAtlasSideSize, nAtlasSideSize, nGrowAmount );
  258. Vector2D vTextureSize( nAtlasTextureSizeX, nAtlasTextureSizeY );
  259. Vector2D vGutterOffset( flGutterSize / vTextureSize.x, flGutterSize / vTextureSize.y );
  260. // Update triangle coordinates to fit into this atlas
  261. for ( int c=0; c<nCharts; ++c )
  262. {
  263. UVChart_t *pChart = chartList[ c ];
  264. AtlasChart_t &atlasData = atlasChartVector[ c ];
  265. Vector2D vAtlasMin = ( atlasData.m_vAtlasMin ) + vGutterOffset;
  266. Vector2D vAtlasMax = ( atlasData.m_vAtlasMax ) - vGutterOffset;
  267. Vector2D vDeltaAtlas = vAtlasMax - vAtlasMin;
  268. Vector2D vDeltaBounds(1,1);
  269. Vector2D vAtlasUVSize(0,0);
  270. if ( vDeltaBounds.x != 0.0f && vDeltaBounds.y != 0.0f )
  271. vAtlasUVSize = vDeltaAtlas / vDeltaBounds;
  272. Vector2D vShift = vAtlasMin - Vector2D(0,0) * vAtlasUVSize;
  273. for ( int v=pChart->m_nVertexStart; v<pChart->m_nVertexStart + pChart->m_nVertexCount; ++v )
  274. {
  275. float *pNewVert = tempMesh.GetVertex( v );
  276. Vector2D *pNewTex = (Vector2D*)( pNewVert + nTexOffset );
  277. pNewTex->x = pNewTex->x * vAtlasUVSize.x + vShift.x;
  278. pNewTex->y = pNewTex->y * vAtlasUVSize.y + vShift.y;
  279. }
  280. }
  281. // Clean and weld the mesh
  282. float flEpsilon = 1e-6;
  283. float *pEpsilons = new float[ tempMesh.m_nVertexStrideFloats ];
  284. for ( int e=0; e<tempMesh.m_nVertexStrideFloats; ++e )
  285. {
  286. pEpsilons[ e ] = flEpsilon;
  287. }
  288. WeldVertices( pMeshOut, tempMesh, pEpsilons, tempMesh.m_nVertexStrideFloats );
  289. delete []pEpsilons;
  290. }
  291. else
  292. {
  293. // We didn't make a chart
  294. bMadeAtlas = false;
  295. // Create an atlas
  296. Msg( "Too many charts (%d), creating planar mapping\n", nCharts );
  297. // Create a planar projection
  298. Vector vMinBounds;
  299. Vector vMaxBounds;
  300. inputMesh.CalculateBounds( &vMinBounds, &vMaxBounds );
  301. // BBox delta
  302. Vector vBoundsDelta = vMaxBounds - vMinBounds;
  303. DuplicateMesh( pMeshOut, inputMesh );
  304. // Update UVs based on... shakes magic 8 ball... XZ projection, for now
  305. for ( int v=0; v<pMeshOut->m_nVertexCount; ++v )
  306. {
  307. float *pNewVert = pMeshOut->GetVertex( v );
  308. Vector *pPos = ( Vector* )( pNewVert + nPosOffset );
  309. Vector2D *pNewTex = ( Vector2D* )( pNewVert + nTexOffset );
  310. pNewTex->x = ( pPos->x - vMinBounds.x ) / vBoundsDelta.x;
  311. pNewTex->y = ( pPos->z - vMinBounds.z ) / vBoundsDelta.z;
  312. }
  313. }
  314. // Delete the charts
  315. nCharts = chartList.Count();
  316. for ( int c=0; c<nCharts; ++c )
  317. {
  318. UVChart_t *pChart = chartList[c];
  319. delete pChart;
  320. }
  321. chartList.Purge();
  322. return bMadeAtlas;
  323. }