//=========== Copyright © Valve Corporation, All rights reserved. ============// // // Purpose: Mesh class operations. // //===========================================================================// #include "mesh.h" #include "tier1/utlbuffer.h" #include "tier1/utlhash.h" // simplest mesh type - array of vec3 static CMeshVertexAttribute g_PositionAttribute = {0,VERTEX_ELEMENT_POSITION}; // CMesh - utility mesh class implementation CMesh::CMesh() : m_pVerts(NULL), m_pAttributes(NULL), m_pIndices(NULL), m_nVertexCount(0), m_nVertexStrideFloats(0), m_nAttributeCount(0), m_nIndexCount(0), m_bAllocatedMeshData(false) { } CMesh::~CMesh() { FreeAllMemory(); } // free anything we allocated void CMesh::FreeAllMemory() { if ( m_bAllocatedMeshData ) { delete[] m_pVerts; delete[] m_pAttributes; delete[] m_pIndices; } m_pVerts = NULL; m_pAttributes = NULL; m_pIndices = NULL; m_bAllocatedMeshData = false; m_nAttributeCount = 0; m_nVertexStrideFloats = 0; m_nVertexCount = 0; m_nIndexCount = 0; } void CMesh::AllocateMesh( int nVertexCount, int nIndexCount, int nVertexStride, CMeshVertexAttribute *pAttributes, int nAtrributeCount ) { FreeAllMemory(); if ( !pAttributes ) { pAttributes = &g_PositionAttribute; nAtrributeCount = 1; } m_nVertexCount = nVertexCount; m_nVertexStrideFloats = nVertexStride; m_nIndexCount = nIndexCount; m_nAttributeCount = nAtrributeCount; // allocate the mesh data, mark as allocated so it will be freed on destruct m_bAllocatedMeshData = true; m_pVerts = new float[nVertexStride * nVertexCount]; m_pIndices = new uint32[nIndexCount]; m_pAttributes = new CMeshVertexAttribute[nAtrributeCount]; for ( int i = 0; i < nAtrributeCount; i++ ) { m_pAttributes[i] = pAttributes[i]; } } void CMesh::AllocateAndCopyMesh( int nInputVertexCount, const float *pInputVerts, int nInputIndexCount, const uint32 *pInputIndices, int nVertexStride, CMeshVertexAttribute *pAttributes, int nAtrributeCount ) { AllocateMesh( nInputVertexCount, nInputIndexCount, nVertexStride, pAttributes, nAtrributeCount ); V_memcpy( m_pVerts, pInputVerts, GetTotalVertexSizeInBytes() ); V_memcpy( m_pIndices, pInputIndices, GetTotalIndexSizeInBytes() ); } void CMesh::InitExternalMesh( float *pVerts, int nVertexCount, uint32 *pIndices, int nIndexCount, int nVertexStride, CMeshVertexAttribute *pAttributes, int nAtrributeCount ) { if ( !pAttributes ) { pAttributes = &g_PositionAttribute; nAtrributeCount = 1; } FreeAllMemory(); m_bAllocatedMeshData = false; m_nVertexCount = nVertexCount; m_nVertexStrideFloats = nVertexStride; m_nIndexCount = nIndexCount; m_nAttributeCount = nAtrributeCount; m_pVerts = pVerts; m_pIndices = pIndices; m_pAttributes = pAttributes; } void CMesh::AppendMesh( const CMesh &inputMesh ) { Assert( inputMesh.m_nAttributeCount == m_nAttributeCount ); Assert( inputMesh.m_nVertexStrideFloats == m_nVertexStrideFloats ); // Find total sizes int nTotalIndices = m_nIndexCount + inputMesh.m_nIndexCount; int nTotalVertices = m_nVertexCount + inputMesh.m_nVertexCount; float *pNewVB = new float[ nTotalVertices * m_nVertexStrideFloats ]; uint32 *pNewIB = new uint32[ nTotalIndices ]; Q_memcpy( pNewVB, m_pVerts, m_nVertexCount * m_nVertexStrideFloats * sizeof( float ) ); Q_memcpy( pNewIB, m_pIndices, m_nIndexCount * sizeof( uint32 ) ); int nCurrentIndex = m_nIndexCount; // copy vertices over Q_memcpy( pNewVB + m_nVertexCount * m_nVertexStrideFloats, inputMesh.m_pVerts, inputMesh.m_nVertexCount * m_nVertexStrideFloats * sizeof( float ) ); for ( int i=0; i<inputMesh.m_nIndexCount; ++i ) { pNewIB[ nCurrentIndex ] = inputMesh.m_pIndices[ i ] + m_nVertexCount; nCurrentIndex ++; } delete []m_pVerts; delete []m_pIndices; m_pVerts = pNewVB; m_pIndices = pNewIB; m_nVertexCount = nTotalVertices; m_nIndexCount = nTotalIndices; } bool CMesh::CalculateBounds( Vector *pMinOut, Vector *pMaxOut, int nStartVertex, int nVertexCount ) const { Assert( pMinOut && pMaxOut ); Vector vMin( FLT_MAX, FLT_MAX, FLT_MAX ); Vector vMax( -FLT_MAX, -FLT_MAX, -FLT_MAX ); int nPosOffset = FindFirstAttributeOffset( VERTEX_ELEMENT_POSITION ); if ( nPosOffset < 0 ) return false; float *pPositions = ( m_pVerts + nStartVertex * m_nVertexStrideFloats + nPosOffset ); if ( nVertexCount == 0 ) nVertexCount = m_nVertexCount; for ( int v=0; v<nVertexCount; ++v ) { vMin.x = MIN( pPositions[0], vMin.x ); vMin.y = MIN( pPositions[1], vMin.y ); vMin.z = MIN( pPositions[2], vMin.z ); vMax.x = MAX( pPositions[0], vMax.x ); vMax.y = MAX( pPositions[1], vMax.y ); vMax.z = MAX( pPositions[2], vMax.z ); pPositions += m_nVertexStrideFloats; } *pMinOut = vMin; *pMaxOut = vMax; return true; } struct EdgeHash_t { inline bool operator==( const EdgeHash_t& src ) const { return src.m_nV0 == m_nV0 && src.m_nV1 == m_nV1; } int m_nV0; int m_nV1; int m_nTri0; int m_nTri1; }; // Fills in an adjacency array with the 3 edge adjacent triangles for each triangle in the array. // The input pAdjacencyOut must be at least 3 * nFaces => ( 3 * m_nIndexCount / 3 ) => m_nIndexCount in size. // Any boundary edges are given an edge adjacent triangle index of -1. bool CMesh::CalculateAdjacency( int *pAdjacencyOut, int nSizeAdjacencyOut ) const { Assert( pAdjacencyOut ); if ( nSizeAdjacencyOut != m_nIndexCount ) return false; CUtlScalarHash<EdgeHash_t> edgeHash; edgeHash.Init( m_nIndexCount * 2 ); int nFaces = m_nIndexCount / 3; int nIndex = 0; for ( int f=0; f<nFaces; ++f ) { for ( int i=0; i<3; ++i ) { int nV0 = m_pIndices[ nIndex + ( i ) ]; int nV1 = m_pIndices[ nIndex + ( i + 1 ) % 3 ]; if ( nV1 < nV0 ) { Swap( nV1, nV0 ); } EdgeHash_t tmp; tmp.m_nV0 = nV0; tmp.m_nV1 = nV1; tmp.m_nTri0 = f; tmp.m_nTri1 = -1; uint nHashKey = VertHashKey( nV0, nV1 ); UtlHashFastHandle_t edgeHashIndex = edgeHash.Find( nHashKey, tmp ); if ( edgeHash.InvalidHandle() == edgeHashIndex ) { edgeHash.Insert( nHashKey, tmp ); } else { EdgeHash_t &edge = edgeHash.Element( edgeHashIndex ); edge.m_nTri1 = f; } } nIndex += 3; } // Now that we have an edge datastructure, fill out edge-adjacency nIndex = 0; for ( int f=0; f<nFaces; ++f ) { for ( int i=0; i<3; ++i ) { int nV0 = m_pIndices[ nIndex + ( i ) ]; int nV1 = m_pIndices[ nIndex + ( i + 1 ) % 3 ]; if ( nV1 < nV0 ) { Swap( nV1, nV0 ); } EdgeHash_t tmp; tmp.m_nV0 = nV0; tmp.m_nV1 = nV1; tmp.m_nTri0 = -1; tmp.m_nTri1 = -1; uint nHashKey = VertHashKey( nV0, nV1 ); UtlHashFastHandle_t edgeHashIndex = edgeHash.Find( nHashKey, tmp ); Assert( edgeHash.InvalidHandle() != edgeHashIndex ); EdgeHash_t &edge = edgeHash.Element( edgeHashIndex ); if ( edge.m_nTri0 == f ) { pAdjacencyOut[ nIndex + i ] = edge.m_nTri1; } else { pAdjacencyOut[ nIndex + i ] = edge.m_nTri0; } } nIndex += 3; } return true; } // Fills in a list of all faces that contain that particular vertex. The input pFacesPerVertex, must // contain at least m_nVertexCount elements of type CUtlLinkedList<int>. bool CMesh::CalculateIndicentFacesForVertices( CUtlLinkedList<int> *pFacesPerVertex, int nFacesPerVertexSize ) const { Assert( pFacesPerVertex ); if ( nFacesPerVertexSize != m_nVertexCount ) return false; int nFaces = m_nIndexCount / 3; int nIndex = 0; for ( int f=0; f<nFaces; ++f ) { int v0 = m_pIndices[ nIndex ]; nIndex++; int v1 = m_pIndices[ nIndex ]; nIndex++; int v2 = m_pIndices[ nIndex ]; nIndex++; pFacesPerVertex[ v0 ].AddToTail( f ); pFacesPerVertex[ v1 ].AddToTail( f ); pFacesPerVertex[ v2 ].AddToTail( f ); } return true; } struct InputDataForVertexElement_t { char *m_pSemanticName; int m_nSemanticIndex; }; InputDataForVertexElement_t g_pElementData[] = { { "POSITION", 0 }, // VERTEX_ELEMENT_POSITION = 0, { "POSITION", 0 }, // VERTEX_ELEMENT_POSITION4D = 1, { "NORMAL", 0 }, // VERTEX_ELEMENT_NORMAL = 2, { "NORMAL", 0 }, // VERTEX_ELEMENT_NORMAL4D = 3, { "COLOR", 0 }, // VERTEX_ELEMENT_COLOR = 4, { "SPECULAR", 0 }, // VERTEX_ELEMENT_SPECULAR = 5, { "TANGENT", 0 }, // VERTEX_ELEMENT_TANGENT_S = 6, { "BINORMAL", 0 }, // VERTEX_ELEMENT_TANGENT_T = 7, { "WRINKLE", 0 }, // VERTEX_ELEMENT_WRINKLE = 8, { "BLENDINDICES", 0 }, // VERTEX_ELEMENT_BONEINDEX = 9, { "BLENDWEIGHT", 0 }, // VERTEX_ELEMENT_BONEWEIGHTS1 = 10, { "BLENDWEIGHT", 1 }, // VERTEX_ELEMENT_BONEWEIGHTS2 = 11, { "BLENDWEIGHT", 2 }, // VERTEX_ELEMENT_BONEWEIGHTS3 = 12, { "BLENDWEIGHT", 3 }, // VERTEX_ELEMENT_BONEWEIGHTS4 = 13, { "TEXCOORD", 8 }, // VERTEX_ELEMENT_USERDATA1 = 14, { "TEXCOORD", 9 }, // VERTEX_ELEMENT_USERDATA2 = 15, { "TEXCOORD", 10 }, // VERTEX_ELEMENT_USERDATA3 = 16, { "TEXCOORD", 11 }, // VERTEX_ELEMENT_USERDATA4 = 17, { "TEXCOORD", 0 }, // VERTEX_ELEMENT_TEXCOORD1D_0 = 18, { "TEXCOORD", 1 }, // VERTEX_ELEMENT_TEXCOORD1D_1 = 19, { "TEXCOORD", 2 }, // VERTEX_ELEMENT_TEXCOORD1D_2 = 20, { "TEXCOORD", 3 }, // VERTEX_ELEMENT_TEXCOORD1D_3 = 21, { "TEXCOORD", 4 }, // VERTEX_ELEMENT_TEXCOORD1D_4 = 22, { "TEXCOORD", 5 }, // VERTEX_ELEMENT_TEXCOORD1D_5 = 23, { "TEXCOORD", 6 }, // VERTEX_ELEMENT_TEXCOORD1D_6 = 24, { "TEXCOORD", 7 }, // VERTEX_ELEMENT_TEXCOORD1D_7 = 25, { "TEXCOORD", 0 }, // VERTEX_ELEMENT_TEXCOORD2D_0 = 26, { "TEXCOORD", 1 }, // VERTEX_ELEMENT_TEXCOORD2D_1 = 27, { "TEXCOORD", 2 }, // VERTEX_ELEMENT_TEXCOORD2D_2 = 28, { "TEXCOORD", 3 }, // VERTEX_ELEMENT_TEXCOORD2D_3 = 29, { "TEXCOORD", 4 }, // VERTEX_ELEMENT_TEXCOORD2D_4 = 30, { "TEXCOORD", 5 }, // VERTEX_ELEMENT_TEXCOORD2D_5 = 31, { "TEXCOORD", 6 }, // VERTEX_ELEMENT_TEXCOORD2D_6 = 32, { "TEXCOORD", 7 }, // VERTEX_ELEMENT_TEXCOORD2D_7 = 33, { "TEXCOORD", 0 }, // VERTEX_ELEMENT_TEXCOORD3D_0 = 34, { "TEXCOORD", 1 }, // VERTEX_ELEMENT_TEXCOORD3D_1 = 35, { "TEXCOORD", 2 }, // VERTEX_ELEMENT_TEXCOORD3D_2 = 36, { "TEXCOORD", 3 }, // VERTEX_ELEMENT_TEXCOORD3D_3 = 37, { "TEXCOORD", 4 }, // VERTEX_ELEMENT_TEXCOORD3D_4 = 38, { "TEXCOORD", 5 }, // VERTEX_ELEMENT_TEXCOORD3D_5 = 39, { "TEXCOORD", 6 }, // VERTEX_ELEMENT_TEXCOORD3D_6 = 40, { "TEXCOORD", 7 }, // VERTEX_ELEMENT_TEXCOORD3D_7 = 41, { "TEXCOORD", 0 }, // VERTEX_ELEMENT_TEXCOORD4D_0 = 42, { "TEXCOORD", 1 }, // VERTEX_ELEMENT_TEXCOORD4D_1 = 43, { "TEXCOORD", 2 }, // VERTEX_ELEMENT_TEXCOORD4D_2 = 44, { "TEXCOORD", 3 }, // VERTEX_ELEMENT_TEXCOORD4D_3 = 45, { "TEXCOORD", 4 }, // VERTEX_ELEMENT_TEXCOORD4D_4 = 46, { "TEXCOORD", 5 }, // VERTEX_ELEMENT_TEXCOORD4D_5 = 47, { "TEXCOORD", 6 }, // VERTEX_ELEMENT_TEXCOORD4D_6 = 48, { "TEXCOORD", 7 }, // VERTEX_ELEMENT_TEXCOORD4D_7 = 49, }; ColorFormat_t GetColorFormatForVertexElement( VertexElement_t element ) { int nBytes = GetVertexElementSize( element, VERTEX_COMPRESSION_NONE ); if ( nBytes % sizeof( float ) != 0 ) { return COLOR_FORMAT_UNKNOWN; } int nFloats = nBytes / sizeof( float ); switch ( nFloats ) { case 1: return COLOR_FORMAT_R32_FLOAT; case 2: return COLOR_FORMAT_R32G32_FLOAT; case 3: return COLOR_FORMAT_R32G32B32_FLOAT; case 4: return COLOR_FORMAT_R32G32B32A32_FLOAT; } return COLOR_FORMAT_UNKNOWN; } bool CMesh::CalculateInputLayoutFromAttributes( RenderInputLayoutField_t *pOutFields, int *pInOutNumFields ) const { if ( *pInOutNumFields < m_nAttributeCount ) return false; for ( int a=0; a<m_nAttributeCount; ++a ) { InputDataForVertexElement_t elementData = g_pElementData[ m_pAttributes[ a ].m_nType ]; pOutFields[ a ].m_Format = GetColorFormatForVertexElement( m_pAttributes[ a ].m_nType ); pOutFields[ a ].m_nInstanceStepRate = 0; pOutFields[ a ].m_nOffset = m_pAttributes[ a ].m_nOffsetFloats * sizeof( float ); pOutFields[ a ].m_nSemanticIndex = elementData.m_nSemanticIndex; pOutFields[ a ].m_nSlot = 0; pOutFields[ a ].m_nSlotType = RENDER_SLOT_PER_VERTEX; Q_strncpy( pOutFields[ a ].m_pSemanticName, elementData.m_pSemanticName, RENDER_INPUT_LAYOUT_FIELD_SEMANTIC_NAME_SIZE ); } *pInOutNumFields = m_nAttributeCount; return true; } int CMesh::FindFirstAttributeOffset( VertexElement_t nType ) const { for ( int a=0; a<m_nAttributeCount; ++a ) { if ( m_pAttributes[ a ].m_nType == nType ) { return m_pAttributes[ a ].m_nOffsetFloats; } } return -1; } void CMesh::RestrideVertexBuffer( int nNewStrideFloats ) { float *pNewMemory = new float[ nNewStrideFloats * m_nVertexCount ]; int nMinStride = MIN( nNewStrideFloats, m_nVertexStrideFloats ) * sizeof( float ); float *pNewStart = pNewMemory; for ( int i=0; i<m_nVertexCount; ++i ) { Q_memcpy( pNewStart, GetVertex( i ), nMinStride ); pNewStart += nNewStrideFloats; } delete []m_pVerts; m_pVerts = pNewMemory; m_nVertexStrideFloats = nNewStrideFloats; m_bAllocatedMeshData = true; } void CMesh::AddAttributes( CMeshVertexAttribute *pAttributes, int nAttributeCount ) { Assert( nAttributeCount ); CMeshVertexAttribute *pNewAttributes = new CMeshVertexAttribute[ m_nAttributeCount + nAttributeCount ]; for ( int a=0; a<m_nAttributeCount; ++a ) { pNewAttributes[ a ] = m_pAttributes[ a ]; } int nNewStrideFloats = m_nVertexStrideFloats; for ( int a=0; a<nAttributeCount; ++a ) { nNewStrideFloats = MAX( pAttributes[ a ].m_nOffsetFloats + GetVertexElementSize( pAttributes[ a ].m_nType, VERTEX_COMPRESSION_NONE ) / (int)sizeof( float ), nNewStrideFloats ); pNewAttributes[ m_nAttributeCount + a ] = pAttributes[ a ]; } delete []m_pAttributes; m_pAttributes = pNewAttributes; m_nAttributeCount += nAttributeCount; if ( nNewStrideFloats > m_nVertexStrideFloats ) { RestrideVertexBuffer( nNewStrideFloats ); } } typedef CUtlVector<int> CIntVector; void CMesh::CalculateTangents() { int nPositionOffset = FindFirstAttributeOffset( VERTEX_ELEMENT_POSITION ); int nNormalOffset = FindFirstAttributeOffset( VERTEX_ELEMENT_NORMAL ); int nTexOffset = FindFirstAttributeOffset( VERTEX_ELEMENT_TEXCOORD2D_0 ); if ( nTexOffset == -1 ) nTexOffset = FindFirstAttributeOffset( VERTEX_ELEMENT_TEXCOORD3D_0 ); if ( nPositionOffset == -1 || nTexOffset == -1 || nNormalOffset == -1 ) { Msg( "Need valid position, normal, and texcoord when creating tangent frames!\n" ); return; } // Look for a space to store tangents int nTangentOffset = FindFirstAttributeOffset( VERTEX_ELEMENT_TANGENT_WITH_FLIP ); if ( nTangentOffset == -1 ) { nTangentOffset = m_nVertexStrideFloats; // Add a tangent CMeshVertexAttribute attribute; attribute.m_nOffsetFloats = m_nVertexStrideFloats; attribute.m_nType = VERTEX_ELEMENT_TANGENT_WITH_FLIP; AddAttributes( &attribute, 1 ); } // Calculate tangent ( pulled from studiomdl ). We've left it this way for now to keep any weirdness from studiomdl that we've come to rely on. // In the future we should remove the vertToFaceMap and just accumulate tangents inplace in the vertices. // TODO: fix this function to iterate over quads as well as triangles int nIndicesPerFace = 3; int nFaces = m_nIndexCount / nIndicesPerFace; int nMaxIter = nIndicesPerFace; CUtlVector<CIntVector> vertToFaceMap; vertToFaceMap.AddMultipleToTail( m_nVertexCount ); int index = 0; uint32 *pIndices = m_pIndices; for( int faceID = 0; faceID < nFaces; faceID++ ) { for ( int i=0; i<nMaxIter; ++i ) { vertToFaceMap[ pIndices[ index + i ] ].AddToTail( faceID ); } index += nIndicesPerFace; } CUtlVector<Vector> faceSVect; CUtlVector<Vector> faceTVect; faceSVect.AddMultipleToTail( nFaces ); faceTVect.AddMultipleToTail( nFaces ); index = 0; for ( int f=0; f<nFaces; ++f ) { Vector vPos[3]; Vector2D vTex[3]; for ( int i=0; i<nMaxIter; ++i ) { float *pVertex = GetVertex( pIndices[ index + i ] ); vPos[i] = *( ( Vector* )( pVertex + nPositionOffset ) ); vTex[i] = *( ( Vector2D* )( pVertex + nTexOffset ) ); } CalcTriangleTangentSpace( vPos[0], vPos[1], vPos[2], vTex[0], vTex[1], vTex[2], faceSVect[f], faceTVect[f] ); index += nIndicesPerFace; } // Calculate an average tangent space for each vertex. for( int vertID = 0; vertID < m_nVertexCount; vertID++ ) { float *pVertex = GetVertex( vertID ); const Vector &normal = *( ( Vector* )( pVertex + nNormalOffset ) ); Vector4D &finalSVect = *( ( Vector4D* )( pVertex + nTangentOffset ) ); Vector sVect, tVect; sVect.Init( 0.0f, 0.0f, 0.0f ); tVect.Init( 0.0f, 0.0f, 0.0f ); for( int faceID = 0; faceID < vertToFaceMap[vertID].Count(); faceID++ ) { sVect += faceSVect[vertToFaceMap[vertID][faceID]]; tVect += faceTVect[vertToFaceMap[vertID][faceID]]; } // Make an orthonormal system. // Need to check if we are left or right handed. Vector tmpVect; CrossProduct( sVect, tVect, tmpVect ); bool leftHanded = DotProduct( tmpVect, normal ) < 0.0f; if( !leftHanded ) { CrossProduct( normal, sVect, tVect ); CrossProduct( tVect, normal, sVect ); VectorNormalize( sVect ); VectorNormalize( tVect ); finalSVect[0] = sVect[0]; finalSVect[1] = sVect[1]; finalSVect[2] = sVect[2]; finalSVect[3] = 1.0f; } else { CrossProduct( sVect, normal, tVect ); CrossProduct( normal, tVect, sVect ); VectorNormalize( sVect ); VectorNormalize( tVect ); finalSVect[0] = sVect[0]; finalSVect[1] = sVect[1]; finalSVect[2] = sVect[2]; finalSVect[3] = -1.0f; } } } //-------------------------------------------------------------------------------------- // Calculates an unnormalized tangent space //-------------------------------------------------------------------------------------- #define SMALL_FLOAT 1e-12 void CalcTriangleTangentSpaceL( const Vector &p0, const Vector &p1, const Vector &p2, const Vector2D &t0, const Vector2D &t1, const Vector2D& t2, Vector &sVect, Vector &tVect ) { /* Compute the partial derivatives of X, Y, and Z with respect to S and T. */ sVect.Init( 0.0f, 0.0f, 0.0f ); tVect.Init( 0.0f, 0.0f, 0.0f ); // x, s, t Vector edge01( p1.x - p0.x, t1.x - t0.x, t1.y - t0.y ); Vector edge02( p2.x - p0.x, t2.x - t0.x, t2.y - t0.y ); Vector cross; CrossProduct( edge01, edge02, cross ); if ( fabs( cross.x ) > SMALL_FLOAT ) { sVect.x += -cross.y / cross.x; tVect.x += -cross.z / cross.x; } // y, s, t edge01.Init( p1.y - p0.y, t1.x - t0.x, t1.y - t0.y ); edge02.Init( p2.y - p0.y, t2.x - t0.x, t2.y - t0.y ); CrossProduct( edge01, edge02, cross ); if ( fabs( cross.x ) > SMALL_FLOAT ) { sVect.y += -cross.y / cross.x; tVect.y += -cross.z / cross.x; } // z, s, t edge01.Init( p1.z - p0.z, t1.x - t0.x, t1.y - t0.y ); edge02.Init( p2.z - p0.z, t2.x - t0.x, t2.y - t0.y ); CrossProduct( edge01, edge02, cross ); if( fabs( cross.x ) > SMALL_FLOAT ) { sVect.z += -cross.y / cross.x; tVect.z += -cross.z / cross.x; } } bool CMesh::CalculateTangentSpaceWorldLengthsPerFace( Vector2D *pLengthsOut, int nLengthsOut, float flMaxWorldPerUV ) { int nPositionOffset = FindFirstAttributeOffset( VERTEX_ELEMENT_POSITION ); int nTexOffset = FindFirstAttributeOffset( VERTEX_ELEMENT_TEXCOORD2D_0 ); if ( nTexOffset == -1 ) nTexOffset = FindFirstAttributeOffset( VERTEX_ELEMENT_TEXCOORD3D_0 ); if ( nPositionOffset == -1 || nTexOffset == -1 ) { Msg( "Need valid position and texcoord when creating world space tangent lengths!\n" ); return false; } // TODO: fix this to eventually iterate over quads int nIndicesPerFace = 3; int nFaces = m_nIndexCount / nIndicesPerFace; int nMaxIter = nIndicesPerFace; if ( nLengthsOut < nFaces ) { return false; } // Get the textures float flMaxUnitsX = flMaxWorldPerUV; float flMaxUnitsY = flMaxWorldPerUV; Vector sdir; Vector tdir; int index = 0; uint32 *pIndices = m_pIndices; for ( int f=0; f<nFaces; ++f ) { Vector2D vMin( FLT_MAX, FLT_MAX ); // find the min UV Vector vPos[3]; Vector2D vTex[3]; for ( int i=0; i<nMaxIter; ++i ) { float *pVertex = GetVertex( pIndices[ index + i ] ); vPos[i] = *( ( Vector* )( pVertex + nPositionOffset ) ); vTex[i] = *( ( Vector2D* )( pVertex + nTexOffset ) ); } // calc tan-space CalcTriangleTangentSpaceL( vPos[0], vPos[1], vPos[2], vTex[0], vTex[1], vTex[2], sdir, tdir ); pLengthsOut[ f ].x = MIN( flMaxUnitsX, sdir.Length() ); pLengthsOut[ f ].y = MIN( flMaxUnitsY, sdir.Length() ); index += nIndicesPerFace; } return true; } bool CMesh::CalculateFaceCenters( Vector *pCentersOut, int nCentersOut ) { int nPositionOffset = FindFirstAttributeOffset( VERTEX_ELEMENT_POSITION ); if ( nPositionOffset == -1 ) { Msg( "Need valid position to calculate face centers!\n" ); return false; } // TODO: fix this to eventually iterate over quads int nIndicesPerFace = 3; int nFaces = m_nIndexCount / nIndicesPerFace; float fIndicesPerFace = (float)nIndicesPerFace; if ( nCentersOut < nFaces ) { return false; } int index = 0; uint32 *pIndices = m_pIndices; for ( int f=0; f<nFaces; ++f ) { pCentersOut[ f ] = Vector(0,0,0); for ( int i=0; i<nIndicesPerFace; ++i ) { float *pVertex = GetVertex( pIndices[ index ] ); Vector &vPos = *( ( Vector* )( pVertex + nPositionOffset ) ); pCentersOut[ f ] += vPos; index++; } pCentersOut[ f ] /= fIndicesPerFace; } return true; } void DuplicateMesh( CMesh *pMeshOut, const CMesh &inputMesh ) { Assert( pMeshOut ); pMeshOut->AllocateMesh( inputMesh.m_nVertexCount, inputMesh.m_nIndexCount, inputMesh.m_nVertexStrideFloats, inputMesh.m_pAttributes, inputMesh.m_nAttributeCount ); Q_memcpy( pMeshOut->m_pVerts, inputMesh.m_pVerts, inputMesh.m_nVertexCount * inputMesh.m_nVertexStrideFloats * sizeof( float ) ); Q_memcpy( pMeshOut->m_pIndices, inputMesh.m_pIndices, inputMesh.m_nIndexCount * sizeof( uint32 ) ); pMeshOut->m_materialName = inputMesh.m_materialName; } // Shifts the UVs of an entire triangle to the origin defined by the smallest set of UV coordinates. // The mesh must be de-indexed to do this. This ensures our UVs are as close to the origin as possible. bool RationalizeUVsInPlace( CMesh *pMesh ) { // We need to have texcoords to rationalize int nTexcoordOffset = pMesh->FindFirstAttributeOffset( VERTEX_ELEMENT_TEXCOORD2D_0 ); if ( nTexcoordOffset < 0 ) { nTexcoordOffset = pMesh->FindFirstAttributeOffset( VERTEX_ELEMENT_TEXCOORD3D_0 ); } if ( nTexcoordOffset < 0 ) { return false; } if ( pMesh->m_nVertexCount != pMesh->m_nIndexCount ) return false; int nFaces = pMesh->m_nIndexCount / 3; float *pVertices = pMesh->m_pVerts; for ( int f=0; f<nFaces; ++f ) { Vector2D vMin(FLT_MAX,FLT_MAX); // find the min UV float *pTriVerts = pVertices; for ( int i=0; i<3; ++i ) { float flU = pVertices[ nTexcoordOffset ]; float flV = pVertices[ nTexcoordOffset + 1 ]; vMin.x = MIN( vMin.x, flU ); vMin.y = MIN( vMin.y, flV ); pVertices += pMesh->m_nVertexStrideFloats; } // clamp to the nearest whole rep Vector2D vMinFloor; vMinFloor.x = floor( vMin.x ); vMinFloor.y = floor( vMin.y ); // rationalize UVs across the face for ( int i=0; i<3; ++i ) { pTriVerts[ nTexcoordOffset ] -= vMinFloor.x; pTriVerts[ nTexcoordOffset + 1 ] -= vMinFloor.y; pTriVerts += pMesh->m_nVertexStrideFloats; } } return true; } // Shifts the UVs of an entire triangle to the origin defined by the smallest set of UV coordinates. // The mesh must be de-indexed to do this. This ensures our UVs are as close to the origin as possible. bool RationalizeUVs( CMesh *pRationalMeshOut, const CMesh &inputMesh ) { // We need our mesh de-indexed to rationalize UVs since we'll be shifting // UVs on a per-triangle basis if ( inputMesh.m_nVertexCount != inputMesh.m_nIndexCount ) { DeIndexMesh( pRationalMeshOut, inputMesh ); } else { pRationalMeshOut->AllocateMesh( inputMesh.m_nIndexCount, inputMesh.m_nIndexCount, inputMesh.m_nVertexStrideFloats, inputMesh.m_pAttributes, inputMesh.m_nAttributeCount ); } return RationalizeUVsInPlace( pRationalMeshOut ); } // Removes the need for an index buffer by storing redundant vertices // back into the vertex buffer. void DeIndexMesh( CMesh *pMeshOut, const CMesh &inputMesh ) { Assert( pMeshOut ); pMeshOut->AllocateMesh( inputMesh.m_nIndexCount, inputMesh.m_nIndexCount, inputMesh.m_nVertexStrideFloats, inputMesh.m_pAttributes, inputMesh.m_nAttributeCount ); float *pOutVerts = pMeshOut->m_pVerts; for ( int i=0; i<inputMesh.m_nIndexCount; ++i ) { CopyVertex( pOutVerts, inputMesh.GetVertex( inputMesh.m_pIndices[ i ] ), inputMesh.m_nVertexStrideFloats ); pOutVerts += inputMesh.m_nVertexStrideFloats; pMeshOut->m_pIndices[ i ] = i; } } // Combines two compatibles meshes into one. The two meshes must have the same vertex stride. bool ConcatMeshes( CMesh *pMeshOut, CMesh **ppMeshIn, int nInputMeshes, CMeshVertexAttribute *pAttributeOverride, int nAttributeOverrideCount, int nStrideOverride ) { Assert( pMeshOut && ppMeshIn && nInputMeshes > 1 ); // Find total sizes int nTotalIndices = 0; int nTotalVertices = 0; int nAttributes = 0; int nStrideFloats = 0; CMeshVertexAttribute *pAttributes = NULL; for ( int m=0; m<nInputMeshes; ++m ) { CMesh *pMesh = ppMeshIn[m]; nTotalIndices += pMesh->m_nIndexCount; nTotalVertices += pMesh->m_nVertexCount; if ( m == 0 ) { nAttributes = pMesh->m_nAttributeCount; nStrideFloats = pMesh->m_nVertexStrideFloats; pAttributes = pMesh->m_pAttributes; } else if ( nStrideOverride == 0 ) { if ( nStrideFloats != pMesh->m_nVertexStrideFloats ) { Warning( "Trying to concatenate differently strided meshes!\n" ); return false; } } } if ( pAttributeOverride && nAttributeOverrideCount > 0 ) { pAttributes = pAttributeOverride; nAttributes = nAttributeOverrideCount; } if ( nStrideOverride > 0 ) { nStrideFloats = nStrideOverride; } pMeshOut->AllocateMesh( nTotalVertices, nTotalIndices, nStrideFloats, pAttributes, nAttributes ); int nCurrentVertex = 0; int nCurrentIndex = 0; for ( int m=0; m<nInputMeshes; ++m ) { CMesh *pMesh = ppMeshIn[m]; for ( int v=0; v<pMesh->m_nVertexCount; ++v ) { CopyVertex( pMeshOut->GetVertex( nCurrentVertex + v ), pMesh->GetVertex( v ), nStrideFloats ); } for ( int i=0; i<pMesh->m_nIndexCount; ++i ) { pMeshOut->m_pIndices[ nCurrentIndex ] = pMesh->m_pIndices[ i ] + nCurrentVertex; nCurrentIndex ++; } nCurrentVertex += pMesh->m_nVertexCount; } return true; } // Recursively tessellates a triangle if it's UV ranges are greater than 1 void TessellateTriangle( CUtlBuffer *pOutTriangles, const float **ppVertsIn, int nTexcoordOffset, int nVertexStride ) { // Find the longest and second longest edges float flLongestUV = -1; float flSecondLongestUV = -1; int nLongestEdge = -1; int nSecondLongestEdge = -1; for ( int e=0; e<3; ++e ) { int eNext = ( e + 1 ) % 3; float flUDelta = fabs( ppVertsIn[ eNext ][ nTexcoordOffset ] - ppVertsIn[ e ][ nTexcoordOffset ] ); float flVDelta = fabs( ppVertsIn[ eNext ][ nTexcoordOffset + 1 ] - ppVertsIn[ e ][ nTexcoordOffset + 1 ] ); if ( flUDelta > flLongestUV ) { flSecondLongestUV = flLongestUV; nSecondLongestEdge = nLongestEdge; flLongestUV = flUDelta; nLongestEdge = e; } else if ( flUDelta > flSecondLongestUV ) { flSecondLongestUV = flUDelta; nSecondLongestEdge = e; } if ( flVDelta > flLongestUV ) { flSecondLongestUV = flLongestUV; nSecondLongestEdge = nLongestEdge; flLongestUV = flVDelta; nLongestEdge = e; } else if( flVDelta > flSecondLongestUV ) { flSecondLongestUV = flVDelta; nSecondLongestEdge = e; } } Assert( nLongestEdge > -1 && nSecondLongestEdge > -1 ); static const int pCornerTable[3][3] = { -1, // 0, 0 1, // 0, 1 0, // 0, 2 1, // 1, 0 -1, // 1, 1 2, // 1, 2 0, // 2, 0 2, // 2, 1 -1, // 2, 2 }; if ( flLongestUV > 1.0f ) { // Subdivide in half float *pEdgeVertex0 = new float[ nVertexStride ]; float *pEdgeVertex1 = new float[ nVertexStride ]; // Find the vertex that is the corner of the two longest edges int nCornerVertex = pCornerTable[ nLongestEdge ][ nSecondLongestEdge ]; Assert( nCornerVertex > -1 ); int nCornerPlus1 = ( nCornerVertex + 1 ) % 3; int nCornerPlus2 = ( nCornerVertex + 2 ) % 3; // Cut the two longest edges in half LerpVertex( pEdgeVertex0, ppVertsIn[ nCornerVertex ], ppVertsIn[ nCornerPlus1 ], 0.5f, nVertexStride ); LerpVertex( pEdgeVertex1, ppVertsIn[ nCornerPlus2 ], ppVertsIn[ nCornerVertex ], 0.5f, nVertexStride ); // Test the 3 children const float *pVerts0[3] = { ppVertsIn[ nCornerVertex ], pEdgeVertex0, pEdgeVertex1 }; TessellateTriangle( pOutTriangles, pVerts0, nTexcoordOffset, nVertexStride ); const float *pVerts1[3] = { pEdgeVertex0, ppVertsIn[ nCornerPlus1 ], ppVertsIn[ nCornerPlus2 ] }; TessellateTriangle( pOutTriangles, pVerts1, nTexcoordOffset, nVertexStride ); const float *pVerts2[3] = { pEdgeVertex0, ppVertsIn[ nCornerPlus2 ], pEdgeVertex1 }; TessellateTriangle( pOutTriangles, pVerts2, nTexcoordOffset, nVertexStride ); delete []pEdgeVertex0; delete []pEdgeVertex1; } else { // This triangle is OK pOutTriangles->Put( ppVertsIn[ 0 ], nVertexStride * sizeof( float ) ); pOutTriangles->Put( ppVertsIn[ 1 ], nVertexStride * sizeof( float ) ); pOutTriangles->Put( ppVertsIn[ 2 ], nVertexStride * sizeof( float ) ); } } // Subdivide triangles when their UV coordinates exceed the [0..1] range bool TessellateOnWrappedUV( CMesh *pMeshOut, const CMesh &inputMesh ) { // We need to have texcoords to rationalize int nTexcoordOffset = inputMesh.FindFirstAttributeOffset( VERTEX_ELEMENT_TEXCOORD2D_0 ); if ( nTexcoordOffset < 0 ) { nTexcoordOffset = inputMesh.FindFirstAttributeOffset( VERTEX_ELEMENT_TEXCOORD3D_0 ); } if ( nTexcoordOffset < 0 ) { return false; } // Tessellate the triangles CUtlBuffer triangleBuffer; for ( int i=0; i<inputMesh.m_nIndexCount; i += 3 ) { const float *pVerts[3]; pVerts[ 0 ] = inputMesh.GetVertex( inputMesh.m_pIndices[ i ] ); pVerts[ 1 ] = inputMesh.GetVertex( inputMesh.m_pIndices[ i + 1 ] ); pVerts[ 2 ] = inputMesh.GetVertex( inputMesh.m_pIndices[ i + 2 ] ); TessellateTriangle( &triangleBuffer, pVerts, nTexcoordOffset, inputMesh.m_nVertexStrideFloats ); } int nNewVertices = triangleBuffer.TellPut() / ( inputMesh.m_nVertexStrideFloats * sizeof( float ) ); pMeshOut->AllocateMesh( nNewVertices, nNewVertices, inputMesh.m_nVertexStrideFloats, inputMesh.m_pAttributes, inputMesh.m_nAttributeCount ); Q_memcpy( pMeshOut->m_pVerts, triangleBuffer.Base(), triangleBuffer.TellPut() ); for ( int i=0; i<nNewVertices; ++i ) { pMeshOut->m_pIndices[ i ] = i; } return RationalizeUVsInPlace( pMeshOut ); } bool VertexMatches( const float *pV0, const float *pV1, const float *pEpsilons, int nEpsilons ) { for ( int i = 0; i < nEpsilons; ++i ) { float flDelta = pV0[i] - pV1[i]; if ( fabs( flDelta ) > pEpsilons[ i ] ) return false; } return true; } class CVertexKDNode { public: uint32 m_nChildren[2]; int32 m_nAxis; float m_flSplit; inline bool IsLeaf() const { return m_nAxis == 0xFF ? true : false; } inline void InitAsSplit( float flSplit, int nAxis, int nCount ) { m_nAxis = nAxis; m_flSplit = flSplit; m_nChildren[0] = uint32(~0); m_nChildren[1] = uint32(~0); } inline int GetLeafVertCount() const { Assert(IsLeaf()); return m_nChildren[1]; } inline int GetLeafVertStart() const { Assert(IsLeaf()); return m_nChildren[0]; } void InitAsLeaf( int nStart, int nCount ) { m_nAxis = 0xFF; m_flSplit = 0; m_nChildren[0] = nStart; m_nChildren[1] = nCount; } }; class CVertexKDTree { public: CVertexKDTree() {} void BuildMidpoint( const float *pVerts, int nVertexCount, int nVertexStrideFloats ); void FindVertsInBox( CUtlVectorFixedGrowable<const float *, 64> &list, const Vector &mins, const Vector &maxs, int nStartNode = 0 ); private: int FindMidpointIndex( int nStart, int nCount, int nAxis, float flSplit ); void ComputeBounds( Vector *pMins, Vector *pMaxs, int nStart, int nCount ); int BuildNode( int nStart, int nCount ); // recursive CUtlVector<CVertexKDNode> m_tree; CUtlVector<const float *> m_vertexList; }; int CVertexKDTree::FindMidpointIndex( int nStart, int nCount, int nAxis, float flSplit ) { // partition the verts in this run on the axis with the greatest extent const float **pBase = m_vertexList.Base() + nStart; int nMid = nCount/2; int nEnd = nCount; for ( int i = nMid; i < nEnd; i++ ) { if ( pBase[i][nAxis] < flSplit ) { const float *pSwap = pBase[nMid]; pBase[nMid] = pBase[i]; pBase[i] = pSwap; nMid++; } } for ( int i = nMid-1; i >= 0; i-- ) { if ( pBase[i][nAxis] >= flSplit ) { const float *pSwap = pBase[nMid-1]; pBase[nMid-1] = pBase[i]; pBase[i] = pSwap; nMid--; } } return nMid + nStart; } void CVertexKDTree::FindVertsInBox( CUtlVectorFixedGrowable<const float *, 64> &list, const Vector &mins, const Vector &maxs, int nStartNode ) { const CVertexKDNode &node = m_tree[nStartNode]; if ( node.IsLeaf() ) { int nVertCount = node.GetLeafVertCount(); int nVertStart = node.GetLeafVertStart(); // check each vert at this leaf for ( int i = 0; i < nVertCount; i++ ) { const float *pVert = m_vertexList[nVertStart + i]; // is point in box, add to tail if ( pVert[0] >= mins.x && pVert[0] <= maxs.x && pVert[1] >= mins.y && pVert[1] <= maxs.y && pVert[2] >= mins.z && pVert[2] <= maxs.z ) { list.AddToTail( pVert ); } } } else { // recurse to the sides of the tree that contain the box int nAxis = node.m_nAxis; if ( mins[nAxis] <= node.m_flSplit ) { FindVertsInBox( list, mins, maxs, node.m_nChildren[0] ); } if ( maxs[nAxis] >= node.m_flSplit ) { FindVertsInBox( list, mins, maxs, node.m_nChildren[1] ); } } } void CVertexKDTree::ComputeBounds( Vector *pMins, Vector *pMaxs, int nStart, int nCount ) { Vector mins = *(Vector *)m_vertexList[nStart]; Vector maxs = mins; for ( int i = 1; i < nCount; i++ ) { mins.x = MIN(mins.x, m_vertexList[i+nStart][0]); maxs.x = MAX(maxs.x, m_vertexList[i+nStart][0]); mins.y = MIN(mins.y, m_vertexList[i+nStart][1]); maxs.y = MAX(maxs.y, m_vertexList[i+nStart][1]); mins.z = MIN(mins.z, m_vertexList[i+nStart][2]); maxs.z = MAX(maxs.z, m_vertexList[i+nStart][2]); } if ( pMins ) { *pMins = mins; } if ( pMaxs ) { *pMaxs = maxs; } } inline int GreatestAxis( const Vector &v ) { if ( v.x >= v.y ) { return v.x > v.z ? 0 : 2; } return v.y > v.z ? 1 : 2; } int CVertexKDTree::BuildNode( int nStart, int nCount ) { if ( nCount > 8 ) { Vector mins, maxs; ComputeBounds( &mins, &maxs, nStart, nCount ); int nAxis = GreatestAxis( maxs - mins ); float flSplit = 0.5f * (maxs[nAxis] + mins[nAxis]); int nSplit = FindMidpointIndex( nStart, nCount, nAxis, flSplit ); int nLeftCount = nSplit - nStart; int nRightCount = (nStart + nCount) - nSplit; if ( nLeftCount != 0 && nRightCount != 0 ) { int nIndex = m_tree.AddToTail(); int nLeft = BuildNode( nStart, nLeftCount ); int nRight = BuildNode( nSplit, nRightCount ); m_tree[nIndex].InitAsSplit( flSplit, nAxis, nCount ); m_tree[nIndex].m_nChildren[0] = nLeft; m_tree[nIndex].m_nChildren[1] = nRight; return nIndex; } } int nIndex = m_tree.AddToTail(); m_tree[nIndex].InitAsLeaf( nStart, nCount ); return nIndex; } void CVertexKDTree::BuildMidpoint( const float *pVerts, int nVertexCount, int nVertexStrideFloats ) { m_vertexList.SetCount( nVertexCount ); for ( int i = 0; i < nVertexCount; i++ ) { m_vertexList[i] = pVerts + i * nVertexStrideFloats; } BuildNode( 0, nVertexCount ); } // TODO: Extreme welds can cause faces to flip/invert. Should we add an option to detect and avoid this? bool WeldVertices( CMesh *pMeshOut, const CMesh &inputMesh, float *pEpsilons, int nEpsilons ) { // Must have epsilons for at least the first three position components if ( nEpsilons != inputMesh.m_nVertexStrideFloats ) return false; CVertexKDTree searchTree; searchTree.BuildMidpoint( inputMesh.m_pVerts, inputMesh.m_nVertexCount, inputMesh.m_nVertexStrideFloats ); CUtlVector< const float* > inOrderVertices; CUtlVector<uint32> remapTable; const uint32 nEmptyValue = uint32(~0); remapTable.SetCount( inputMesh.m_nVertexCount ); remapTable.FillWithValue( nEmptyValue ); Vector mins, maxs; CUtlVectorFixedGrowable<const float *, 64> list; for ( int i = 0; i < inputMesh.m_nVertexCount; i++ ) { // skip if already welded this vertex if ( remapTable[i] != nEmptyValue ) continue; // build weld box around vert const float *pVertex = inputMesh.GetVertex(i); mins.x = pVertex[0] - pEpsilons[0]; mins.y = pVertex[1] - pEpsilons[1]; mins.z = pVertex[2] - pEpsilons[2]; maxs.x = pVertex[0] + pEpsilons[0]; maxs.y = pVertex[1] + pEpsilons[1]; maxs.z = pVertex[2] + pEpsilons[2]; list.RemoveAll(); searchTree.FindVertsInBox( list, mins, maxs ); for ( int j = 0; j < list.Count(); j++ ) { const float *pCheck = list[j]; // only check lower indexed vertices, the opposite check will happen in future iterations if ( pCheck >= pVertex ) continue; int nMatchIndex = (list[j] - inputMesh.m_pVerts) / inputMesh.m_nVertexStrideFloats; Assert(remapTable[nMatchIndex] != nEmptyValue); // match the output vert instead of the input - this vert may have been welded to some other vert pCheck = inOrderVertices[remapTable[nMatchIndex]]; if ( VertexMatches( pVertex, pCheck, pEpsilons, inputMesh.m_nVertexStrideFloats ) ) { remapTable[i] = remapTable[nMatchIndex]; break; } } if ( remapTable[i] == nEmptyValue ) { int nVertexIndex = inOrderVertices.AddToTail( pVertex ); remapTable[i] = nVertexIndex; } } // allocate enough for all new verts int nNewVerts = inOrderVertices.Count(); pMeshOut->AllocateMesh( nNewVerts, inputMesh.m_nIndexCount, inputMesh.m_nVertexStrideFloats, inputMesh.m_pAttributes, inputMesh.m_nAttributeCount ); // copy the welded verts out for ( int v=0; v<nNewVerts; ++v ) { CopyVertex( pMeshOut->GetVertex( v ), inOrderVertices[ v ], inputMesh.m_nVertexStrideFloats ); } // now remap the indices for ( int i=0; i<inputMesh.m_nIndexCount; ++i ) { pMeshOut->m_pIndices[ i ] = remapTable[ inputMesh.m_pIndices[ i ] ]; Assert( pMeshOut->m_pIndices[i] < (uint32)nNewVerts ); } return true; } void CleanMesh( CMesh *pMeshOut, const CMesh &inputMesh ) { CUtlVector<uint32> indexMap; indexMap.SetCount( inputMesh.m_nVertexCount ); const uint32 nUnusedIndex = ~0UL; for ( int i = 0; i < inputMesh.m_nVertexCount; i++ ) { indexMap[i] = nUnusedIndex; } // build a compact map of vertices uint32 nVertexOut = 0; int nIndexOut = 0; for ( int i = 0; i < inputMesh.m_nIndexCount; i += 3 ) { // skip degenerate triangles int nV0 = inputMesh.m_pIndices[i+0]; int nV1 = inputMesh.m_pIndices[i+1]; int nV2 = inputMesh.m_pIndices[i+2]; if ( nV0 == nV1 || nV1 == nV2 || nV0 == nV2 ) continue; if ( indexMap[nV0] == nUnusedIndex ) { indexMap[nV0] = nVertexOut++; } if ( indexMap[nV1] == nUnusedIndex ) { indexMap[nV1] = nVertexOut++; } if ( indexMap[nV2] == nUnusedIndex ) { indexMap[nV2] = nVertexOut++; } nIndexOut += 3; } // allocate the cleaned mesh now that we know its size pMeshOut->AllocateMesh( nVertexOut, nIndexOut, inputMesh.m_nVertexStrideFloats, inputMesh.m_pAttributes, inputMesh.m_nAttributeCount ); nIndexOut = 0; for ( int i = 0; i < inputMesh.m_nIndexCount; i += 3 ) { // skip degenerate triangles (again) int nV0 = inputMesh.m_pIndices[i+0]; int nV1 = inputMesh.m_pIndices[i+1]; int nV2 = inputMesh.m_pIndices[i+2]; if ( nV0 == nV1 || nV1 == nV2 || nV0 == nV2 ) continue; // copy and remap the indices pMeshOut->m_pIndices[ nIndexOut++ ] = indexMap[ nV0 ]; pMeshOut->m_pIndices[ nIndexOut++ ] = indexMap[ nV1 ]; pMeshOut->m_pIndices[ nIndexOut++ ] = indexMap[ nV2 ]; } Assert( nIndexOut == pMeshOut->m_nIndexCount ); // copy out the vertices in order by use for ( int v = 0; v < inputMesh.m_nVertexCount; ++v ) { if ( indexMap[v] == nUnusedIndex ) continue; uint32 nVOut = indexMap[v]; CopyVertex( pMeshOut->GetVertex( nVOut ), inputMesh.GetVertex(v), inputMesh.m_nVertexStrideFloats ); } } // partitions the mesh into an array of meshes, each with <= nMaxVertex vertices void SplitMesh( CUtlVector<CMesh> &list, const CMesh &input, int nMaxVertex ) { const uint32 nUnusedIndex = uint32(~0); CUtlVector<uint32> indexMap; indexMap.SetCount( input.m_nVertexCount ); for ( int i = 0; i < input.m_nVertexCount; i++ ) { indexMap[i] = nUnusedIndex; } CUtlVectorFixedGrowable<int, 32> indexCount; CUtlVectorFixedGrowable<int, 32> vertexCount; const uint32 *pIndex = input.m_pIndices; const int nCount = input.m_nIndexCount; int nIndex = 0; int nIndexOut = 0; int nVertexOut = 0; // count how many you need while ( nIndex < nCount ) { if ( indexMap[pIndex[nIndex]] == nUnusedIndex ) { indexMap[pIndex[nIndex]] = nVertexOut; nVertexOut++; if ( nVertexOut >= nMaxVertex ) { nIndexOut -= (nIndex%3); nIndex -= (nIndex%3); indexCount.AddToTail(nIndexOut); vertexCount.AddToTail(nVertexOut); for ( int i = 0; i < input.m_nVertexCount; i++ ) { indexMap[i] = nUnusedIndex; } nVertexOut = 0; nIndexOut = 0; continue; } } nIndexOut++; nIndex++; } if ( nVertexOut > 0 && nIndexOut > 0 ) { indexCount.AddToTail( nIndexOut ); vertexCount.AddToTail( nVertexOut ); } // now allocate the actual meshes and populate them nIndex = 0; int nMeshCount = indexCount.Count(); list.SetCount( nMeshCount ); for ( int nMesh = 0; nMesh < nMeshCount; nMesh++ ) { list[nMesh].AllocateMesh( vertexCount[nMesh], indexCount[nMesh], input.m_nVertexStrideFloats, input.m_pAttributes, input.m_nAttributeCount ); int nIndexCount = indexCount[nMesh]; nVertexOut = 0; Assert( (nIndexCount%3) == 0 ); Assert( (nIndex%3) == 0 ); for ( int i = 0; i < input.m_nVertexCount; i++ ) { indexMap[i] = nUnusedIndex; } for ( int j = 0; j < nIndexCount; j++ ) { int nV0 = pIndex[nIndex]; if ( indexMap[nV0] == nUnusedIndex ) { CopyVertex( list[nMesh].GetVertex(nVertexOut), input.GetVertex(nV0), input.m_nVertexStrideFloats ); indexMap[nV0] = nVertexOut; nVertexOut++; Assert( nVertexOut <= vertexCount[nMesh] ); } list[nMesh].m_pIndices[j] = indexMap[nV0]; nIndex++; } } } bool CMesh::HasSkinningData()const { return GetSkinningDataFields().HasSkinningData(); } // Find and return joint weight and joint indices attributes in a convenient struct CMesh::SkinningDataFields_t CMesh::GetSkinningDataFields()const { SkinningDataFields_t dataFields; for ( int a = 0; a < m_nAttributeCount; ++a ) { if ( m_pAttributes[ a ].IsJointWeight() ) { if ( dataFields.m_nBoneWeights < 0 /*|| m_pAttributes[ a ].m_nSemanticIndex < m_pAttributes[ dataFields.m_nBoneWeights ].m_nSemanticIndex*/ ) { dataFields.m_nBoneWeights = a; } } if ( m_pAttributes[ a ].IsJointIndices() ) { if ( dataFields.m_nBoneIndices < 0 /*|| m_pAttributes[ a ].m_nSemanticIndex < m_pAttributes[ dataFields.m_nBoneIndices ].m_nSemanticIndex*/ ) { dataFields.m_nBoneIndices = a; } } } // we must have either both or none or indices but not just weights by themselves Assert( ( dataFields.m_nBoneWeights < 0 ) || ( dataFields.m_nBoneIndices >= 0 ) ); return dataFields; } CMesh::ClothDataFields_t CMesh::GetClothDataFields() const { ClothDataFields_t dataFields; for ( int a = 0; a < m_nAttributeCount; ++a ) { if ( m_pAttributes[ a ].IsClothEnable() ) { dataFields.m_nClothEnable = a; } if ( m_pAttributes[ a ].IsPositionRemap() ) { dataFields.m_nPositionRemap = a; } } return dataFields; } int CMesh::GetAttrSizeFloats( int nAttribute ) { if ( nAttribute < 0 || nAttribute >= m_nAttributeCount ) return 0; if ( nAttribute + 1 >= m_nAttributeCount ) return m_nVertexStrideFloats - m_pAttributes[ nAttribute ].m_nOffsetFloats; return m_pAttributes[ nAttribute + 1 ].m_nOffsetFloats - m_pAttributes[ nAttribute ].m_nOffsetFloats; } float CMesh::GetVertexJointSumWeight( const SkinningDataFields_t &skinData, int nVertex, const CVarBitVec &jointSet ) { if ( skinData.m_nBoneIndices < 0 ) { return 0.0f; // this isn't a skinned mesh } const CMeshVertexAttribute &attrIndices = m_pAttributes[ skinData.m_nBoneIndices ]; int nWeightAttrSize= Min( GetAttrSizeFloats( skinData.m_nBoneIndices ), GetAttrSizeFloats( skinData.m_nBoneWeights ) ); if ( nWeightAttrSize > 1 ) { Assert( uint( skinData.m_nBoneWeights ) < uint( m_nAttributeCount ) ); const CMeshVertexAttribute &attrWeights = m_pAttributes[ skinData.m_nBoneWeights ]; //Assert( attrIndices.m_nSizeFloats == attrWeights.m_nSizeFloats ); float flSumWeight = 0.0f; for ( int nWeightIndex = 0; nWeightIndex < nWeightAttrSize; ++nWeightIndex ) { const float *pVertex = GetVertex( nVertex ); int nAssignedJoint = *( int* )( pVertex + attrIndices.m_nOffsetFloats + nWeightIndex ); if ( jointSet.IsBitSet( nAssignedJoint ) ) { float flAssignedWeight = pVertex[ attrWeights.m_nOffsetFloats + nWeightIndex ]; flSumWeight += flAssignedWeight; } } return flSumWeight; // didn't find the joint } else { // there's just one weight int nAssignedJoint = *( int* )( GetVertex( nVertex ) + attrIndices.m_nOffsetFloats ); return jointSet.IsBitSet( nAssignedJoint ) ? 1.0f : 0.0f; } }