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.
 
 
 
 
 
 

1543 lines
45 KiB

//=========== 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;
}
}