//=========== 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 ( 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; edgeHash.Init( m_nIndexCount * 2 ); int nFaces = m_nIndexCount / 3; int nIndex = 0; for ( int f=0; f. bool CMesh::CalculateIndicentFacesForVertices( CUtlLinkedList *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 m_nVertexStrideFloats ) { RestrideVertexBuffer( nNewStrideFloats ); } } typedef CUtlVector 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 vertToFaceMap; vertToFaceMap.AddMultipleToTail( m_nVertexCount ); int index = 0; uint32 *pIndices = m_pIndices; for( int faceID = 0; faceID < nFaces; faceID++ ) { for ( int i=0; i faceSVect; CUtlVector faceTVect; faceSVect.AddMultipleToTail( nFaces ); faceTVect.AddMultipleToTail( nFaces ); index = 0; for ( int f=0; f 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; fAllocateMesh( 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; fm_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; im_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; mm_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; mm_nVertexCount; ++v ) { CopyVertex( pMeshOut->GetVertex( nCurrentVertex + v ), pMesh->GetVertex( v ), nStrideFloats ); } for ( int i=0; im_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; iAllocateMesh( nNewVertices, nNewVertices, inputMesh.m_nVertexStrideFloats, inputMesh.m_pAttributes, inputMesh.m_nAttributeCount ); Q_memcpy( pMeshOut->m_pVerts, triangleBuffer.Base(), triangleBuffer.TellPut() ); for ( int i=0; im_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 &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 m_tree; CUtlVector 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 &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 remapTable; const uint32 nEmptyValue = uint32(~0); remapTable.SetCount( inputMesh.m_nVertexCount ); remapTable.FillWithValue( nEmptyValue ); Vector mins, maxs; CUtlVectorFixedGrowable 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; vGetVertex( v ), inOrderVertices[ v ], inputMesh.m_nVertexStrideFloats ); } // now remap the indices for ( int i=0; im_pIndices[ i ] = remapTable[ inputMesh.m_pIndices[ i ] ]; Assert( pMeshOut->m_pIndices[i] < (uint32)nNewVerts ); } return true; } void CleanMesh( CMesh *pMeshOut, const CMesh &inputMesh ) { CUtlVector 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 &list, const CMesh &input, int nMaxVertex ) { const uint32 nUnusedIndex = uint32(~0); CUtlVector indexMap; indexMap.SetCount( input.m_nVertexCount ); for ( int i = 0; i < input.m_nVertexCount; i++ ) { indexMap[i] = nUnusedIndex; } CUtlVectorFixedGrowable indexCount; CUtlVectorFixedGrowable 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; } }